diff --git a/ConsoleAppTest/ConsoleAppTest.csproj b/ConsoleAppTest/ConsoleAppTest.csproj new file mode 100644 index 0000000..373f49d --- /dev/null +++ b/ConsoleAppTest/ConsoleAppTest.csproj @@ -0,0 +1,15 @@ + + + + Exe + net6.0 + enable + enable + AnyCPU;x86;x64 + + + + + + + diff --git a/ConsoleAppTest/ConsoleAppTest.csproj.user b/ConsoleAppTest/ConsoleAppTest.csproj.user new file mode 100644 index 0000000..70b12d7 --- /dev/null +++ b/ConsoleAppTest/ConsoleAppTest.csproj.user @@ -0,0 +1,6 @@ + + + + <_LastSelectedProfileId>D:\PPWin\SecureDNSClient\ConsoleAppTest\Properties\PublishProfiles\FolderProfile.pubxml + + \ No newline at end of file diff --git a/ConsoleAppTest/Program.cs b/ConsoleAppTest/Program.cs new file mode 100644 index 0000000..0a7c128 --- /dev/null +++ b/ConsoleAppTest/Program.cs @@ -0,0 +1,180 @@ +using MsmhToolsClass; +using MsmhToolsClass.MsmhAgnosticServer; +using System.Net; + +namespace ConsoleAppTest; + +internal class Program +{ + static async Task Main(string[] args) + { + MsmhAgnosticServer server1 = new(); + MsmhAgnosticServer server2 = new(); + + server1.OnRequestReceived += Server_OnRequestReceived; + server2.OnRequestReceived += Server_OnRequestReceived; + + string dohUrl = "https://dns.cloudflare.com/dns-query"; + string dohHost = "dns.cloudflare.com"; + string dohCleanIP = "104.16.132.229"; + string cfClenIP = "172.66.192.140"; + + // Set DnsRules Content + string dnsRulesContent = $"{dohHost}|{dohCleanIP};"; + + // Set ProxyRules Content (Apply Fake DNS and White List Program) + string proxyRulesContent = $"{dohHost}|{dohCleanIP};"; + proxyRulesContent += $"\n{dohCleanIP}|+;"; + proxyRulesContent += $"\n*|-;"; // Block Other Requests + + List dnsServers1 = new() + { + //"sdns://AQMAAAAAAAAAEjEwMy44Ny42OC4xOTQ6ODQ0MyAxXDKkdrOao8ZeLyu7vTnVrT0C7YlPNNf6trdMkje7QR8yLmRuc2NyeXB0LWNlcnQuZG5zLmJlYmFzaWQuY29t", + //"tls://free.shecan.ir", + //"https://free.shecan.ir/dns-query", + //"tls://dns.google", + //"https://notworking.com/dns-query", + //"tcp://8.8.8.8:53", + //"udp://8.8.8.8:53", + //"tcp://1.1.1.1:53", + //"https://every1dns.com/dns-query", + //"https://dns.cloudflare.com/dns-query", + //"https://dns.google/dns-query", + //"https://45.90.29.204:443/dns-query", + //"udp://208.67.222.222:5353", + //"9.9.9.9:9953", + dohUrl + }; + + AgnosticSettings settings1 = new() + { + Working_Mode = AgnosticSettings.WorkingMode.DnsAndProxy, + ListenerPort = 53, + DnsTimeoutSec = 10, + ProxyTimeoutSec = 40, + MaxRequests = 1000000, + KillOnCpuUsage = 40, + DNSs = dnsServers1, + BootstrapIpAddress = IPAddress.Loopback, + BootstrapPort = 53, + AllowInsecure = false, + BlockPort80 = true, + CloudflareCleanIP = cfClenIP, + UpstreamProxyScheme = $"socks5://{IPAddress.Loopback}:53", + //ApplyUpstreamOnlyToBlockedIps = true + }; + + AgnosticProgram.Fragment fragment = new(); + fragment.Set(AgnosticProgram.Fragment.Mode.Program, 50, AgnosticProgram.Fragment.ChunkMode.SNI, 5, 2, 1); + server1.EnableFragment(fragment); + + AgnosticProgram.DnsRules dnsRules1 = new(); + dnsRules1.Set(AgnosticProgram.DnsRules.Mode.Text, dnsRulesContent); + server1.EnableDnsRules(dnsRules1); + + AgnosticProgram.ProxyRules proxyRules1 = new(); + proxyRules1.Set(AgnosticProgram.ProxyRules.Mode.Text, proxyRulesContent); + server1.EnableProxyRules(proxyRules1); + + + List dnsServers2 = new() + { + $"udp://{IPAddress.Loopback}:53" + }; + + AgnosticSettings settings2 = new() + { + Working_Mode = AgnosticSettings.WorkingMode.Dns, + ListenerPort = 443, + DnsTimeoutSec = 10, + DNSs = dnsServers2, + MaxRequests = 1000000, + BootstrapIpAddress = IPAddress.Loopback, + BootstrapPort = 53, + AllowInsecure = false, + //UpstreamProxyScheme = "socks5://192.168.1.120:10808", + //ApplyUpstreamOnlyToBlockedIps = true + }; + + AgnosticSettingsSSL settingsSSL = new(true) + { + EnableSSL = true, + //ChangeSni = true, + //DefaultSni = "speedtest.net", + }; + + await server2.EnableSSL(settingsSSL); + + + + server1.Start(settings1); + server2.Start(settings2); + + + + DnsMessage dmQ1 = DnsMessage.CreateQuery(DnsEnums.DnsProtocol.UDP, "youtube.com", DnsEnums.RRType.A, DnsEnums.CLASS.IN); + DnsMessage.TryWrite(dmQ1, out byte[] dmQBytes1); + DnsMessage dmQ2 = DnsMessage.CreateQuery(DnsEnums.DnsProtocol.DoH, "mail.yahoo.com", DnsEnums.RRType.A, DnsEnums.CLASS.IN); + DnsMessage.TryWrite(dmQ2, out byte[] dmQBytes2); + string dns = "udp://127.0.0.1:53"; + string doh = "https://127.0.0.1:443/dns-query"; + //doh = "https://dns-cloudflare.com/dns-query"; + + IPAddress bootIP = IPAddress.Parse("8.8.8.8"); + int bootPort = 53; + + + + HttpRequest httpRequest = new() + { + URI = new Uri("https://google.com"), + ProxyScheme = "socks5://127.0.0.1:443", + TimeoutMS = 5000 + }; + + + int n = 0; + + + tt1(); + //tt1(); + //tt1(); + tt2(); + //tt2(); + //tt2(); + + + async void tt1() + { + while (true) + { + //await Task.Delay(50); + n++; + await DnsClient.QueryAsync(dmQBytes1, DnsEnums.DnsProtocol.UDP, dns, true, bootIP, bootPort, 1000, CancellationToken.None); + //HttpRequest.SendAsync(httpRequest); + //if (n == 5000) break; + } + } + + async void tt2() + { + while (true) + { + //await Task.Delay(50); + n++; + await DnsClient.QueryAsync(dmQBytes2, DnsEnums.DnsProtocol.DoH, doh, true, bootIP, bootPort, 1000, CancellationToken.None); + //HttpRequest.SendAsync(httpRequest); + if (n == 5000) break; + } + } + + + Console.ReadLine(); + } + + private static void Server_OnRequestReceived(object? sender, EventArgs e) + { + if (sender is not string msg) return; + Console.WriteLine(msg); + } +} \ No newline at end of file diff --git a/ConsoleAppTest/Properties/PublishProfiles/FolderProfile.pubxml b/ConsoleAppTest/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..92a3de4 --- /dev/null +++ b/ConsoleAppTest/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,18 @@ + + + + + Release + Any CPU + bin\Release\net6.0\publish\win-x86\ + FileSystem + <_TargetId>Folder + net6.0 + win-x86 + false + true + true + + \ No newline at end of file diff --git a/ConsoleAppTest/Properties/PublishProfiles/FolderProfile.pubxml.user b/ConsoleAppTest/Properties/PublishProfiles/FolderProfile.pubxml.user new file mode 100644 index 0000000..8a928bc --- /dev/null +++ b/ConsoleAppTest/Properties/PublishProfiles/FolderProfile.pubxml.user @@ -0,0 +1,10 @@ + + + + + True|2024-03-22T20:30:05.2407815Z; + + + \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass.csproj b/MsmhToolsClass/MsmhToolsClass.csproj index caa5dba..cf16fad 100644 --- a/MsmhToolsClass/MsmhToolsClass.csproj +++ b/MsmhToolsClass/MsmhToolsClass.csproj @@ -10,11 +10,193 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + diff --git a/MsmhToolsClass/MsmhToolsClass/ByteArrayTool.cs b/MsmhToolsClass/MsmhToolsClass/ByteArrayTool.cs index 82a6c57..389d4c6 100644 --- a/MsmhToolsClass/MsmhToolsClass/ByteArrayTool.cs +++ b/MsmhToolsClass/MsmhToolsClass/ByteArrayTool.cs @@ -1,47 +1,264 @@ -using System; +using System.Diagnostics; namespace MsmhToolsClass; -public class ByteArrayTool +public static class ByteArrayTool { - public static int Search(byte[] src, byte[] pattern) + public static byte[] Append(this byte[] orig, byte[] append) { - int maxFirstCharSlot = src.Length - pattern.Length + 1; - for (int i = 0; i < maxFirstCharSlot; i++) + if (append == null) return orig; + if (orig == null) return append; + + byte[] bytes = new byte[orig.Length + append.Length]; + Buffer.BlockCopy(orig, 0, bytes, 0, orig.Length); + Buffer.BlockCopy(append, 0, bytes, orig.Length, append.Length); + return bytes; + } + + public static bool CanFitInBits(int number, int numberOfBits) + { + int maxValue = (1 << numberOfBits) - 1; + return number >= 0 && number <= maxValue; + } + + public static byte[] GenerateRandom(int length) + { + byte[] bytes = new byte[length]; + try { - if (src[i] != pattern[0]) // compare only first byte - continue; + Random random = new(length); + random.NextBytes(bytes); + // OR: LibSodium.randombytes_buf(bytes, length); + } + catch (Exception ex) + { + Debug.WriteLine("GenerateRandom2: " + ex.Message); + } + return bytes; + } - // found a match on first byte, now try to match rest of the pattern - for (int j = pattern.Length - 1; j >= 1; j--) + public static bool TryConvertBytesToUInt16(byte[] bytes, out ushort result) + { + try + { + int n = (bytes[0] << 8) + bytes[1]; + if (n < 0) { - if (src[i + j] != pattern[j]) break; - if (j == 1) return i; + result = 0; + return false; } + result = Convert.ToUInt16(n); + // OR + //if (BitConverter.IsLittleEndian) Array.Reverse(bytes); + //result = BitConverter.ToUInt16(bytes, 0); + return true; + } + catch (Exception) + { + result = 0; + return false; } - return -1; } - /// - /// Fully read a stream into a byte array. - /// - /// The input stream. - /// A byte array containing the data read from the stream. - public static byte[] StreamToBytes(Stream input) + public static bool TryConvertBytesToUInt32(byte[] bytes, out uint result) { - if (input == null) throw new ArgumentNullException(nameof(input)); - if (!input.CanRead) throw new InvalidOperationException("Input stream is not readable"); + try + { + int n = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3]; + if (n < 0) + { + result = 0; + return false; + } + result = Convert.ToUInt32(n); + return true; + } + catch (Exception) + { + result = 0; + return false; + } + } + + public static bool TryConvertUInt16ToBytes(ushort value, out byte[] result) + { + try + { + byte[] bytes = new byte[2]; + bytes[0] = (byte)(value >> 8); + bytes[1] = (byte)value; + result = bytes; + // OR + //byte[] result0 = BitConverter.GetBytes(value); + //result = BitConverter.IsLittleEndian ? result0.Reverse().ToArray() : result0; + return true; + } + catch (Exception) + { + result = Array.Empty(); + return false; + } + } + + public static bool TryConvertUInt32ToBytes(uint value, out byte[] result) + { + try + { + byte[] bytes = new byte[4]; + bytes[0] = (byte)(value >> 24); + bytes[1] = (byte)(value >> 16); + bytes[2] = (byte)(value >> 8); + bytes[3] = (byte)value; + result = bytes; + return true; + } + catch (Exception) + { + result = Array.Empty(); + return false; + } + } + + public static bool TryConvertToBinary(byte[] buffer, out string result) + { + try + { + result = string.Empty; + foreach (byte b in buffer) result += Convert.ToString(b, 2).PadLeft(8, '0'); + return true; + } + catch (Exception) + { + result = string.Empty; + return false; + } + } - byte[] buffer = new byte[16 * 1024]; + public static bool TryConvertToBinary(byte oneByte, out string result) + { + try + { + result = Convert.ToString(oneByte, 2).PadLeft(8, '0'); + return true; + } + catch (Exception) + { + result = string.Empty; + return false; + } + } + + public static bool TryConvertToBinary(ushort value, out string result) + { + try + { + int binaryBase = 2; + result = Convert.ToString(value, binaryBase); + return true; + } + catch (Exception) + { + result = string.Empty; + return false; + } + } + + public static bool TrySplitBinary(string bits, out bool[] result) + { + try + { + result = Array.ConvertAll(bits.ToCharArray(), x => x == '1'); + //result = bits.Select(x => x == '1').ToArray(); // Using LINQ + return true; + } + catch (Exception) + { + result = Array.Empty(); + return false; + } + } + + public static bool TrySplitBinary(int bits, out bool[] result) + { + return TrySplitBinary(bits.ToString(), out result); + } + + public static bool TryConvertSplittedBinaryToBytes(bool[] bits, out byte[] result) + { + try + { + if (bits.Length % 8 != 0) + { + result = Array.Empty(); + return false; + } + + // No need to worry about endianness + byte[] bytes = new byte[bits.Length / 8]; + for (int i = 0; i < bits.Length; i += 8) + { + int value = 0; + for (int j = 0; j < 8; j++) + { + if (bits[i + j]) value += 1 << (7 - j); + } + bytes[i / 8] = (byte)value; + } + + // OR / No need to worry about endianness + //int nBytes = bits.Length / 8; + //var bytesAsBools = Enumerable.Range(0, nBytes).Select(i => bits.Skip(8 * i).Take(8)); + //byte[] bytes = bytesAsBools.Select(b => Convert.ToByte(string.Join("", b.Select(x => x ? "1" : "0")), 2)).ToArray(); + + // OR (Don't Use This One) + //byte[] bytes = new byte[bits.Length / 8]; // 1 Byte = 8 Bit + //BitArray bitArray = new(bits); + //bitArray.CopyTo(bytes, 0); + + result = bytes; + return true; + } + catch (Exception ex) + { + Debug.WriteLine("TryConvertSplittedBinaryToBytes: " + ex.Message); + result = Array.Empty(); + return false; + } + } + + public static async Task StreamToBytes(Stream stream) + { + if (!stream.CanRead) return Array.Empty(); using MemoryStream ms = new(); - int read; - while ((read = input.Read(buffer, 0, buffer.Length)) > 0) + try + { + await stream.CopyToAsync(ms); + } + catch (Exception ex) { - ms.Write(buffer, 0, read); + Debug.WriteLine("ByteArrayTool StreamToBytes: " + ex.Message); } return ms.ToArray(); } + public static int Search(byte[] src, byte[] pattern) + { + int maxFirstCharSlot = src.Length - pattern.Length + 1; + for (int i = 0; i < maxFirstCharSlot; i++) + { + if (src[i] != pattern[0]) // compare only first byte + continue; + + // found a match on first byte, now try to match rest of the pattern + for (int j = pattern.Length - 1; j >= 1; j--) + { + if (src[i + j] != pattern[j]) break; + if (j == 1) return i; + } + } + return -1; + } + } diff --git a/MsmhToolsClass/MsmhToolsClass/CertificateTool.cs b/MsmhToolsClass/MsmhToolsClass/CertificateTool.cs index 1b5fd59..0b43ecd 100644 --- a/MsmhToolsClass/MsmhToolsClass/CertificateTool.cs +++ b/MsmhToolsClass/MsmhToolsClass/CertificateTool.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Net; +using System.Net.Sockets; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; @@ -127,39 +128,52 @@ private static SubjectAlternativeNameBuilder GetSanBuilderForGateway(IPAddress g { // Create SubjectAlternativeNameBuilder SubjectAlternativeNameBuilder sanBuilder = new(); - sanBuilder.AddDnsName("localhost"); // Add Localhost - sanBuilder.AddDnsName(Environment.UserName); // Add Current User - if (OperatingSystem.IsWindows()) - sanBuilder.AddUserPrincipalName(System.Security.Principal.WindowsIdentity.GetCurrent().Name); // Add User Principal Name - sanBuilder.AddIpAddress(IPAddress.Loopback); - sanBuilder.AddIpAddress(IPAddress.IPv6Loopback); - sanBuilder.AddIpAddress(IPAddress.Any); - sanBuilder.AddIpAddress(IPAddress.IPv6Any); - - // Generate IP range for gateway - if (NetworkTool.IsIPv4(gateway)) + + try { - string ipString = gateway.ToString(); - string[] ipSplit = ipString.Split('.'); - string ip1 = ipSplit[0] + "." + ipSplit[1] + "." + ipSplit[2] + "."; - for (int n = 0; n <= 255; n++) - { - string ip2 = ip1 + n.ToString(); - sanBuilder.AddIpAddress(IPAddress.Parse(ip2)); - sanBuilder.AddUri(new Uri($"https://{ip2}")); - } - // Generate local IP range in case a VPN is active. - if (!ip1.Equals("192.168.1.")) + sanBuilder.AddDnsName("localhost"); // Add Localhost + sanBuilder.AddDnsName(Environment.UserName); // Add Current User + if (OperatingSystem.IsWindows()) + sanBuilder.AddUserPrincipalName(System.Security.Principal.WindowsIdentity.GetCurrent().Name); // Add User Principal Name + sanBuilder.AddIpAddress(IPAddress.Loopback); + sanBuilder.AddIpAddress(IPAddress.IPv6Loopback); + sanBuilder.AddIpAddress(IPAddress.Any); + sanBuilder.AddIpAddress(IPAddress.IPv6Any); + + // Add All Machine IPv4 And IPv6 Configuration To SAN Extension + foreach (var ipAddress in Dns.GetHostAddresses(Dns.GetHostName())) + if (ipAddress.AddressFamily == AddressFamily.InterNetwork || ipAddress.AddressFamily == AddressFamily.InterNetworkV6) + sanBuilder.AddIpAddress(ipAddress); + + // Generate IP range for gateway + if (NetworkTool.IsIPv4(gateway)) { - string ipLocal1 = "192.168.1."; + string ipString = gateway.ToString(); + string[] ipSplit = ipString.Split('.'); + string ip1 = ipSplit[0] + "." + ipSplit[1] + "." + ipSplit[2] + "."; for (int n = 0; n <= 255; n++) { - string ipLocal2 = ipLocal1 + n.ToString(); - sanBuilder.AddIpAddress(IPAddress.Parse(ipLocal2)); - sanBuilder.AddUri(new Uri($"https://{ipLocal2}")); + string ip2 = ip1 + n.ToString(); + sanBuilder.AddIpAddress(IPAddress.Parse(ip2)); + sanBuilder.AddUri(new Uri($"https://{ip2}")); + } + // Generate local IP range in case a VPN is active. + if (!ip1.Equals("192.168.1.")) + { + string ipLocal1 = "192.168.1."; + for (int n = 0; n <= 255; n++) + { + string ipLocal2 = ipLocal1 + n.ToString(); + sanBuilder.AddIpAddress(IPAddress.Parse(ipLocal2)); + sanBuilder.AddUri(new Uri($"https://{ipLocal2}")); + } } } } + catch (Exception ex) + { + Debug.WriteLine("CertificateTool GetSanBuilderForGateway: " + ex.Message); + } return sanBuilder; } @@ -297,10 +311,15 @@ public static async Task GenerateCertificateAsync(string folderPath, IPAddress g SubjectAlternativeNameBuilder sanBuilder = GetSanBuilderForGateway(gateway); // Create Issuer Certificate - using X509Certificate2 issuerCert = GenerateRootCertificate(sanBuilder, issuerSubjectName, out RSA issuerRsaKey); + X509Certificate2 issuerCert = GenerateRootCertificate(sanBuilder, issuerSubjectName, out RSA issuerRsaKey); + //if (!issuerCert.HasPrivateKey) issuerCert = issuerCert.CopyWithPrivateKey(issuerRsaKey); + //string pass = Guid.NewGuid().ToString(); + //issuerCert = new(issuerCert.Export(X509ContentType.Pfx, pass), pass); // Create Certificate - using X509Certificate2 cert = GenerateCertificateByIssuer(issuerCert, sanBuilder, subjectName, out RSA rsaKey); + X509Certificate2 cert = GenerateCertificateByIssuer(issuerCert, sanBuilder, subjectName, out RSA rsaKey); + //if (!cert.HasPrivateKey) cert = cert.CopyWithPrivateKey(rsaKey); + //cert = new(cert.Export(X509ContentType.Pfx, pass), pass); //== Export to Files // Export Issuer Private Key @@ -310,6 +329,7 @@ public static async Task GenerateCertificateAsync(string folderPath, IPAddress g // Export Issuer Certificate await issuerCert.SaveToFileAsCrt(issuerFilePathNoExt); + issuerCert.Dispose(); // Export Private Key string filePathNoExt = Path.GetFullPath(Path.Combine(folderPath, "localhost")); @@ -318,16 +338,24 @@ public static async Task GenerateCertificateAsync(string folderPath, IPAddress g // Export Certificate await cert.SaveToFileAsCrt(filePathNoExt); + cert.Dispose(); } public static async void CreateP12(string certPath, string keyPath, string password = "") { - string? folderPath = Path.GetDirectoryName(certPath); - string fileName = Path.GetFileNameWithoutExtension(certPath); - using X509Certificate2 certWithKey = X509Certificate2.CreateFromPemFile(certPath, keyPath); - byte[] certWithKeyExport = certWithKey.Export(X509ContentType.Pfx, password); - if (!string.IsNullOrEmpty(folderPath)) - await File.WriteAllBytesAsync(Path.Combine(folderPath, fileName + ".p12"), certWithKeyExport); + try + { + string? folderPath = Path.GetDirectoryName(certPath); + string fileName = Path.GetFileNameWithoutExtension(certPath); + using X509Certificate2 certWithKey = X509Certificate2.CreateFromPemFile(certPath, keyPath); + byte[] certWithKeyExport = certWithKey.Export(X509ContentType.Pfx, password); + if (!string.IsNullOrEmpty(folderPath)) + await File.WriteAllBytesAsync(Path.Combine(folderPath, fileName + ".p12"), certWithKeyExport); + } + catch (Exception ex) + { + Debug.WriteLine("CreateP12: " + ex.Message); + } } /// diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsReader.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsReader.cs deleted file mode 100644 index 4f2c1d1..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsReader.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System.Net; - -namespace MsmhToolsClass.DnsTool; - -public class DnsReader -{ - public string? IP { get; private set; } - public int Port { get; private set; } - public string Dns { get; private set; } - public string Host { get; private set; } = string.Empty; - public string Path { get; private set; } = string.Empty; - public string CompanyName { get; private set; } = string.Empty; - private string CompanyNameDataFileContent { get; set; } = string.Empty; - public DnsProtocol Protocol { get; private set; } - public string ProtocolName { get; private set; } - public bool IsDnsCryptStamp { get; private set; } = false; - public DnsCryptStamp StampProperties = new(); - - public class DnsCryptStamp - { - public bool IsDnsSec { get; set; } = false; - public bool IsNoLog { get; set; } = false; - public bool IsNoFilter { get; set; } = false; - } - - /// - /// Read any DNS - /// - /// DNS Address - /// File content to get company name. each line e.g. 8.8.8.8|Google Inc. - public DnsReader(string dns, string? companyNameDataFileContent) - { - Dns = dns; - - if (!string.IsNullOrEmpty(companyNameDataFileContent)) - CompanyNameDataFileContent = companyNameDataFileContent; - - Protocol = DnsProtocol.Unknown; - ProtocolName = DnsProtocolName.Unknown; - - if (dns.ToLower().StartsWith("sdns://")) - { - IsDnsCryptStamp = true; - - // Decode Stamp - DNSCryptStampReader stamp = new(dns); - if (stamp != null && stamp.IsDecryptionSuccess) - { - IP = stamp.IP; - Port = stamp.Port; - Host = stamp.Host; - Path = stamp.Path; - Protocol = ParseProtocol(stamp.Protocol); - ProtocolName = stamp.ProtocolName; - StampProperties.IsDnsSec = stamp.IsDnsSec; - StampProperties.IsNoLog = stamp.IsNoLog; - StampProperties.IsNoFilter = stamp.IsNoFilter; - - // Get Company Name (SDNS) - string stampHost = stamp.Host; - if (string.IsNullOrEmpty(stampHost)) stampHost = stamp.IP; - if (string.IsNullOrEmpty(stampHost)) stampHost = stamp.ProviderName; - if (!string.IsNullOrEmpty(CompanyNameDataFileContent)) - CompanyName = GetCompanyName(stampHost, CompanyNameDataFileContent); - } - } - else - { - if (dns.ToLower().StartsWith("https://")) - { - // DoH - SetIpPortHostPath(dns, 443); - - Protocol = DnsProtocol.DoH; - ProtocolName = DnsProtocolName.DoH; - } - else if (dns.ToLower().StartsWith("tls://")) - { - // TLS - SetIpPortHostPath(dns, 853); - - Protocol = DnsProtocol.DoT; - ProtocolName = DnsProtocolName.DoT; - } - else if (dns.ToLower().StartsWith("quic://")) - { - // DoQ - SetIpPortHostPath(dns, 853); - - Protocol = DnsProtocol.DoQ; - ProtocolName = DnsProtocolName.DoQ; - } - else if (dns.ToLower().StartsWith("tcp://") || dns.ToLower().StartsWith("udp://")) - { - // Plain DNS - SetIpPortHostPath(dns, 53); - - Protocol = DnsProtocol.PlainDNS; - ProtocolName = DnsProtocolName.PlainDNS; - } - else - { - // Plain DNS - SetIpPortHost(dns, 53); - - Protocol = DnsProtocol.PlainDNS; - ProtocolName = DnsProtocolName.PlainDNS; - } - } - } - - private void SetIpPortHostPath(string dns, int defaultPort) - { - NetworkTool.GetUrlDetails(dns, defaultPort, out _, out string host, out _, out _, out int port, out string path, out bool isIPv6); - Port = port; - Path = path; - bool isIPv4 = NetworkTool.IsIPv4Valid(host, out IPAddress? _); - if (isIPv6 || isIPv4) - { - IP = host; - } - else - { - Host = host; - IP = GetIP.GetIpFromSystem(host, false, false); - } - if (!string.IsNullOrEmpty(CompanyNameDataFileContent)) - { - string? ipOrHost = Host; - if (string.IsNullOrEmpty(ipOrHost)) ipOrHost = IP; - if (string.IsNullOrEmpty(ipOrHost)) ipOrHost = host; - CompanyName = GetCompanyName(ipOrHost, CompanyNameDataFileContent); - } - } - - private void SetIpPortHost(string hostIpPort, int defaultPort) - { - NetworkTool.GetHostDetails(hostIpPort, defaultPort, out string host, out _, out _, out int port, out string _, out bool isIPv6); - Port = port; - bool isIPv4 = NetworkTool.IsIPv4Valid(host, out IPAddress? _); - if (isIPv6 || isIPv4) - { - IP = host; - } - else - { - Host = host; - IP = GetIP.GetIpFromSystem(host, false, false); - } - if (!string.IsNullOrEmpty(CompanyNameDataFileContent)) - { - string? ipOrHost = Host; - if (string.IsNullOrEmpty(ipOrHost)) ipOrHost = IP; - if (string.IsNullOrEmpty(ipOrHost)) ipOrHost = host; - CompanyName = GetCompanyName(ipOrHost, CompanyNameDataFileContent); - } - } - - private static string GetCompanyName(string host, string fileContent) - { - return DnsTool.GetCompanyName.HostToCompanyOffline(host, fileContent); - } - - private static DnsProtocol ParseProtocol(DNSCryptStampReader.StampProtocol stampProtocol) - { - var protocol = stampProtocol switch - { - DNSCryptStampReader.StampProtocol.PlainDNS => DnsProtocol.PlainDNS, - DNSCryptStampReader.StampProtocol.DnsCrypt => DnsProtocol.DnsCrypt, - DNSCryptStampReader.StampProtocol.DoH => DnsProtocol.DoH, - DNSCryptStampReader.StampProtocol.DoT => DnsProtocol.DoT, - DNSCryptStampReader.StampProtocol.DoQ => DnsProtocol.DoQ, - DNSCryptStampReader.StampProtocol.ObliviousDohTarget => DnsProtocol.ObliviousDohTarget, - DNSCryptStampReader.StampProtocol.AnonymizedDNSCryptRelay => DnsProtocol.AnonymizedDNSCryptRelay, - DNSCryptStampReader.StampProtocol.ObliviousDohRelay => DnsProtocol.ObliviousDohRelay, - DNSCryptStampReader.StampProtocol.Unknown => DnsProtocol.Unknown, - _ => DnsProtocol.Unknown, - }; - return protocol; - } - - public enum DnsProtocol - { - PlainDNS, - DnsCrypt, - DoH, - DoT, - DoQ, - ObliviousDohTarget, - AnonymizedDNSCryptRelay, - ObliviousDohRelay, - Unknown - } - - private struct DnsProtocolName - { - public static string PlainDNS = "Plain DNS"; - public static string DnsCrypt = "DNSCrypt"; - public static string DoH = "DNS-Over-HTTPS"; - public static string DoT = "DNS-Over-TLS"; - public static string DoQ = "DNS-Over-Quic"; - public static string ObliviousDohTarget = "Oblivious DoH Target"; - public static string AnonymizedDNSCryptRelay = "Anonymized DNSCrypt Relay"; - public static string ObliviousDohRelay = "Oblivious DoH Relay"; - public static string Unknown = "Unknown"; - } - -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsByteExtensions.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsByteExtensions.cs deleted file mode 100644 index a4d758b..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsByteExtensions.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System; -using System.Text; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -internal static class DnsByteExtensions -{ - public static short ReadInt16(byte a, byte b) - { - return (short)(a << 0 | b << 8); - } - - public static ushort ReadUInt16(byte a, byte b) - { - return (ushort)(a << 0 | b << 8); - } - - public static short ReadInt16(ReadOnlyMemory bytes, ref int offset) - { - var value = ReadInt16(bytes.Span[offset + 1], bytes.Span[offset + 0]); - offset += sizeof(short); - return value; - } - - public static ushort ReadUInt16(ReadOnlyMemory bytes, ref int offset) - { - var value = ReadUInt16(bytes.Span[offset + 1], bytes.Span[offset + 0]); - offset += sizeof(ushort); - return value; - } - - public static int ReadInt32(ReadOnlyMemory bytes, ref int offset) - { - var value = bytes.Span[offset + 3] << 0 | - bytes.Span[offset + 2] << 8 | - bytes.Span[offset + 1] << 16 | - bytes.Span[offset + 0] << 24; - offset += sizeof(int); - return value; - } - - public static uint ReadUInt32(ReadOnlyMemory bytes, ref int offset) - { - var value = bytes.Span[offset + 3] << 0 | - bytes.Span[offset + 2] << 8 | - bytes.Span[offset + 1] << 16 | - bytes.Span[offset + 0] << 24; - offset += sizeof(uint); - return (uint)value; - } - - public static string ToDebugString(ReadOnlyMemory bytes) - { - if (bytes.IsEmpty) - { - return ""; - } - - return $"new byte [] {{{string.Join(",", bytes.ToArray())}}}"; - } - - public static ReadOnlyMemory ReadBytes(ReadOnlyMemory bytes, int length, ref int offset) - { - var data = bytes.Slice(offset, length); - offset += length; - return data; - } - - public static string ReadStringSimple(ReadOnlyMemory bytes, ref int offset) - { - byte labelLength = bytes.Span[offset]; - offset += 1; -#if NETSTANDARD2_0 - var str = Encoding.ASCII.GetString(bytes.Slice(offset, labelLength).ToArray()); -#else - var str = Encoding.ASCII.GetString(bytes.Slice(offset, labelLength).Span); -#endif - offset += labelLength; - return str; - } - - public static string[] ReadString(ReadOnlyMemory bytes, ref int offset, bool compressionPermitted = true) - { - // Assume most labels consist of 3 parts - var parts = new List(3); - - int? preCompressionOffset = null; - while (offset < bytes.Length) - { - if (bytes.Span[offset] == 0) - { - offset++; - break; - } - - // Pointers are always 192 or more, because the first two bits are 1s - if (bytes.Span[offset] >= 192 && compressionPermitted) - { - if (!preCompressionOffset.HasValue) - { - preCompressionOffset = offset + 2; - } - - // Read the 14 bit pointer as our new offset - offset = ReadUInt16(bytes.Span[offset + 1], (byte)(bytes.Span[offset] & (1 << 6) - 1)); - } - - parts.Add(ReadStringSimple(bytes, ref offset)); - } - - if (preCompressionOffset.HasValue) - { - offset = preCompressionOffset.Value; - } - - return parts.ToArray(); - } - - public static ReadOnlyMemory AllocateAndWrite(IDnsByteArrayWriter writer) - { - var buffer = AllocatePinnedNetworkBuffer(); - var offset = 0; - writer.WriteBytes(buffer, ref offset); -#if NETSTANDARD2_0 - return buffer.AsMemory().Slice(0, offset); -#else - return buffer.Slice(0, offset); -#endif - } - -#if NETSTANDARD2_0 - public static ArraySegment Slice(this ArraySegment buffer, int start, int length) - { - return new ArraySegment(buffer.Array, start, length); - } - - public static ArraySegment Slice(this ArraySegment buffer, int start) - { - return Slice(buffer, start, buffer.Count - start); - } -#endif - - private const int NetworkBufferSize = 65527; - -#if NETSTANDARD2_0 || NETSTANDARD2_1 - public static ArraySegment AllocatePinnedNetworkBuffer() => new ArraySegment(new byte[NetworkBufferSize]); -#else - // Allocate a buffer which will be used for the incoming query, and re-used to send the answer. - // Also make it pinned, see https://enclave.io/high-performance-udp-sockets-net6/ - public static Memory AllocatePinnedNetworkBuffer() => GC.AllocateArray(NetworkBufferSize, true); -#endif - - public static TReader FromBytes(ReadOnlyMemory bytes) where TReader : IDnsByteArrayReader, new() - { - var offset = 0; - - var reader = new TReader(); - reader.ReadBytes(bytes, ref offset); - - if (offset != bytes.Length) - { - throw new InvalidOperationException($"Should have read {bytes.Length} bytes, only read {offset} bytes"); - } - return reader; - } - - public static void ToBytes(ReadOnlySpan strings, Memory buffer, ref int offset) - { - for (int i = 0; i < strings.Length; i++) - { - // First write the value 1 byte from the offset to leave room for the length byte -#if NETSTANDARD2_0 - var stringBytes = Encoding.ASCII.GetBytes(strings[i]); - stringBytes.CopyTo(buffer.Slice(offset + 1, strings[i].Length)); - var length = stringBytes.Length; -#else - var length = Encoding.ASCII.GetBytes(strings[i], buffer.Slice(offset + 1, strings[i].Length).Span); -#endif - - // Then write the length before the value - buffer.Span[offset] = (byte)length; - - // Finally advance the offset past the length and value - offset += 1 + length; - } - - buffer.Span[offset++] = 0; - } - - public static void ToBytes(int value, Memory buffer, ref int offset) - { - buffer.Span[offset++] = (byte)(value >> 24); - buffer.Span[offset++] = (byte)(value >> 16); - buffer.Span[offset++] = (byte)(value >> 8); - buffer.Span[offset++] = (byte)(value >> 0); - } - - public static void ToBytes(uint value, Memory buffer, ref int offset) - { - buffer.Span[offset++] = (byte)(value >> 24); - buffer.Span[offset++] = (byte)(value >> 16); - buffer.Span[offset++] = (byte)(value >> 8); - buffer.Span[offset++] = (byte)(value >> 0); - } - - public static void ToBytes(short value, Memory buffer, ref int offset) - { - buffer.Span[offset++] = (byte)(value >> 8); - buffer.Span[offset++] = (byte)(value >> 0); - } - - public static void ToBytes(ushort value, Memory buffer, ref int offset) - { - buffer.Span[offset++] = (byte)(value >> 8); - buffer.Span[offset++] = (byte)(value >> 0); - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsDomainResource.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsDomainResource.cs deleted file mode 100644 index 6075a9a..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsDomainResource.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Represents a DNS text resource containing a domain name. -/// -public sealed class DnsDomainResource : DnsStringResource -{ - /// - protected override bool CanUseCompression => true; - - /// - /// Get the value of this entry as a domain name. - /// - public string Domain => string.Join(".", Entries); - - /// - public override string ToString() => Domain; -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsHeader.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsHeader.cs deleted file mode 100644 index 8807563..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsHeader.cs +++ /dev/null @@ -1,230 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Represents a DNS header, the first entry in a . -/// See for methods to create DNS headers for specific purposes. -/// -public sealed class DnsHeader : IEquatable, IDnsByteArrayReader, IDnsByteArrayWriter -{ - /// - /// Gets or sets the unique ID of this DNS query. - /// - /// - /// A unique ID which allows the sender/receiver to - /// identify this DNS message (for example if the transport protocol is stateless). - /// - public ushort Id { get; set; } - - internal ushort Flags { get; set; } - - /// - /// The number of questions in this header. - /// - /// - /// A count of the number of questions asked of the DNS server. - /// - public short QuestionCount { get; set; } - - /// - /// The number of records in this header. - /// - /// - /// A count of the number of answers contained within this DNS message. - /// - public short AnswerRecordCount { get; set; } - - /// - /// The number of name server records in this header. - /// - /// - /// A count of the number of DNS name server records in this header. - /// - public short NameServerRecordCount { get; set; } - - /// - /// The number of additional records in this header. - /// - /// - /// A count of the number of additional records in this header. - /// - public short AdditionalRecordCount { get; set; } - - internal string[] Labels { get; set; } = Array.Empty(); - - /// - /// The of this header. - /// - /// - /// If the type of query is , it means the query is for IP address records. - /// - public DnsQueryType QueryType { get; set; } - - /// - /// The of this header, normally (Internet). - /// - /// - /// The class of query, most likely to represent Internet DNS queries. - /// - public DnsQueryClass QueryClass { get; set; } - - /// - /// Determines whether this is a DNS query or answer. - /// - /// - /// For DNS queries this is false, for DNS responses this is true. - /// - public bool IsQueryResponse - { - get => (Flags & 0x8000) == 0x8000; - set => Flags = value ? (ushort)(Flags | 0x8000) : (ushort)(Flags & (~0x8000)); - } - - /// - /// Determines the operation code for this DNS header. - /// - /// - /// The DNS operation code for this DNS answer. For example . - /// - public DnsOperationCode OperationCode - { - get => (DnsOperationCode)((Flags & 0x7800) >> 11); - set => Flags = (ushort)((Flags & ~0x7800) | ((int)value << 11)); - } - - /// - /// Determines whether the answer was from an authoritative DNS server. - /// - /// - /// If true, this DNS answer is authoritative. - /// - public bool AuthoritativeAnswer - { - get => (Flags & 0x0400) == 0x0400; - set => Flags = value ? (ushort)(Flags | 0x0400) : (ushort)(Flags & (~0x0400)); - } - - /// - /// Determines whether this DNS answer was truncated due to size. - /// - public bool Truncation - { - get => (Flags & 0x0200) == 0x0200; - set => Flags = value ? (ushort)(Flags | 0x0200) : (ushort)(Flags & (~0x0200)); - } - - /// - /// Indicates whether recursion is desired on this DNS query. - /// If this is an answer, indicates whether the original query requested recursion. - /// - public bool RecursionDesired - { - get => (Flags & 0x0100) == 0x0100; - set => Flags = value ? (ushort)(Flags | 0x0100) : (ushort)(Flags & (~0x0100)); - } - - /// - /// Indicates whether recursion is available. - /// - public bool RecursionAvailable - { - get => (Flags & 0x0080) == 0x0080; - set => Flags = value ? (ushort)(Flags | 0x0080) : (ushort)(Flags & (~0x0080)); - } - - /// - /// The for this result. - /// - public DnsResponseCode ResponseCode - { - get => (DnsResponseCode)(Flags & 0x000F); - set => Flags = (ushort)((Flags & ~0x000F) | (byte)value); - } - - /// - /// The DNS host to use, for example "example.com". - /// - public string Host - { - get => string.Join(".", Labels); - set => Labels = value.Split('.'); - } - - /// - public override string ToString() => $"{(IsQueryResponse ? "RES" : "QRY")}: {Id} Domain: {Host} Type: {QueryType} Class: {QueryClass}"; - - /// - public bool Equals(DnsHeader? other) - { - return Id == other?.Id && - Flags == other.Flags && - QuestionCount == other.QuestionCount && - AnswerRecordCount == other.AnswerRecordCount && - NameServerRecordCount == other.NameServerRecordCount && - AdditionalRecordCount == other.AdditionalRecordCount && - Host == other.Host && - QueryType == other.QueryType && - QueryClass == other.QueryClass; - } - - /// - public override int GetHashCode() - { - HashCode hash = new(); - hash.Add(Id); - hash.Add(Flags); - hash.Add(QuestionCount); - hash.Add(AnswerRecordCount); - hash.Add(NameServerRecordCount); - hash.Add(AdditionalRecordCount); - hash.Add(QueryType); - hash.Add(QueryClass); - hash.Add(Host); - return hash.ToHashCode(); - } - - /// - public void ReadBytes(ReadOnlyMemory bytes, ref int offset) - { - Id = DnsByteExtensions.ReadUInt16(bytes, ref offset); - Flags = DnsByteExtensions.ReadUInt16(bytes, ref offset); - QuestionCount = DnsByteExtensions.ReadInt16(bytes, ref offset); - AnswerRecordCount = DnsByteExtensions.ReadInt16(bytes, ref offset); - NameServerRecordCount = DnsByteExtensions.ReadInt16(bytes, ref offset); - AdditionalRecordCount = DnsByteExtensions.ReadInt16(bytes, ref offset); - Labels = DnsByteExtensions.ReadString(bytes, ref offset); - QueryType = (DnsQueryType)DnsByteExtensions.ReadUInt16(bytes, ref offset); - QueryClass = (DnsQueryClass)DnsByteExtensions.ReadUInt16(bytes, ref offset); - - if (!IsQueryResponse && (AnswerRecordCount > 0 || NameServerRecordCount > 0)) - { - throw new InvalidOperationException($"Header states that this message is a query, yet there are answer and nameserver records."); - } - } - - /// - public void WriteBytes(Memory bytes, ref int offset) - { - DnsByteExtensions.ToBytes(Id, bytes, ref offset); - DnsByteExtensions.ToBytes(Flags, bytes, ref offset); - DnsByteExtensions.ToBytes(QuestionCount, bytes, ref offset); - DnsByteExtensions.ToBytes(AnswerRecordCount, bytes, ref offset); - DnsByteExtensions.ToBytes(NameServerRecordCount, bytes, ref offset); - DnsByteExtensions.ToBytes(AdditionalRecordCount, bytes, ref offset); - DnsByteExtensions.ToBytes(Labels, bytes, ref offset); - DnsByteExtensions.ToBytes((ushort)QueryType, bytes, ref offset); - DnsByteExtensions.ToBytes((ushort)QueryClass, bytes, ref offset); - } - - /// - /// A generic bag of tags associated with this object. - /// Will not be serialised and/or passed over the wire. - /// - public IDictionary Tags { get; } = new Dictionary(); - - public override bool Equals(object? obj) - { - return Equals(obj as DnsHeader); - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsIpAddressResource.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsIpAddressResource.cs deleted file mode 100644 index 8677374..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsIpAddressResource.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Net; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Represents a DNS resource record which contains an Internet Protocol address. -/// See and . -/// -public sealed class DnsIpAddressResource : IDnsResource, IEquatable -{ - /// - /// The Internet Protocol address. - /// - /// - /// May be an IPv4 or IPv6 address, depending on the record type. - /// - public IPAddress IPAddress { get; set; } = IPAddress.Loopback; - - /// - public bool Equals(DnsIpAddressResource? other) => IPAddress.Equals(other?.IPAddress); - - /// - public override bool Equals(object? obj) => obj is DnsIpAddressResource record ? Equals(record) : base.Equals(obj); - - /// - public override int GetHashCode() => HashCode.Combine(IPAddress); - - /// - public void ReadBytes(ReadOnlyMemory bytes, ref int offset, int length) - { - var raw = DnsByteExtensions.ReadBytes(bytes, length, ref offset); - IPAddress = new IPAddress(raw.ToArray()); - } - - /// - public IEnumerable> WriteBytes() - { - yield return IPAddress.GetAddressBytes(); - } - - /// - public override string ToString() => IPAddress.ToString(); - - /// - public void WriteBytes(Memory bytes, ref int offset) - { - Span address = IPAddress.GetAddressBytes(); - - address.CopyTo(bytes[offset..].Span); - offset += address.Length; - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsMessage.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsMessage.cs deleted file mode 100644 index 1dc82ca..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsMessage.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Text; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Represents an answer to a DNS query, generated by a DNS server. -/// -public sealed class DnsMessage : IEquatable, IDnsByteArrayReader, IDnsByteArrayWriter -{ - /// - /// The section of this answer. - /// - /// Gets or sets the , which describes the original DNS query. - public DnsHeader Header { get; set; } = new DnsHeader(); - - /// - /// The list of DNS resources returned by the server. - /// - /// Gets or sets the list representing values returned by the DNS server. - public IList Answers { get; set; } = Array.Empty(); - - /// - /// The list of name server DNS resources returned by the server. - /// - /// Gets or sets the list representing values returned by the DNS server. - public IList Nameservers { get; set; } = Array.Empty(); - - /// - /// The list of additional DNS resources returned by the server. - /// - /// Gets or sets the list representing values returned by the DNS server. - public IList Additional { get; set; } = Array.Empty(); - - /// - public bool Equals(DnsMessage? other) => Header.Equals(other?.Header) && - Answers.SequenceEqual(other.Answers) && - Nameservers.SequenceEqual(other.Nameservers) && - Additional.SequenceEqual(other.Additional); - - /// - public override bool Equals(object? obj) => obj is DnsMessage record ? Equals(record) : base.Equals(obj); - - /// - public override int GetHashCode() => HashCode.Combine(Header, Answers, Nameservers, Additional); - - private IList ReadRecords(int count, ReadOnlyMemory bytes, ref int offset) - { - var records = new DnsResourceRecord[count]; - for (var i = 0; i < count; i++) - { - var record = new DnsResourceRecord(); - record.ReadBytes(bytes, ref offset); - records[i] = record; - } - - return records; - } - - private void EnsureCorrectCounts() - { - if (Header.AnswerRecordCount != Answers.Count) - { - throw new InvalidOperationException($"Header states there are {Header.AnswerRecordCount} answer records, there are {Answers.Count}"); - } - - if (Header.AdditionalRecordCount != Additional.Count) - { - throw new InvalidOperationException($"Header states there are {Header.AdditionalRecordCount} additional records, there are {Additional.Count}"); - } - - if (Header.NameServerRecordCount != Nameservers.Count) - { - throw new InvalidOperationException($"Header states there are {Header.NameServerRecordCount} nameserver records, there are {Nameservers.Count}"); - } - } - - /// - public void ReadBytes(ReadOnlyMemory bytes, ref int offset) - { - EnsureCorrectCounts(); - Header.ReadBytes(bytes, ref offset); - Answers = ReadRecords(Header.AnswerRecordCount, bytes, ref offset); - Nameservers = ReadRecords(Header.NameServerRecordCount, bytes, ref offset); - Additional = ReadRecords(Header.AdditionalRecordCount, bytes, ref offset); - } - - /// - public override string ToString() - { - var sb = new StringBuilder(); - sb.Append($"{Header} Response: {Header.ResponseCode}"); - - if (Answers.Count > 0) - { - sb.Append($"{Environment.NewLine}Answers: {Answers.Count}{string.Concat(Answers.Select(x => $"{Environment.NewLine} * {x}"))}"); - } - - if (Additional.Count > 0) - { - sb.Append($"{Environment.NewLine}Additional: {Additional.Count}{string.Concat(Additional.Select(x => $"{Environment.NewLine} * {x}"))}"); - } - - return sb.ToString(); - } - - /// - public void WriteBytes(Memory bytes, ref int offset) - { - EnsureCorrectCounts(); - Header.WriteBytes(bytes, ref offset); - - foreach (var answer in Answers) - { - answer.WriteBytes(bytes, ref offset); - } - - foreach (var nameserver in Nameservers) - { - nameserver.WriteBytes(bytes, ref offset); - } - - foreach (var additional in Additional) - { - additional.WriteBytes(bytes, ref offset); - } - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsMxResource.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsMxResource.cs deleted file mode 100644 index 63da6f6..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsMxResource.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Represents a mail server DNS record. See . -/// -public sealed class DnsMxResource : DnsStringResource, IEquatable -{ - /// - /// The priority or preference field identifies which mailserver should be preferred. - /// - /// - /// The priority field identifies which mailserver should be preferred - if multiple - /// values are the same, mail would be expected to flow evenly to all hosts. - /// - public ushort Preference { get; set; } - - /// - /// The domain name of a mailserver. - /// - /// - /// The host name must map directly to one or more address records (A, or AAAA) in the DNS, and must not point to any CNAME records. - /// - public string Exchange => string.Join(".", Entries); - - /// - protected override bool CanUseCompression => true; - - /// - public bool Equals(DnsMxResource? other) => Preference == other?.Preference && Exchange == other.Exchange; - - /// - public override bool Equals(object? obj) => obj is DnsMxResource record ? Equals(record) : base.Equals(obj); - - /// - public override int GetHashCode() => HashCode.Combine(Preference, Exchange); - - /// - public override void ReadBytes(ReadOnlyMemory bytes, ref int offset, int length) - { - Preference = DnsByteExtensions.ReadUInt16(bytes, ref offset); - base.ReadBytes(bytes, ref offset, length - sizeof(ushort)); - } - - /// - public override string ToString() => Exchange; - - /// - public override void WriteBytes(Memory bytes, ref int offset) - { - DnsByteExtensions.ToBytes(Preference, bytes, ref offset); - base.WriteBytes(bytes, ref offset); - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsOperationCode.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsOperationCode.cs deleted file mode 100644 index be23e08..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsOperationCode.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// DNS operation codes. See https://www.iana.org/assignments/dns-parameters/dns-parameters.xml#dns-parameters-5 -/// -public enum DnsOperationCode : byte -{ - /// - /// Query - /// - QUERY = 0, - /// - /// IQuery (Inverse Query, OBSOLETE) - /// - IQUERY = 1, - /// - /// Status - /// - STATUS = 2, - /// - /// Notify - /// - NOTIFY = 4, - /// - /// Update - /// - UPDATE = 5, - /// - /// DNS Stateful Operations (DSO) - /// - DSO = 6, -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsOptResource.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsOptResource.cs deleted file mode 100644 index 7cc1d53..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsOptResource.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Describes an EDNS0 psuedo-resource. -/// -public sealed class DnsOptResource : IDnsResource, IEquatable -{ - /// - /// The raw bytes recieved for this DNS resource. - /// - /// - /// The raw set of bytes representing the value fo this DNS resource. - /// For string values, due to compression the whole packet may be needed. - /// - public ReadOnlyMemory Raw { get; set; } - - /// - public bool Equals(DnsOptResource? other) - { - return other != null && Raw.Span.SequenceEqual(other.Raw.Span); - } - - /// - public override bool Equals(object? obj) => obj is DnsOptResource record ? Equals(record) : base.Equals(obj); - - /// - public override int GetHashCode() => HashCode.Combine(Raw); - - /// - public void ReadBytes(ReadOnlyMemory bytes, ref int offset, int length) - { - Raw = DnsByteExtensions.ReadBytes(bytes, length, ref offset); - } - - /// - public void WriteBytes(Memory bytes, ref int offset) - { - // Payloadsize / flags is serialised at a higher level - Raw.CopyTo(bytes[offset..]); - offset += Raw.Length; - } - - /// - public override string ToString() => $"Raw: {Raw.Length} bytes"; -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsQueryClass.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsQueryClass.cs deleted file mode 100644 index 07c70f7..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsQueryClass.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// DNS class. See https://www.iana.org/assignments/dns-parameters/dns-parameters.xml#dns-parameters-2 -/// -public enum DnsQueryClass : ushort -{ - /// - /// Internet (IN) - /// - IN = 1, - /// - /// Chaos (CH) - /// - CH = 3, - /// - /// Hesiod (HS) - /// - HS = 4, - /// - /// QCLASS NONE - /// - QCLASS_NONE = 254, - /// - /// QCLASS * (ANY) - /// - QCLASS_ANY = 255, - /// - /// Standards Action - /// - StandardsAction = 65535 -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsQueryFactory.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsQueryFactory.cs deleted file mode 100644 index d508831..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsQueryFactory.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Provides extension methods around . -/// -public static class DnsQueryFactory -{ - /// - /// Generate a unique ID to identify this DNS message. - /// - /// A random value. - public static ushort GenerateId() - { - var offset = 0; - return DnsByteExtensions.ReadUInt16(Guid.NewGuid().ToByteArray(), ref offset); - } - - /// - /// Create a DNS query using the specified host name and DNS query type. - /// - /// The DNS host to request in the query. - /// The type of DNS query to request. - /// The complete DNS query. - public static DnsMessage CreateQuery(string host, DnsQueryType type = DnsQueryType.A) - { - return new DnsMessage - { - Header = new DnsHeader - { - Id = GenerateId(), - Host = host, - QueryType = type, - QueryClass = DnsQueryClass.IN, - OperationCode = DnsOperationCode.QUERY, - QuestionCount = 1, - RecursionDesired = true - } - }; - } - - public static DnsHeader Clone(DnsHeader header) - { - return new DnsHeader - { - Id = header.Id, - Host = header.Host, - QueryType = header.QueryType, - QueryClass = header.QueryClass, - OperationCode = header.OperationCode, - QuestionCount = header.QuestionCount, - RecursionDesired = header.RecursionDesired, - AdditionalRecordCount = header.AdditionalRecordCount, - AnswerRecordCount = header.AnswerRecordCount, - AuthoritativeAnswer = header.AuthoritativeAnswer, - IsQueryResponse = header.IsQueryResponse, - NameServerRecordCount = header.NameServerRecordCount, - RecursionAvailable = header.RecursionAvailable, - ResponseCode = header.ResponseCode, - Truncation = header.Truncation - }; - } - - public static DnsMessage TruncateAnswer(DnsMessage query) - { - var header = Clone(query.Header); - - header.AnswerRecordCount = 0; - header.NameServerRecordCount = 0; - header.AdditionalRecordCount = 0; - header.IsQueryResponse = true; - header.OperationCode = DnsOperationCode.QUERY; - header.Truncation = true; - - return new DnsMessage { Header = header }; - } - - /// - /// Create a reverse DNS query which resolves an IP address to a host name. - /// - /// The IPv4 or IPv6 address to resolve. - /// The correctly formatted DNS query. - public static DnsMessage CreateReverseQuery(IPAddress ipAddress) - { - return CreateQuery(GetReverseLookupHostForIpAddress(ipAddress), DnsQueryType.PTR); - } - - private static string GetReverseLookupHostForIpAddress(IPAddress ipAddress) - { - if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) - { - return string.Join(".", string.Concat(ipAddress.GetAddressBytes().Select(x => x.ToString("x2"))).Reverse()) + ".ip6.arpa"; - } - else if (ipAddress.AddressFamily == AddressFamily.InterNetwork) - { - return string.Join(".", ipAddress.ToString().Split('.').Reverse()) + ".in-addr.arpa"; - } - else - { - throw new InvalidOperationException($"Invalid address type: {ipAddress.AddressFamily}"); - } - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsQueryType.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsQueryType.cs deleted file mode 100644 index 9943641..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsQueryType.cs +++ /dev/null @@ -1,291 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// DNS resource record type. See https://www.iana.org/assignments/dns-parameters/dns-parameters.xml#dns-parameters-4 -/// -public enum DnsQueryType : ushort -{ - /// - /// A host address - /// - A = 1, - /// - /// An authoritative name server - /// - NS = 2, - /// - /// A mail destination (OBSOLETE - use MX) - /// - MD = 3, - /// - /// A mail forwarder (OBSOLETE - use MX) - /// - MF = 4, - /// - /// The canonical name for an alias - /// - CNAME = 5, - /// - /// Marks the start of a zone of authority - /// - SOA = 6, - /// - /// A mailbox domain name (EXPERIMENTAL) - /// - MB = 7, - /// - /// A mail group member (EXPERIMENTAL) - /// - MG = 8, - /// - /// A mail rename domain name (EXPERIMENTAL) - /// - MR = 9, - /// - /// A null RR (EXPERIMENTAL) - /// - NULL = 10, - /// - /// A well known service description - /// - WKS = 11, - /// - /// A domain name pointer - /// - PTR = 12, - /// - /// Host information - /// - HINFO = 13, - /// - /// Mailbox or mail list information - /// - MINFO = 14, - /// - /// Mail exchange - /// - MX = 15, - /// - /// Text strings - /// - TEXT = 16, - /// - /// For Responsible Person - /// - RP = 17, - /// - /// For AFS Data Base location - /// - AFSDB = 18, - /// - /// For X.25 PSDN address - /// - X25 = 19, - /// - /// For ISDN address - /// - ISDN = 20, - /// - /// For Route Through - /// - RT = 21, - /// - /// For NSAP address, NSAP style A record - /// - NSAP = 22, - /// - /// For domain name pointer, NSAP style - /// - NSAPPTR = 23, - /// - /// For security signature - /// - SIG = 24, - /// - /// For security key - /// - KEY = 25, - /// - /// X.400 mail mapping information - /// - PX = 26, - /// - /// Geographical Position - /// - GPOS = 27, - /// - /// IP6 Address - /// - AAAA = 28, - /// - /// Location Information - /// - LOC = 29, - /// - /// Next Domain (OBSOLETE) - /// - NXT = 30, - /// - /// Endpoint Identifier - /// - EID = 31, - /// - /// Nimrod Locator - /// - NIMLOC = 32, - /// - /// Server Selection - /// - SRV = 33, - /// - /// ATM Address - /// - ATMA = 34, - /// - /// Naming Authority Pointer - /// - NAPTR = 35, - /// - /// Key Exchanger - /// - KX = 36, - CERT = 37, - /// - /// A6 (OBSOLETE - use AAAA) - /// - A6 = 38, - DNAME = 39, - SINK = 40, - OPT = 41, - APL = 42, - /// - /// Delegation Signer - /// - DS = 43, - /// - /// SSH Key Fingerprint - /// - SSHFP = 44, - IPSECKEY = 45, - RRSIG = 46, - NSEC = 47, - DNSKEY = 48, - DHCID = 49, - NSEC3 = 50, - NSEC3PARAM = 51, - TLSA = 52, - /// - /// S/MIME cert association - /// - SMIMEA = 53, - /// - /// Host Identity Protocol - /// - HIP = 55, - NINFO = 56, - RKEY = 57, - /// - /// Trust Anchor LINK - /// - TALINK = 58, - /// - /// Child DS - /// - CDS = 59, - /// - /// DNSKEY(s) the Child wants reflected in DS - /// - CDNSKEY = 60, - /// - /// OpenPGP Key - /// - OPENPGPKEY = 61, - /// - /// Child-To-Parent Synchronization - /// - CSYNC = 62, - /// - /// Message digest for DNS zone - /// - ZONEMD = 63, - /// - /// Service Binding - /// - SVCB = 64, - /// - /// HTTPS Binding - /// - HTTPS = 65, - SPF = 99, - UINFO = 100, - UID = 101, - GID = 102, - UNSPEC = 103, - NID = 104, - L32 = 105, - L64 = 106, - LP = 107, - /// - /// An EUI-48 address - /// - EUI48 = 108, - /// - /// An EUI-64 address - /// - EUI64 = 109, - /// - /// Transaction Key - /// - TKEY = 249, - /// - /// Transaction Signature - /// - TSIG = 250, - /// - /// Incremental transfer - /// - IXFR = 251, - /// - /// Transfer of an entire zone - /// - AXFR = 252, - /// - /// Mailbox-related RRs (MB, MG or MR) - /// - MAILB = 253, - /// - /// Mail agent RRs (OBSOLETE - see MX) - /// - MAILA = 254, - /// - /// A request for some or all records the server has available - /// - ANY = 255, - URI = 256, - /// - /// Certification Authority Restriction - /// - CAA = 257, - /// - /// Application Visibility and Control - /// - AVC = 258, - /// - /// Digital Object Architecture - /// - DOA = 259, - /// - /// Automatic Multicast Tunneling Relay - /// - AMTRELAY = 260, - /// - /// DNSSEC Trust Authorities - /// - TA = 32768, - /// - /// DNSSEC Lookaside Validation (OBSOLETE) - /// - DLV = 32769 -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsResourceRecord.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsResourceRecord.cs deleted file mode 100644 index 4c46519..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsResourceRecord.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Represents metadata around a DNS resource record returned by a DNS server. -/// -public sealed class DnsResourceRecord : IEquatable, IDnsByteArrayReader, IDnsByteArrayWriter -{ - /// - /// The type of DNS query. - /// - public DnsQueryType Type { get; set; } - /// - /// The class of DNS query. - /// - public DnsQueryClass Class { get; set; } - /// - /// The time to live entry for this record, in seconds. - /// - public uint TimeToLive { get; set; } - - /// - /// The host name associated with this record. - /// - public string Host - { - get => string.Join(".", Name); - set => Name = value.Split('.'); - } - - private string[] Name { get; set; } = Array.Empty(); - - /// - /// The value of this DNS record, which should be - /// cast to the appropriate resource record type - /// class depending on the . - /// - public IDnsResource? Resource { get; set; } - - private IDnsResource CreateResourceRecord(DnsQueryType recordType) - { - return recordType switch - { - DnsQueryType.A => new DnsIpAddressResource(), - DnsQueryType.AAAA => new DnsIpAddressResource(), - DnsQueryType.TEXT => new DnsTextResource(), - DnsQueryType.CNAME => new DnsDomainResource(), - DnsQueryType.NS => new DnsDomainResource(), - DnsQueryType.PTR => new DnsDomainResource(), - DnsQueryType.SPF => new DnsTextResource(), - DnsQueryType.SOA => new DnsSoaResource(), - DnsQueryType.MX => new DnsMxResource(), - DnsQueryType.OPT => new DnsOptResource(), - _ => new DnsUnknownResource(), - }; - } - - /// - public override string ToString() => $"Name: {Host} Type: {Type} Class: {Class} TTL: {TimeToLive} Resource: {Resource}"; - - /// - public bool Equals(DnsResourceRecord? other) => Host == other?.Host && - Type == other.Type && - Class == other.Class && - TimeToLive == other.TimeToLive && - Resource != null && - Resource.Equals(other.Resource); - - /// - public override bool Equals(object? obj) => obj is DnsResourceRecord record ? Equals(record) : base.Equals(obj); - - /// - public override int GetHashCode() => HashCode.Combine(Name, Type, Class, TimeToLive, Host, Resource); - - /// - public void ReadBytes(ReadOnlyMemory bytes, ref int offset) - { - Name = DnsByteExtensions.ReadString(bytes, ref offset); - Type = (DnsQueryType)DnsByteExtensions.ReadUInt16(bytes, ref offset); - Class = (DnsQueryClass)DnsByteExtensions.ReadUInt16(bytes, ref offset); - TimeToLive = DnsByteExtensions.ReadUInt32(bytes, ref offset); - Resource = CreateResourceRecord(Type); - var dataLength = DnsByteExtensions.ReadUInt16(bytes, ref offset); - FromBytesKnownLength(Resource, bytes, ref offset, dataLength); - } - - private static void FromBytesKnownLength(IDnsResource resource, ReadOnlyMemory bytes, ref int offset, int length) - { - var expectedOffset = offset + length; - resource.ReadBytes(bytes, ref offset, length); - if (offset != expectedOffset) - { - throw new InvalidOperationException($"{resource.GetType().Name}.{nameof(IDnsResource.ReadBytes)} did not read to offset {expectedOffset} (read to {offset})"); - } - } - - /// - public void WriteBytes(Memory bytes, ref int offset) - { - DnsByteExtensions.ToBytes(Name, bytes, ref offset); - DnsByteExtensions.ToBytes((ushort)Type, bytes, ref offset); - DnsByteExtensions.ToBytes((ushort)Class, bytes, ref offset); - DnsByteExtensions.ToBytes(TimeToLive, bytes, ref offset); - - // First, write the resource, but save two bytes for the size (and do not advance the offset) - var resourceSize = 0; - Resource?.WriteBytes(bytes[(offset + sizeof(ushort))..], ref resourceSize); - - // Write the size of the resource in the two bytes preceeding (current offset) - DnsByteExtensions.ToBytes((ushort)resourceSize, bytes, ref offset); - - // Advance the offset with the size of the resource - offset += resourceSize; - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsResponseCode.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsResponseCode.cs deleted file mode 100644 index 3522bb8..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsResponseCode.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// DNS response codes. See https://www.iana.org/assignments/dns-parameters/dns-parameters.xml#dns-parameters-6 -/// -public enum DnsResponseCode : byte -{ - /// - /// No Error - /// - NoError = 0, - /// - /// Format Error - /// - FormErr = 1, - /// - /// Server Failure - /// - ServFail = 2, - /// - /// Non-Existent Domain - /// - NXDomain = 3, - /// - /// Not Implemented - /// - NotImp = 4, - /// - /// Query Refused - /// - Refused = 5, - /// - /// Name Exists when it should not - /// - YXDomain = 6, - /// - /// RR Set Exists when it should not - /// - YXRRSet = 7, - /// - /// RR Set that should exist does not - /// - NXRRSet = 8, - /// - /// Server Not Authoritative for zone or Not Authorized - /// - NotAuth = 9, - /// - /// Name not contained in zone - /// - NotZone = 10, - /// - /// DSO-TYPE Not Implemented - /// - DSOTYPENI = 11, - /// - /// BADVERS or TSIG Signature Failure - /// - BADVERS = 16, - /// - /// Key not recognized - /// - BADKEY = 17, - /// - /// Signature out of time window - /// - BADTIME = 18, - /// - /// Bad TKEY Mode - /// - BADMODE = 19, - /// - /// Duplicate key name - /// - BADNAME = 20, - /// - /// Algorithm not supported - /// - BADALG = 21, - /// - /// Bad Truncation - /// - BADTRUNC = 22, - /// - /// Bad/missing Server Cookie - /// - BADCOOKIE = 23 -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsSoaResource.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsSoaResource.cs deleted file mode 100644 index cef22d4..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsSoaResource.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// A Start of Authority record (abbreviated as SOA record) is a type of -/// resource record in the Domain Name System (DNS) containing administrative -/// information about the zone, especially regarding zone transfers. -/// The SOA record format is specified in RFC 1035. -/// -public sealed class DnsSoaResource : IDnsResource, IEquatable -{ - /// - /// Primary master name server for this zone - /// - public string MName { get; set; } = string.Empty; - /// - /// Email address of the administrator responsible for this zone. - /// (As usual, the email address is encoded as a name. The part of the - /// email address before the @ becomes the first label of the name; the - /// domain name after the @ becomes the rest of the name. In zone-file - /// format, dots in labels are escaped with backslashes; thus the email - /// address john.doe@example.com would be represented in a zone file - /// as john\.doe.example.com.) - /// - public string RName { get; set; } = string.Empty; - /// - /// Serial number for this zone. If a secondary name server slaved to - /// this one observes an increase in this number, the slave will assume - /// that the zone has been updated and initiate a zone transfer. - /// - public uint Serial { get; set; } - /// - /// Number of seconds after which secondary name servers should query - /// the master for the SOA record, to detect zone changes. Recommendation - /// for small and stable zones:[4] 86400 seconds (24 hours). - /// - public TimeSpan Refresh { get; set; } - /// - /// Number of seconds after which secondary name servers should retry to - /// request the serial number from the master if the master does not respond. - /// It must be less than Refresh. Recommendation for small and stable - /// zones: 7200 seconds (2 hours). - /// - public TimeSpan Retry { get; set; } - /// - /// Number of seconds after which secondary name servers should stop answering - /// request for this zone if the master does not respond. This value must be - /// bigger than the sum of Refresh and Retry. Recommendation for small and - /// stable zones: 3600000 seconds (1000 hours). - /// - public TimeSpan Expire { get; set; } - /// - /// Time to live for purposes of negative caching. Recommendation for - /// small and stable zones: 3600 seconds (1 hour). Originally this - /// field had the meaning of a minimum TTL value for resource records - /// in this zone; it was changed to its current meaning by RFC 2308. - /// - public TimeSpan Minimum { get; set; } - - /// - public bool Equals(DnsSoaResource? other) => MName == other?.MName && RName == other.RName && Serial == other.Serial && Refresh == other.Refresh && Retry == other.Retry && Expire == other.Expire && Minimum == other.Minimum; - - /// - public override bool Equals(object? obj) => obj is DnsSoaResource record ? Equals(record) : base.Equals(obj); - - /// - public override int GetHashCode() => HashCode.Combine(MName, RName, Serial, Refresh, Retry, Expire, Minimum); - - /// - public void ReadBytes(ReadOnlyMemory bytes, ref int offset, int length) - { - MName = string.Join(".", DnsByteExtensions.ReadString(bytes, ref offset)); - RName = string.Join(".", DnsByteExtensions.ReadString(bytes, ref offset)); - Serial = DnsByteExtensions.ReadUInt32(bytes, ref offset); - Refresh = TimeSpan.FromSeconds(DnsByteExtensions.ReadInt32(bytes, ref offset)); - Retry = TimeSpan.FromSeconds(DnsByteExtensions.ReadInt32(bytes, ref offset)); - Expire = TimeSpan.FromSeconds(DnsByteExtensions.ReadInt32(bytes, ref offset)); - Minimum = TimeSpan.FromSeconds(DnsByteExtensions.ReadUInt32(bytes, ref offset)); - } - - /// - public override string ToString() => MName; - - /// - public void WriteBytes(Memory bytes, ref int offset) - { - DnsByteExtensions.ToBytes(MName.Split('.'), bytes, ref offset); - DnsByteExtensions.ToBytes(RName.Split('.'), bytes, ref offset); - DnsByteExtensions.ToBytes(Serial, bytes, ref offset); - DnsByteExtensions.ToBytes((int)Refresh.TotalSeconds, bytes, ref offset); - DnsByteExtensions.ToBytes((int)Retry.TotalSeconds, bytes, ref offset); - DnsByteExtensions.ToBytes((int)Expire.TotalSeconds, bytes, ref offset); - DnsByteExtensions.ToBytes((uint)Minimum.TotalSeconds, bytes, ref offset); - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsStringResource.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsStringResource.cs deleted file mode 100644 index 1f35e0d..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsStringResource.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Represents a DNS resource containing a string. -/// -public abstract class DnsStringResource : IDnsResource, IEquatable -{ - /// - /// The text entry contained within this resource. - /// - /// - /// The text values of this resource as an array of strings. - /// - public string[] Entries { get; set; } = Array.Empty(); - - /// - public bool Equals(DnsStringResource? other) => Entries.SequenceEqual(other?.Entries ?? Array.Empty()); - - /// - public override bool Equals(object? obj) => obj is DnsStringResource record ? Equals(record) : base.Equals(obj); - - /// - public override int GetHashCode() => HashCode.Combine(Entries); - - /// - /// Describes whether this string resource can use string compression. - /// - protected abstract bool CanUseCompression { get; } - - /// - public virtual void ReadBytes(ReadOnlyMemory bytes, ref int offset, int length) - { - // Provide the ReadString method with a sliced buffer, which ends when this resource ends - // It must start where the packet starts, since there are often pointers back to the beginning - Entries = DnsByteExtensions.ReadString(bytes[..(offset + length)], ref offset, CanUseCompression); - } - - /// - public virtual void WriteBytes(Memory bytes, ref int offset) - { - DnsByteExtensions.ToBytes(Entries, bytes, ref offset); - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsTextResource.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsTextResource.cs deleted file mode 100644 index 47e3b54..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsTextResource.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Represents a DNS text resource containing a string. -/// -public sealed class DnsTextResource : DnsStringResource -{ - /// - protected override bool CanUseCompression => false; - - /// - public override string ToString() => '"' + string.Join("\", \"", Entries) + '"'; -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsUnknownResource.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsUnknownResource.cs deleted file mode 100644 index eed7c56..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/DnsUnknownResource.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Represents a DNS resource that this library cannot yet understand. -/// -public sealed class DnsUnknownResource : IDnsResource, IEquatable -{ - /// - /// The raw bytes recieved for this DNS resource. - /// - /// - /// The raw set of bytes representing the value fo this DNS resource. - /// For string values, due to compression the whole packet may be needed. - /// - public ReadOnlyMemory Raw { get; set; } - - /// - public bool Equals(DnsUnknownResource? other) - { - return other != null && Raw.Span.SequenceEqual(other.Raw.Span); - } - - /// - public override bool Equals(object? obj) => obj is DnsUnknownResource record ? Equals(record) : base.Equals(obj); - - /// - public override int GetHashCode() => HashCode.Combine(Raw); - - /// - public void ReadBytes(ReadOnlyMemory bytes, ref int offset, int length) - { - Raw = DnsByteExtensions.ReadBytes(bytes, length, ref offset); - } - - /// - public override string ToString() => $"Raw {Raw.Length} bytes"; - - /// - public void WriteBytes(Memory bytes, ref int offset) - { - Raw.CopyTo(bytes[offset..]); - offset += Raw.Length; - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/IDnsByteArrayReader.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/IDnsByteArrayReader.cs deleted file mode 100644 index c41b714..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/IDnsByteArrayReader.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Represents a class which can read from the specified byte array. -/// -public interface IDnsByteArrayReader -{ - /// - /// Read from the specified byte array, starting at the specified offset. - /// - /// The byte array to read from. - /// The offset to start at. - void ReadBytes(ReadOnlyMemory bytes, ref int offset); -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/IDnsByteArrayWriter.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/IDnsByteArrayWriter.cs deleted file mode 100644 index 73414af..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/IDnsByteArrayWriter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Represents a class which can write its contents to an enumerable of bytes. -/// -public interface IDnsByteArrayWriter -{ - /// - /// Write to the the specified byte array. - /// - /// The byte array to read from. - /// The offset to start at. - void WriteBytes(Memory bytes, ref int offset); -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/IDnsResource.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/IDnsResource.cs deleted file mode 100644 index b0f6796..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsWireformatTools/IDnsResource.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace MsmhToolsClass.DnsTool.DnsWireformatTools; - -/// -/// Represents a type of DNS resource. -/// -public interface IDnsResource : IDnsByteArrayWriter -{ - /// - /// Read from the specified byte array, starting at the specified offset. - /// - /// The byte array to read from. - /// The offset to start at. - /// The maximum number of bytes to read. - void ReadBytes(ReadOnlyMemory bytes, ref int offset, int length); -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/GetIP.cs b/MsmhToolsClass/MsmhToolsClass/DnsTool/GetIP.cs deleted file mode 100644 index 7919986..0000000 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/GetIP.cs +++ /dev/null @@ -1,557 +0,0 @@ -using System.Diagnostics; -using System.Net; -using System.Net.Http.Headers; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Text.Json; -using MsmhToolsClass.DnsTool.DnsWireformatTools; - -namespace MsmhToolsClass.DnsTool; - -public static class GetIP -{ - //================================================= Get From System - - /// - /// Get First IP in Answer Section - /// - /// Host - /// Look for IPv6 - /// Returns string.Empty if fail - public static string GetIpFromSystem(string host, bool getIPv6 = false, bool alsoUsePing = true) - { - string result = string.Empty; - List ips = GetIpsFromSystem(host, getIPv6); - if (ips.Any()) result = ips[0]; - else - { - if (!alsoUsePing) return result; - - try - { - using Ping ping = new(); - PingReply reply = ping.Send(host, 3000); - string ipStr = reply.Address.ToString(); - bool isIpv6 = NetworkTool.IsIPv6(reply.Address); - - if (getIPv6 && isIpv6) result = ipStr; - if (!getIPv6 && !isIpv6) result = ipStr; - } - catch (Exception) - { - // do nothing - } - } - - return result; - } - - /// - /// Get a List of IPs - /// - /// Host - /// Look for IPv6 - /// Returns Empty List if fail - public static List GetIpsFromSystem(string host, bool getIPv6 = false) - { - List ips = new(); - - try - { - //IPAddress[] ipAddresses = System.Net.Dns.GetHostAddresses(host); - IPAddress[] ipAddresses = System.Net.Dns.GetHostEntry(host).AddressList; - - if (ipAddresses == null || ipAddresses.Length == 0) return ips; - - if (!getIPv6) - { - for (int n = 0; n < ipAddresses.Length; n++) - { - AddressFamily addressFamily = ipAddresses[n].AddressFamily; - string ip = ipAddresses[n].ToString(); - if (addressFamily != AddressFamily.InterNetworkV6) - if (!string.IsNullOrEmpty(ip) && !NetworkTool.IsLocalIP(ip)) - ips.Add(ip); - } - } - else - { - for (int n = 0; n < ipAddresses.Length; n++) - { - AddressFamily addressFamily = ipAddresses[n].AddressFamily; - string ip = ipAddresses[n].ToString(); - if (addressFamily == AddressFamily.InterNetworkV6) - if (!string.IsNullOrEmpty(ip) && !NetworkTool.IsLocalIP(ip)) - ips.Add(ip); - } - } - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); - } - return ips; - } - - //================================================= Get From DoH Using Wire Format - - /// - /// Get First IP in Answer Section (Using Wire Format) - /// - /// Host - /// DoH Server - /// Timeout (Sec) - /// Use Proxy to Get IP - /// Returns string.Empty if fail - public static async Task GetIpFromDohUsingWireFormat(string host, string doh, int timeoutSec, string? proxyScheme = null, string? proxyUsername = null, string? proxyPassword = null) - { - List ips = await GetIpsFromDohUsingWireFormat(host, doh, timeoutSec, proxyScheme, proxyUsername, proxyPassword); - if (ips.Any()) return ips[0]; - return string.Empty; - } - - /// - /// Get A List of IPs (Using Wire Format) - /// - /// Host - /// DoH Server - /// Timeout (Sec) - /// Use Proxy to Get IPs - /// Returns an Empty List if fail - public static async Task> GetIpsFromDohUsingWireFormat(string host, string doh, int timeoutSec, string? proxyScheme = null, string? proxyUsername = null, string? proxyPassword = null) - { - if (string.IsNullOrEmpty(proxyScheme)) - return await GetIpsFromDohUsingWireFormatNoProxy(host, doh, timeoutSec); - else - return await GetIpsFromDohUsingWireFormatProxy(host, doh, timeoutSec, proxyScheme, proxyUsername, proxyPassword); - } - - private static async Task> GetIpsFromDohUsingWireFormatNoProxy(string host, string doh, int timeoutSec) - { - List ips = new(); - - try - { - DnsMessage dnsMessage = DnsQueryFactory.CreateQuery(host); - var queryBuffer = DnsByteExtensions.AllocatePinnedNetworkBuffer(); - int queryBufferLength = 0; - dnsMessage.WriteBytes(queryBuffer, ref queryBufferLength); - - Uri uri = new(doh); - - using HttpClient httpClient = new(); - httpClient.Timeout = new TimeSpan(0, 0, timeoutSec); - - HttpContent content = new ReadOnlyMemoryContent(queryBuffer[..queryBufferLength]); - content.Headers.ContentType = new MediaTypeHeaderValue("application/dns-message"); - - HttpRequestMessage message = new(HttpMethod.Post, "/dns-query"); - message.RequestUri = uri; - message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/dns-message")); - message.Content = content; - - using HttpResponseMessage r = await httpClient.SendAsync(message); - - if (r.IsSuccessStatusCode) - { - byte[] buffer = await r.Content.ReadAsByteArrayAsync(); - - DnsMessage answer = DnsByteExtensions.FromBytes(buffer); - if (httpClient.BaseAddress != null) - answer.Header.Tags.Add("Resolver", httpClient.BaseAddress.ToString()); - - for (int n = 0; n < answer.Answers.Count; n++) - { - DnsResourceRecord drr = answer.Answers[n]; - string? ip = drr.Resource?.ToString(); - if (!string.IsNullOrEmpty(ip) && !NetworkTool.IsLocalIP(ip) && IPAddress.TryParse(ip, out IPAddress? _)) - ips.Add(ip); - } - } - } - catch (Exception) { } - - return ips; - } - - private static async Task> GetIpsFromDohUsingWireFormatProxy(string host, string doh, int timeoutSec, string? proxyScheme = null, string? proxyUsername = null, string? proxyPassword = null) - { - List ips = new(); - - try - { - DnsMessage dnsMessage = DnsQueryFactory.CreateQuery(host); - var queryBuffer = DnsByteExtensions.AllocatePinnedNetworkBuffer(); - int queryBufferLength = 0; - dnsMessage.WriteBytes(queryBuffer, ref queryBufferLength); - - Uri uri = new(doh); - - using SocketsHttpHandler socketsHttpHandler = new(); - socketsHttpHandler.Proxy = new WebProxy(proxyScheme, true, null, new NetworkCredential(proxyUsername, proxyPassword)); - - using HttpClient httpClient = new(socketsHttpHandler); - httpClient.Timeout = new TimeSpan(0, 0, timeoutSec); - - HttpContent content = new ReadOnlyMemoryContent(queryBuffer[..queryBufferLength]); - content.Headers.ContentType = new MediaTypeHeaderValue("application/dns-message"); - - HttpRequestMessage message = new(HttpMethod.Post, "/dns-query"); - message.RequestUri = uri; - message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/dns-message")); - message.Content = content; - - using HttpResponseMessage r = await httpClient.SendAsync(message); - - if (r.IsSuccessStatusCode) - { - byte[] buffer = await r.Content.ReadAsByteArrayAsync(); - - DnsMessage answer = DnsByteExtensions.FromBytes(buffer); - if (httpClient.BaseAddress != null) - answer.Header.Tags.Add("Resolver", httpClient.BaseAddress.ToString()); - - for (int n = 0; n < answer.Answers.Count; n++) - { - DnsResourceRecord drr = answer.Answers[n]; - string? ip = drr.Resource?.ToString(); - if (!string.IsNullOrEmpty(ip) && !NetworkTool.IsLocalIP(ip) && IPAddress.TryParse(ip, out IPAddress? _)) - ips.Add(ip); - } - } - } - catch (Exception) { } - - return ips; - } - - //================================================= Get From DoH Using JSON Format - - /// - /// Get first IP Using JSON Format - /// - /// Host - /// DoH Address - /// Timeout (Sec) - /// Use Proxy to Get IP - /// Returns Empty List if fail - public static async Task GetIpFromDohUsingJsonFormat(string host, string doh, int timeoutSec, string? proxyScheme = null, string? proxyUsername = null, string? proxyPassword = null) - { - List ips = await GetIpsFromDohUsingJsonFormat(host, doh, timeoutSec, proxyScheme, proxyUsername, proxyPassword); - if (ips.Any()) return ips[0]; - return string.Empty; - } - - /// - /// Get IPs Using JSON Format - /// - /// Host - /// DoH Address - /// Timeout (Sec) - /// Use Proxy to Get IP - /// Returns Empty List if fail - public static async Task> GetIpsFromDohUsingJsonFormat(string host, string doh, int timeoutSec, string? proxyScheme = null, string? proxyUsername = null, string? proxyPassword = null) - { - if (string.IsNullOrEmpty(proxyScheme)) - return await GetIpFromDohUsingJsonFormatNoProxy(host, doh, timeoutSec); - else - return await GetIpFromDohUsingJsonFormatProxy(host, doh, timeoutSec, proxyScheme, proxyUsername, proxyPassword); - } - - private static async Task> GetIpFromDohUsingJsonFormatNoProxy(string host, string doh, int timeoutSec) - { - List ips = new(); - - try - { - doh = doh.Trim(); - if (doh.EndsWith('/')) doh = doh.TrimEnd('/'); - - host = Uri.EscapeDataString(host); - string path = $"?name={host}&type=A"; - Uri uri = new(doh + path); - - HttpClient httpClient = new(); - httpClient.Timeout = new TimeSpan(0, 0, timeoutSec); - - HttpRequestMessage message = new(); - message.Method = HttpMethod.Get; - message.RequestUri = uri; - message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/dns-json")); - - using HttpResponseMessage r = await httpClient.SendAsync(message); - - if (r.IsSuccessStatusCode) - { - string contents = await r.Content.ReadAsStringAsync(); - httpClient.Dispose(); - ips = GetIpFromJson(contents); - } - httpClient.Dispose(); - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); - } - - return ips; - } - - private static async Task> GetIpFromDohUsingJsonFormatProxy(string host, string doh, int timeoutSec, string? proxyScheme = null, string? proxyUsername = null, string? proxyPassword = null) - { - List ips = new(); - - try - { - doh = doh.Trim(); - if (doh.EndsWith('/')) doh = doh.TrimEnd('/'); - - host = Uri.EscapeDataString(host); - string path = $"?name={host}&type=A"; - Uri uri = new(doh + path); - - using SocketsHttpHandler socketsHttpHandler = new(); - socketsHttpHandler.Proxy = new WebProxy(proxyScheme, true, null, new NetworkCredential(proxyUsername, proxyPassword)); - - using HttpClient httpClient = new(socketsHttpHandler); - httpClient.Timeout = new TimeSpan(0, 0, timeoutSec); - - HttpRequestMessage message = new(); - message.Method = HttpMethod.Get; - message.RequestUri = uri; - message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/dns-json")); - - using HttpResponseMessage r = await httpClient.SendAsync(message); - - if (r.IsSuccessStatusCode) - { - string contents = await r.Content.ReadAsStringAsync(); - httpClient.Dispose(); - ips = GetIpFromJson(contents); - } - httpClient.Dispose(); - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); - } - - return ips; - } - - private static List GetIpFromJson(string jsonContent) - { - List ips = new(); - try - { - using JsonDocument jd = JsonDocument.Parse(jsonContent); - JsonElement rootElement = jd.RootElement; - if (rootElement.ValueKind == JsonValueKind.Object) - { - JsonProperty[] properties = rootElement.EnumerateObject().ToArray(); - for (int n = 0; n < properties.Length; n++) - { - JsonProperty property = properties[n]; - if (property.Name == "Answer") - { - JsonElement[] elementsProperties = property.Value.EnumerateArray().ToArray(); - for (int n2 = 0; n2 < elementsProperties.Length; n2++) - { - JsonElement elementProperty = elementsProperties[n2]; - if (elementProperty.ValueKind == JsonValueKind.Object) - { - JsonProperty[] answerProperties = elementProperty.EnumerateObject().ToArray(); - for (int n3 = 0; n3 < answerProperties.Length; n3++) - { - JsonProperty answerProperty = answerProperties[n3]; - if (answerProperty.Name == "data") - { - string ip = answerProperty.Value.ToString(); - if (!string.IsNullOrEmpty(ip) && !NetworkTool.IsLocalIP(ip) && IPAddress.TryParse(ip, out IPAddress? _)) - ips.Add(ip); - } - } - } - } - } - } - } - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); - } - return ips; - } - - //================================================= Get From Plain DNS - - public enum PlainDnsProtocol - { - UDP, TCP, Both - } - - /// - /// Get First IP in Answer Section - /// - /// Host - /// Plain DNS IP - /// Plain DNS Port - /// Timeout (ms) - /// Returns string.Empty if fail - public static async Task GetIpFromPlainDNS(string host, string dnsIP, int dnsPort, int timeoutSec, PlainDnsProtocol protocol = PlainDnsProtocol.Both) - { - List ips = await GetIpsFromPlainDNS(host, dnsIP, dnsPort, timeoutSec, protocol); - if (ips.Any()) return ips[0]; - return string.Empty; - } - - /// - /// Get a List of IPs - /// - /// Host - /// Plain DNS IP - /// Plain DNS Port - /// Timeout (ms) - /// Returns an Empty List if fail - public async static Task> GetIpsFromPlainDNS(string host, string dnsIP, int dnsPort, int timeoutSec, PlainDnsProtocol protocol = PlainDnsProtocol.Both) - { - List ips; - - if (protocol == PlainDnsProtocol.UDP) - ips = await GetIpsFromPlainDnsUdp(host, dnsIP, dnsPort, timeoutSec); - else if (protocol == PlainDnsProtocol.TCP) - ips = await GetIpsFromPlainDnsTcp(host, dnsIP, dnsPort, timeoutSec); - else - { - ips = await GetIpsFromPlainDnsUdp(host, dnsIP, dnsPort, timeoutSec); - if (!ips.Any()) - ips = await GetIpsFromPlainDnsTcp(host, dnsIP, dnsPort, timeoutSec); - } - - return ips; - } - - /// - /// Get a List of IPs - /// - /// Host - /// Plain DNS IP - /// Plain DNS Port - /// Timeout (ms) - /// Returns an Empty List if fail - private async static Task> GetIpsFromPlainDnsUdp(string host, string dnsIP, int dnsPort, int timeoutSec) - { - List ips = new(); - - Task task = Task.Run(async () => - { - try - { - DnsMessage dnsMessage = DnsQueryFactory.CreateQuery(host); - IPEndPoint ep = new(IPAddress.Parse(dnsIP), dnsPort); - - Memory queryBuffer = DnsByteExtensions.AllocatePinnedNetworkBuffer(); - int queryBufferLength = 0; - dnsMessage.WriteBytes(queryBuffer, ref queryBufferLength); - byte[] raw = queryBuffer[..queryBufferLength].ToArray(); - - using Socket socket = new(ep.AddressFamily, SocketType.Dgram, ProtocolType.Udp | ProtocolType.Tcp); - await socket.SendToAsync(raw, SocketFlags.None, ep); - byte[] buffer = new byte[65536]; - int receivedLength = await socket.ReceiveAsync(buffer, SocketFlags.None); - buffer = buffer.Take(receivedLength).ToArray(); - - socket.Close(); - - DnsMessage answer = DnsByteExtensions.FromBytes(buffer); - answer.Header.Id = dnsMessage.Header.Id; - answer.Header.Tags.Add("Resolver", $"udp://{ep}/"); - Debug.WriteLine("UDP Answers: " + answer.Answers.Count); - - for (int n = 0; n < answer.Answers.Count; n++) - { - DnsResourceRecord drr = answer.Answers[n]; - string? ip = drr.Resource?.ToString(); - if (!string.IsNullOrEmpty(ip) && !NetworkTool.IsLocalIP(ip) && IPAddress.TryParse(ip, out IPAddress? _)) - ips.Add(ip); - } - } - catch (Exception) - { - // do nothing - } - }); - try { await task.WaitAsync(TimeSpan.FromSeconds(timeoutSec)); } catch (Exception) { } - - return ips; - } - - /// - /// Get a List of IPs - /// - /// Host - /// Plain DNS IP - /// Plain DNS Port - /// Timeout (ms) - /// Returns an Empty List if fail - private async static Task> GetIpsFromPlainDnsTcp(string host, string dnsIP, int dnsPort, int timeoutSec) - { - List ips = new(); - - Task task = Task.Run(async () => - { - try - { - DnsMessage dnsMessage = DnsQueryFactory.CreateQuery(host); - IPEndPoint ep = new(IPAddress.Parse(dnsIP), dnsPort); - - using Socket socket = new(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - await socket.ConnectAsync(ep); - - Memory buffer = DnsByteExtensions.AllocatePinnedNetworkBuffer(); - int sendOffset = sizeof(ushort); - dnsMessage.WriteBytes(buffer, ref sendOffset); - - int fakeOffset = 0; - DnsByteExtensions.ToBytes((ushort)(sendOffset - sizeof(ushort)), buffer, ref fakeOffset); - - Memory sendBuffer = buffer[..sendOffset]; - await socket.SendAsync(sendBuffer, SocketFlags.None); - int received = await socket.ReceiveAsync(buffer, SocketFlags.None); - - int offset = 0; - ushort answerLength = DnsByteExtensions.ReadUInt16(buffer, ref offset); - - while (received < answerLength) - { - received += await socket.ReceiveAsync(buffer[received..], SocketFlags.None); - } - - socket.Close(); - - Memory answerBuffer = buffer.Slice(offset, answerLength); - - DnsMessage answer = DnsByteExtensions.FromBytes(answerBuffer); - answer.Header.Tags.Add("Resolver", $"tcp://{ep}/"); - Debug.WriteLine("TCP Answers: " + answer.Answers.Count); - - for (int n = 0; n < answer.Answers.Count; n++) - { - DnsResourceRecord drr = answer.Answers[n]; - string? ip = drr.Resource?.ToString(); - if (!string.IsNullOrEmpty(ip) && !NetworkTool.IsLocalIP(ip) && IPAddress.TryParse(ip, out IPAddress? _)) - ips.Add(ip); - } - } - catch (Exception) - { - // do nothing - } - }); - try { await task.WaitAsync(TimeSpan.FromSeconds(timeoutSec)); } catch (Exception) { } - - return ips; - } - -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DrawingTool.cs b/MsmhToolsClass/MsmhToolsClass/DrawingTool.cs index ff444af..93ced45 100644 --- a/MsmhToolsClass/MsmhToolsClass/DrawingTool.cs +++ b/MsmhToolsClass/MsmhToolsClass/DrawingTool.cs @@ -17,16 +17,15 @@ public static class DrawingTool public static Icon? ExtractIcon(string dllPath, int index, bool largeIcon) { Icon? icon = null; - if (!OperatingSystem.IsWindows()) return icon; + if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1)) return icon; _ = ExtractIconEx(dllPath, index, out IntPtr large, out IntPtr small, 1); + try { icon = Icon.FromHandle(largeIcon ? large : small); } - catch - { - // do nothing - } + catch { } + return icon; } //----------------------------------------------------------------------------------- @@ -35,7 +34,7 @@ public static class DrawingTool /// public static GraphicsPath? RoundedRectangle(Rectangle bounds, int radiusTopLeft, int radiusTopRight, int radiusBottomRight, int radiusBottomLeft) { - if (!OperatingSystem.IsWindows()) return null; + if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1)) return null; int diameterTopLeft = radiusTopLeft * 2; int diameterTopRight = radiusTopRight * 2; int diameterBottomRight = radiusBottomRight * 2; @@ -98,7 +97,7 @@ public static class DrawingTool /// public static Bitmap? Invert(Bitmap source) { - if (!OperatingSystem.IsWindows()) return null; + if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1)) return null; //create a blank bitmap the same size as original Bitmap newBitmap = new(source.Width, source.Height); //get a graphics object from the new image diff --git a/MsmhToolsClass/MsmhToolsClass/EncodingTool.cs b/MsmhToolsClass/MsmhToolsClass/EncodingTool.cs index 79075dc..5177792 100644 --- a/MsmhToolsClass/MsmhToolsClass/EncodingTool.cs +++ b/MsmhToolsClass/MsmhToolsClass/EncodingTool.cs @@ -92,12 +92,11 @@ public static string Base64Decode(string encodedString) } } - public static string UrlEncode(byte[] arg) + public static string Base64ToBase64Url(string base64) { try { - string s = Convert.ToBase64String(arg); - return s.Replace("=", "").Replace("/", "_").Replace("+", "-"); + return base64.Replace("=", "").Replace("/", "_").Replace("+", "-"); } catch (Exception) { @@ -105,11 +104,11 @@ public static string UrlEncode(byte[] arg) } } - public static string UrlToBase64(string arg) + public static string Base64UrlToBase64(string base64Url) { try { - return arg.PadRight(arg.Length + (4 - arg.Length % 4) % 4, '=').Replace("_", "/").Replace("-", "+"); + return base64Url.PadRight(base64Url.Length + (4 - base64Url.Length % 4) % 4, '=').Replace("_", "/").Replace("-", "+"); } catch (Exception) { @@ -117,36 +116,35 @@ public static string UrlToBase64(string arg) } } - public static byte[] UrlDecode(string arg) + public static string UrlEncode(byte[] buffer) { try { - string decrypted = UrlToBase64(arg); - return Convert.FromBase64String(decrypted); + string base64 = Convert.ToBase64String(buffer); + return Base64ToBase64Url(base64); } - catch (Exception ex) + catch (Exception) { - Debug.WriteLine("UrlDecode: " + ex.Message); - return Array.Empty(); + return string.Empty; } } - public static T[] SubArray(T[] arr, int start, int length) + public static byte[] UrlDecode(string base64Url) { - T[] result = new T[length]; + string base64 = Base64UrlToBase64(base64Url); + try { - Buffer.BlockCopy(arr, start, result, 0, length); + + return Convert.FromBase64String(base64); } catch (Exception ex) { - Debug.WriteLine("SubArray: " + ex.Message); + Debug.WriteLine("UrlDecode: " + ex.Message); + Debug.WriteLine("UrlDecode Base64Url: " + base64Url); + Debug.WriteLine("UrlDecode Base64: " + base64); + return Array.Empty(); } - return result; } - public static T[] SubArray(T[] arr, int start) - { - return SubArray(arr, start, arr.Length - start); - } } \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/ExtensionsMethods.cs b/MsmhToolsClass/MsmhToolsClass/ExtensionsMethods.cs index 4b6a60b..98cf9cf 100644 --- a/MsmhToolsClass/MsmhToolsClass/ExtensionsMethods.cs +++ b/MsmhToolsClass/MsmhToolsClass/ExtensionsMethods.cs @@ -13,6 +13,8 @@ using System.Diagnostics; using System.Drawing; using System.Net.Sockets; +using System.Collections.Specialized; +using System.Collections.Concurrent; namespace MsmhToolsClass; @@ -23,6 +25,63 @@ public static class Methods } public static class ExtensionsMethods { + //----------------------------------------------------------------------------------- + public static bool TryUpdate(this ConcurrentDictionary ccDic, K key, V newValue) where K : notnull + { + try + { + if (key == null) return false; + bool isKeyExist = ccDic.TryGetValue(key, out V? oldValue); + if (isKeyExist && oldValue != null) + return ccDic.TryUpdate(key, newValue, oldValue); + return false; + } + catch (Exception ex) + { + Debug.WriteLine("ExtensionsMethods TryUpdate: " + ex.Message); + return false; + } + } + //----------------------------------------------------------------------------------- + public static V? AddOrUpdate(this ConcurrentDictionary ccDic, K key, V newValue) where K : notnull + { + try + { + if (key == null) return default; + return ccDic.AddOrUpdate(key, newValue, (oldkey, oldvalue) => newValue); + } + catch (Exception ex) + { + Debug.WriteLine("ExtensionsMethods TryUpdate: " + ex.Message); + return default; + } + } + //----------------------------------------------------------------------------------- + /// + /// If Key Exist Adds The Value (Comma-Separated) + /// + public static void AddAndUpdate(this NameValueCollection nvc, string? key, string? value) + { + try + { + if (string.IsNullOrEmpty(key)) return; + if (string.IsNullOrEmpty(value)) return; + + string? theKey = nvc[key]; + if (!string.IsNullOrEmpty(theKey)) // Key Exist + { + string tempVal = theKey; + tempVal += "," + value; + nvc.Remove(key); + nvc.Add(key, tempVal); + } + else + { + nvc.Add(key, value); + } + } + catch (Exception) { } + } //----------------------------------------------------------------------------------- public static string GetInnerExceptions(this Exception ex) { @@ -84,12 +143,14 @@ public static async Task SaveAsync(this XDocument xDocument, string xmlFilePath) { try { - XmlWriterSettings xmlWriterSettings = new(); - xmlWriterSettings.WriteEndDocumentOnClose = true; - xmlWriterSettings.Async = true; - xmlWriterSettings.Indent = true; - xmlWriterSettings.OmitXmlDeclaration = true; - xmlWriterSettings.Encoding = new UTF8Encoding(false); + XmlWriterSettings xmlWriterSettings = new() + { + WriteEndDocumentOnClose = true, + Async = true, + Indent = true, + OmitXmlDeclaration = true, + Encoding = new UTF8Encoding(false) + }; using XmlWriter xmlWriter = XmlWriter.Create(xmlFilePath, xmlWriterSettings); await xDocument.SaveAsync(xmlWriter, CancellationToken.None); } @@ -104,10 +165,26 @@ public static TimeSpan Round(this TimeSpan timeSpan, int precision) return TimeSpan.FromSeconds(Math.Round(timeSpan.TotalSeconds, precision)); } //----------------------------------------------------------------------------------- - public static bool IsContain(this List list, T t) + public static string ToString(this List list, string separator) { + string result = string.Empty; for (int n = 0; n < list.Count; n++) - if (t != null && t.Equals(list[n])) return true; + { + T t = list[n]; + result += $"{t}{separator}"; + } + if (result.EndsWith(separator)) result = result.TrimEnd(separator); + return result; + } + //----------------------------------------------------------------------------------- + public static bool IsContain(this List list, T t) + { + try + { + for (int n = 0; n < list.Count; n++) + if (t != null && t.Equals(list[n])) return true; + } + catch (Exception) { } return false; } //----------------------------------------------------------------------------------- @@ -282,7 +359,7 @@ public static T IsNotNull([NotNull] this T? value, [CallerArgumentExpression( /// public static GraphicsPath? Shrink(this GraphicsPath path, float width) { - if (!OperatingSystem.IsWindows()) return null; + if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1)) return null; using GraphicsPath gp = new(); gp.AddPath(path, false); gp.CloseAllFigures(); @@ -314,7 +391,7 @@ public static T IsNotNull([NotNull] this T? value, [CallerArgumentExpression( private static int CountNextFigure(PathData data, int position) { int count = 0; - if (!OperatingSystem.IsWindows()) return count; + if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1)) return count; for (int i = position; i < data?.Types?.Length; i++) { count++; @@ -329,7 +406,7 @@ private static int CountNextFigure(PathData data, int position) /// public static void DrawRoundedRectangle(this Graphics graphics, Pen pen, Rectangle bounds, int radiusTopLeft, int radiusTopRight, int radiusBottomRight, int radiusBottomLeft) { - if (!OperatingSystem.IsWindows()) return; + if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1)) return; GraphicsPath? path = DrawingTool.RoundedRectangle(bounds, radiusTopLeft, radiusTopRight, radiusBottomRight, radiusBottomLeft); graphics.SmoothingMode = SmoothingMode.AntiAlias; if (path != null) @@ -342,7 +419,7 @@ public static void DrawRoundedRectangle(this Graphics graphics, Pen pen, Rectang /// public static void FillRoundedRectangle(this Graphics graphics, Brush brush, Rectangle bounds, int radiusTopLeft, int radiusTopRight, int radiusBottomRight, int radiusBottomLeft) { - if (!OperatingSystem.IsWindows()) return; + if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1)) return; GraphicsPath? path = DrawingTool.RoundedRectangle(bounds, radiusTopLeft, radiusTopRight, radiusBottomRight, radiusBottomLeft); graphics.SmoothingMode = SmoothingMode.AntiAlias; if (path != null) @@ -355,7 +432,7 @@ public static void FillRoundedRectangle(this Graphics graphics, Brush brush, Rec /// public static void DrawCircle(this Graphics g, Pen pen, float centerX, float centerY, float radius) { - if (!OperatingSystem.IsWindows()) return; + if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1)) return; g.DrawEllipse(pen, centerX - radius, centerY - radius, radius + radius, radius + radius); } //----------------------------------------------------------------------------------- @@ -364,7 +441,7 @@ public static void DrawCircle(this Graphics g, Pen pen, float centerX, float cen /// public static void FillCircle(this Graphics g, Brush brush, float centerX, float centerY, float radius) { - if (!OperatingSystem.IsWindows()) return; + if (!OperatingSystem.IsWindowsVersionAtLeast(6, 1)) return; g.FillEllipse(brush, centerX - radius, centerY - radius, radius + radius, radius + radius); } //----------------------------------------------------------------------------------- @@ -475,11 +552,13 @@ public static void SaveToFile(this List list, string filePath) { try { - FileStreamOptions streamOptions = new(); - streamOptions.Access = FileAccess.ReadWrite; - streamOptions.Share = FileShare.ReadWrite; - streamOptions.Mode = FileMode.Create; - streamOptions.Options = FileOptions.RandomAccess; + FileStreamOptions streamOptions = new() + { + Access = FileAccess.ReadWrite, + Share = FileShare.ReadWrite, + Mode = FileMode.Create, + Options = FileOptions.RandomAccess + }; using StreamWriter file = new(filePath, streamOptions); for (int n = 0; n < list.Count; n++) if (list[n] != null) @@ -544,7 +623,7 @@ public static int GetIndex(this List list, T value) { try { - return list.FindIndex(a => a.Equals(value)); + return list.FindIndex(a => a != null && a.Equals(value)); // If the item is not found, it will return -1 } catch (Exception) diff --git a/MsmhToolsClass/MsmhToolsClass/ExternLibs/LibSodium.cs b/MsmhToolsClass/MsmhToolsClass/ExternLibs/LibSodium.cs new file mode 100644 index 0000000..1d646a3 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/ExternLibs/LibSodium.cs @@ -0,0 +1,351 @@ +using System.Runtime.InteropServices; + +namespace MsmhToolsClass.ExternLibs; + +#pragma warning disable CA1401 // P/Invokes should not be visible +internal class LibSodium +{ + private const string DllName = "libsodium"; + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_core_hchacha20(byte[] Hash, byte[] Data, byte[] Key, byte[] Nonce); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_curve25519xchacha20poly1305_beforenm(byte[] SharedKey, byte[] PublicKey, byte[] SecretKey); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_curve25519xchacha20poly1305_easy_afternm(byte[] CipherText, byte[] Message, long MessageLength, byte[] Nonce, byte[] Key); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_curve25519xchacha20poly1305_easy(byte[] CipherText, byte[] Message, long MessageLength, byte[] Nonce, byte[] PublicKey, byte[] SecretKey); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_curve25519xchacha20poly1305_open_easy_afternm(byte[] PlainText, byte[] CipherText, long CipherTextLength, byte[] Nonce, byte[] Key); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_curve25519xchacha20poly1305_open_easy(byte[] PlainText, byte[] CipherText, long CipherTextLength, byte[] Nonce, byte[] PublicKey, byte[] SecretKey); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_easy_afternm(byte[] CipherText, byte[] Message, long MessageLength, byte[] Nonce, byte[] Key); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_easy(byte[] CipherText, byte[] Message, long MessageLength, byte[] Nonce, byte[] PublicKey, byte[] SecretKey); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_open_easy_afternm(byte[] PlainText, byte[] CipherText, long CipherTextLength, byte[] Nonce, byte[] Key); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_open_easy(byte[] PlainText, byte[] CipherText, long CipherTextLength, byte[] Nonce, byte[] PublicKey, byte[] SecretKey); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_beforenm(byte[] SharedKey, byte[] PublicKey, byte[] SecretKey); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void sodium_init(); + + //randombytes_buf + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void randombytes_buf(byte[] Buffer, int Size); + + //randombytes_uniform + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int randombytes_uniform(int UpperBound); + + //sodium_increment + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void sodium_increment(byte[] Buffer, long Length); + + //sodium_compare + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int sodium_compare(byte[] A, byte[] B, long Length); + + //sodium_version_string + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr sodium_version_string(); + + //crypto_hash + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_hash(byte[] Buffer, byte[] Message, long Length); + + //crypto_hash_sha512 + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_hash_sha512(byte[] Buffer, byte[] Message, long Length); + + //crypto_hash_sha256 + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_hash_sha256(byte[] Buffer, byte[] Message, long Length); + + //crypto_generichash + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_generichash(byte[] Buffer, int BufferLength, byte[] Message, long MessageLength, byte[] Key, int KeyLength); + + //crypto_generichash_blake2b_salt_personal + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_generichash_blake2b_salt_personal(byte[] Buffer, int BufferLength, byte[] Message, long MessageLength, byte[] Key, int KeyLength, byte[] Salt, byte[] Personal); + + //crypto_onetimeauth + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_onetimeauth(byte[] Buffer, byte[] Message, long MessageLength, byte[] Key); + + //crypto_onetimeauth_verify + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_onetimeauth_verify(byte[] Signature, byte[] Message, long MessageLength, byte[] Key); + + //crypto_pwhash_str + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_pwhash_str(byte[] Buffer, byte[] Password, long PasswordLen, long OpsLimit, int MemLimit); + + //crypto_pwhash_str_verify + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_pwhash_str_verify(byte[] Buffer, byte[] Password, long PassLength); + + //crypto_pwhash + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_pwhash(byte[] Buffer, long BufferLen, byte[] Password, long PasswordLen, byte[] Salt, long OpsLimit, int MemLimit, int Alg); + + //crypto_pwhash_scryptsalsa208sha256_str + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_pwhash_scryptsalsa208sha256_str(byte[] Buffer, byte[] Password, long PasswordLen, long OpsLimit, int MemLimit); + + //crypto_pwhash_scryptsalsa208sha256 + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_pwhash_scryptsalsa208sha256(byte[] Buffer, long BufferLen, byte[] Password, long PasswordLen, byte[] Salt, long OpsLimit, int MemLimit); + + //crypto_pwhash_scryptsalsa208sha256_str_verify + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_pwhash_scryptsalsa208sha256_str_verify(byte[] Buffer, byte[] Password, long PassLength); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_pwhash_str_needs_rehash(byte[] Buffer, long OpsLimit, int MemLimit); + + //crypto_sign_keypair + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_sign_keypair(byte[] PublicKey, byte[] SecretKey); + + //crypto_sign_seed_keypair + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_sign_seed_keypair(byte[] PublicKey, byte[] SecretKey, byte[] Seed); + + //crypto_sign + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_sign(byte[] Buffer, ref long BufferLength, byte[] Message, long MessageLength, byte[] Key); + + //crypto_sign_open + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_sign_open(byte[] Buffer, ref long BufferLength, byte[] SignedMessage, long SignedMessageLength, byte[] Key); + + //crypto_sign_detached + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_sign_detached(byte[] Signature, ref long SignatureLength, byte[] Message, long MessageLength, byte[] Key); + + //crypto_sign_verify_detached + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_sign_verify_detached(byte[] Signature, byte[] Message, long MessageLength, byte[] Key); + + //crypto_sign_ed25519_sk_to_seed + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_sign_ed25519_sk_to_seed(byte[] Seed, byte[] SecretKey); + + //crypto_sign_ed25519_sk_to_pk + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_sign_ed25519_sk_to_pk(byte[] PublicKey, byte[] SecretKey); + + //crypto_sign_ed25519_pk_to_curve25519 + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_sign_ed25519_pk_to_curve25519(byte[] Curve25519Pk, byte[] Ed25519Pk); + + //crypto_sign_ed25519_sk_to_curve25519 + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_sign_ed25519_sk_to_curve25519(byte[] Curve25519Sk, byte[] Ed25519Sk); + + //crypto_box_keypair + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_keypair(byte[] PublicKey, byte[] SecretKey); + + //crypto_box_seed_keypair + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_seed_keypair(byte[] PublicKey, byte[] SecretKey, byte[] Seed); + + //crypto_box_open_easy + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_detached(byte[] Cipher, byte[] Mac, byte[] Message, long MessageLength, byte[] Nonce, byte[] Pk, byte[] Sk); + + //crypto_box_open_detached + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_open_detached(byte[] Buffer, byte[] CipherText, byte[] Mac, long CipherTextLength, byte[] Nonce, byte[] Pk, byte[] Sk); + + //crypto_box_seedbytes + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_seedbytes(); + + //crypto_scalarmult_bytes + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_scalarmult_bytes(); + + //crypto_scalarmult_scalarbytes + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_scalarmult_scalarbytes(); + + //crypto_scalarmult_primitive + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern byte crypto_scalarmult_primitive(); + + //crypto_scalarmult_base + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_scalarmult_base(byte[] PublicKey, byte[] SecretKey); + + //crypto_scalarmult + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_scalarmult(byte[] SharedKey, byte[] SecretKey, byte[] PublicKey); + + //crypto_box_seal + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_seal(byte[] Buffer, byte[] Message, long MessageLength, byte[] Pk); + + //crypto_box_seal_open + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_box_seal_open(byte[] Buffer, byte[] CipherText, long CipherTextLength, byte[] Pk, byte[] Sk); + + //crypto_secretbox_easy + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_secretbox_easy(byte[] Buffer, byte[] Message, long MessageLength, byte[] Nonce, byte[] Key); + + //crypto_secretbox_open_easy + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_secretbox_open_easy(byte[] Buffer, byte[] CipherText, long CipherTextLength, byte[] Nonce, byte[] Key); + + //crypto_secretbox_detached + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_secretbox_detached(byte[] Cipher, byte[] Mac, byte[] Message, long MessageLength, byte[] Nonce, byte[] Key); + + //crypto_secretbox_open_detached + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_secretbox_open_detached(byte[] Buffer, byte[] CipherText, byte[] Mac, long CipherTextLength, byte[] Nonce, byte[] Key); + + //crypto_auth + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_auth(byte[] Buffer, byte[] Message, long MessageLength, byte[] Key); + + //crypto_auth_verify + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_auth_verify(byte[] Signature, byte[] Message, long MessageLength, byte[] Key); + + //crypto_auth_hmacsha256 + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_auth_hmacsha256(byte[] Buffer, byte[] Message, long MessageLength, byte[] Key); + + //crypto_auth_hmacsha256_verify + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_auth_hmacsha256_verify(byte[] Signature, byte[] Message, long MessageLength, byte[] Key); + + //crypto_auth_hmacsha512 + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_auth_hmacsha512(byte[] Signature, byte[] Message, long MessageLength, byte[] Key); + + //crypto_auth_hmacsha512_verify + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_auth_hmacsha512_verify(byte[] Signature, byte[] Message, long MessageLength, byte[] Key); + + //crypto_shorthash + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_shorthash(byte[] Buffer, byte[] Message, long MessageLength, byte[] Key); + + //crypto_stream_xor + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_stream_xor(byte[] Buffer, byte[] Message, long MessageLength, byte[] Nonce, byte[] Key); + + //crypto_stream_chacha20_xor + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_stream_chacha20_xor(byte[] Buffer, byte[] Message, long MessageLength, byte[] Nonce, byte[] Key); + + //sodium_bin2hex + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr sodium_bin2hex(byte[] Hex, int HexMaxlen, byte[] Bin, int BinLen); + + //sodium_hex2bin + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + internal static extern int sodium_hex2bin(IntPtr Bin, int BinMaxlen, string Hex, int HexLen, string Ignore, out int BinLen, string HexEnd); + + //sodium_bin2base64 + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr sodium_bin2base64(byte[] B64, int B64Maxlen, byte[] Bin, int BinLen, int Variant); + + //sodium_base642bin + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + internal static extern int sodium_base642bin(IntPtr Bin, int BinMaxlen, string B64, int B64Len, string Ignore, out int BinLen, out char B64End, int Variant); + + //sodium_base64_encoded_len + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int sodium_base64_encoded_len(int BinLen, int Variant); + + //crypto_aead_chacha20poly1305_ietf_encrypt + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_aead_chacha20poly1305_ietf_encrypt(IntPtr Cipher, out long CipherLength, byte[] Message, long MessageLength, byte[] AdditionalData, long AdditionalDataLength, byte[] NSEC, byte[] Nonce, byte[] Key); + + //crypto_aead_chacha20poly1305_ietf_decrypt + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_aead_chacha20poly1305_ietf_decrypt(IntPtr Message, out long MessageLength, byte[] NSEC, byte[] Cipher, long CipherLength, byte[] AdditionalData, long AdditionalDataLength, byte[] Nonce, byte[] Key); + + //crypto_aead_chacha20poly1305_encrypt + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_aead_chacha20poly1305_encrypt(IntPtr Cipher, out long CipherLength, byte[] Message, long MessageLength, byte[] AdditionalData, long AdditionalDataLength, byte[] NSEC, byte[] Nonce, byte[] Key); + + //crypto_aead_chacha20poly1305_decrypt + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_aead_chacha20poly1305_decrypt(IntPtr Message, out long MessageLength, byte[] NSEC, byte[] Cipher, long CipherLength, byte[] AdditionalData, long AdditionalDataLength, byte[] Nonce, byte[] Key); + + //crypto_aead_xchacha20poly1305_ietf_encrypt + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_aead_xchacha20poly1305_ietf_encrypt(IntPtr Cipher, out long CipherLength, byte[] Message, long MessageLength, byte[] AdditionalData, long AdditionalDataLength, byte[] NSEC, byte[] Nonce, byte[] Key); + + //crypto_aead_xchacha20poly1305_ietf_decrypt + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_aead_xchacha20poly1305_ietf_decrypt(IntPtr Message, out long MessageLength, byte[] NSEC, byte[] Cipher, long CipherLength, byte[] AdditionalData, long AdditionalDataLength, byte[] Nonce, byte[] Key); + + //crypto_aead_aes256gcm_is_available + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_aead_aes256gcm_is_available(); + + //crypto_aead_aes256gcm_encrypt + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_aead_aes256gcm_encrypt(IntPtr Cipher, out long CipherLength, byte[] Message, long MessageLength, byte[] AdditionalData, long AdditionalDataLength, byte[] NSEC, byte[] Nonce, byte[] Key); + + //crypto_aead_aes256gcm_decrypt + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_aead_aes256gcm_decrypt(IntPtr Message, out long MessageLength, byte[] NSEC, byte[] Cipher, long CipherLength, byte[] AdditionalData, long AdditionalDataLength, byte[] Nonce, byte[] Key); + + //crypto_generichash_init + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_generichash_init(IntPtr State, byte[] Key, int KeySize, int HashSize); + + //crypto_generichash_update + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_generichash_update(IntPtr State, byte[] Message, long MessageLength); + + //crypto_generichash_final + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_generichash_final(IntPtr State, byte[] Buffer, int BufferLength); + + //crypto_generichash_state + [StructLayout(LayoutKind.Sequential, Size = 384)] + internal struct HashState + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public ulong[] h; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public ulong[] t; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public ulong[] f; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] + public byte[] buf; + public uint buflen; + public byte last_node; + } + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_stream_xchacha20(byte[] Buffer, int BufferLength, byte[] Nonce, byte[] Key); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int crypto_stream_xchacha20_xor(byte[] Buffer, byte[] Message, long MessageLength, byte[] Nonce, byte[] Key); +} +#pragma warning restore CA1401 // P/Invokes should not be visible \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/FileDirectory.cs b/MsmhToolsClass/MsmhToolsClass/FileDirectory.cs index a204cf9..8ae439c 100644 --- a/MsmhToolsClass/MsmhToolsClass/FileDirectory.cs +++ b/MsmhToolsClass/MsmhToolsClass/FileDirectory.cs @@ -6,7 +6,43 @@ namespace MsmhToolsClass; public class FileDirectory { //----------------------------------------------------------------------------------- - public static async Task MoveDirectory(string sourceDir, string destDir, bool overWrite, CancellationToken token) + public static async Task> GetAllFilesAsync(string dirPath, CancellationToken ct = default) + { + return await Task.Run(async () => + { + List paths = new(); + + try + { + if (!Directory.Exists(dirPath)) return paths; + DirectoryInfo rootDirInfo = new(dirPath); + + FileInfo[] fileInfos = rootDirInfo.GetFiles(); + for (int n = 0; n < fileInfos.Length; n++) + { + if (ct.IsCancellationRequested) break; + FileInfo fileInfo = fileInfos[n]; + paths.Add(fileInfo.FullName); + } + + DirectoryInfo[] dirInfos = rootDirInfo.GetDirectories(); + for (int n = 0; n < dirInfos.Length; n++) + { + if (ct.IsCancellationRequested) break; + DirectoryInfo dirInfo = dirInfos[n]; + paths.AddRange(await GetAllFilesAsync(dirInfo.FullName, ct)); + } + } + catch (Exception ex) + { + Debug.WriteLine("FileDirectory GetAllFiles: " + ex.Message); + } + + return paths; + }, ct); + } + //----------------------------------------------------------------------------------- + public static async Task MoveDirectoryAsync(string sourceDir, string destDir, bool overWrite, CancellationToken ct) { await Task.Run(async () => { @@ -19,24 +55,26 @@ await Task.Run(async () => FileInfo[] fileInfos = rootDirInfo.GetFiles(); for (int n = 0; n < fileInfos.Length; n++) { + if (ct.IsCancellationRequested) break; FileInfo fileInfo = fileInfos[n]; fileInfo.MoveTo(Path.GetFullPath(Path.Combine(destDir, fileInfo.Name)), overWrite); } DirectoryInfo[] dirInfos = rootDirInfo.GetDirectories(); - for (int n2 = 0; n2 < dirInfos.Length; n2++) + for (int n = 0; n < dirInfos.Length; n++) { - DirectoryInfo dirInfo = dirInfos[n2]; - await MoveDirectory(dirInfo.FullName, Path.GetFullPath(Path.Combine(destDir, dirInfo.Name)), overWrite, token); + if (ct.IsCancellationRequested) break; + DirectoryInfo dirInfo = dirInfos[n]; + await MoveDirectoryAsync(dirInfo.FullName, Path.GetFullPath(Path.Combine(destDir, dirInfo.Name)), overWrite, ct); } if (Directory.Exists(sourceDir)) Directory.Delete(sourceDir, false); } catch (Exception ex) { - Debug.WriteLine("MoveDirectory: " + ex.Message); + Debug.WriteLine("FileDirectory MoveDirectory: " + ex.Message); } - }, token); + }, ct); } //----------------------------------------------------------------------------------- public static bool IsPathTooLong(string path) @@ -107,8 +145,14 @@ public static bool IsDirectoryEmpty(string dirPath) /// public static void CreateEmptyFile(string filePath) { - if (!File.Exists(filePath)) - File.Create(filePath).Dispose(); + try + { + if (!File.Exists(filePath)) File.Create(filePath).Dispose(); + } + catch (Exception ex) + { + Debug.WriteLine("FileDirectory CreateEmptyFile: " + ex.Message); + } } //----------------------------------------------------------------------------------- /// @@ -116,8 +160,14 @@ public static void CreateEmptyFile(string filePath) /// public static void CreateEmptyDirectory(string directoryPath) { - if (!Directory.Exists(directoryPath)) - Directory.CreateDirectory(directoryPath); + try + { + if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath); + } + catch (Exception ex) + { + Debug.WriteLine("FileDirectory CreateEmptyDirectory: " + ex.Message); + } } //----------------------------------------------------------------------------------- public static bool CompareByLength(string path1, string path2) @@ -193,7 +243,7 @@ public static void AppendTextLine(string filePath, string textToAppend, Encoding } catch (Exception ex) { - Debug.WriteLine(ex.Message); + Debug.WriteLine("FileDirectory AppendTextLine: " + ex.Message); } } //----------------------------------------------------------------------------------- @@ -209,7 +259,7 @@ public static async Task AppendTextLineAsync(string filePath, string textToAppen } catch (Exception ex) { - Debug.WriteLine("AppendTextLineAsync: " + ex.Message); + Debug.WriteLine("FileDirectory AppendTextLineAsync: " + ex.Message); } } //----------------------------------------------------------------------------------- @@ -223,7 +273,7 @@ public static void AppendText(string filePath, string textToAppend, Encoding enc } catch (Exception ex) { - Debug.WriteLine("AppendText: " + ex.Message); + Debug.WriteLine("FileDirectory AppendText: " + ex.Message); } } //----------------------------------------------------------------------------------- @@ -237,7 +287,7 @@ public static async Task AppendTextAsync(string filePath, string textToAppend, E } catch (Exception ex) { - Debug.WriteLine("AppendTextAsync: " + ex.Message); + Debug.WriteLine("FileDirectory AppendTextAsync: " + ex.Message); } } //----------------------------------------------------------------------------------- @@ -253,7 +303,7 @@ public static void WriteAllText(string filePath, string fileContent, Encoding en } catch (Exception ex) { - Debug.WriteLine("WriteAllText: " + ex.Message); + Debug.WriteLine("FileDirectory WriteAllText: " + ex.Message); } } //----------------------------------------------------------------------------------- @@ -269,7 +319,7 @@ public static async Task WriteAllTextAsync(string filePath, string fileContent, } catch (Exception ex) { - Debug.WriteLine("WriteAllTextAsync: " + ex.Message); + Debug.WriteLine("FileDirectory WriteAllTextAsync: " + ex.Message); } } //----------------------------------------------------------------------------------- @@ -306,119 +356,35 @@ public static bool IsFileLocked(string fileNameOrPath) } catch (Exception ex) { - Debug.WriteLine("IsFileLocked: " + ex.Message); + Debug.WriteLine("FileDirectory IsFileLocked: " + ex.Message); return true; } } //----------------------------------------------------------------------------------- public static List? FindFilesByPartialName(string partialName, string dirPath) - { - if (Directory.Exists(dirPath)) - { - DirectoryInfo hdDirectoryInWhichToSearch = new(dirPath); - FileInfo[] filesInDir = hdDirectoryInWhichToSearch.GetFiles("*" + partialName + "*.*"); - List list = new(); - foreach (FileInfo foundFile in filesInDir) - { - string fullName = foundFile.FullName; - list.Add(fullName); - } - return list; - } - Console.WriteLine("Directory Not Exist: " + dirPath); - return null; - } - //----------------------------------------------------------------------------------- - // ===== Old Methods ================================================================ - public static byte[] ReadAllBytes(MemoryStream memoryStream) - { - return memoryStream.ToArray(); - } - //----------------------------------------------------------------------------------- - public static byte[]? ReadAllBytes(string filePath) { try { - using FileStream fsSource = new(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - // Read the source file into a byte array. - byte[] bytes = new byte[fsSource.Length]; - int numBytesToRead = (int)fsSource.Length; - int numBytesRead = 0; - while (numBytesToRead > 0) + if (Directory.Exists(dirPath)) { - // Read may return anything from 0 to numBytesToRead. - int n = fsSource.Read(bytes, numBytesRead, numBytesToRead); - // Break when the end of the file is reached. - if (n == 0) break; - numBytesRead += n; - numBytesToRead -= n; - } - numBytesToRead = bytes.Length; - return bytes; - } - catch (FileNotFoundException ioEx) - { - Console.WriteLine(ioEx.Message); - return null; - } - } - //----------------------------------------------------------------------------------- - public static async Task ReadAllBytesAsync(string filePath) - { - try - { - using FileStream fsSource = new(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - // Read the source file into a byte array. - byte[] bytes = new byte[fsSource.Length]; - int numBytesToRead = (int)fsSource.Length; - int numBytesRead = 0; - while (numBytesToRead > 0) - { - // Read may return anything from 0 to numBytesToRead. - int n = await fsSource.ReadAsync(bytes.AsMemory(numBytesRead, numBytesToRead)); - // Break when the end of the file is reached. - if (n == 0) break; - numBytesRead += n; - numBytesToRead -= n; + DirectoryInfo hdDirectoryInWhichToSearch = new(dirPath); + FileInfo[] filesInDir = hdDirectoryInWhichToSearch.GetFiles("*" + partialName + "*.*"); + List list = new(); + foreach (FileInfo foundFile in filesInDir) + { + string fullName = foundFile.FullName; + list.Add(fullName); + } + return list; } - numBytesToRead = bytes.Length; - return bytes; + Console.WriteLine("FileDirectory Directory Not Exist: " + dirPath); } - catch (FileNotFoundException ioEx) - { - Console.WriteLine(ioEx.Message); - return null; - } - } - //----------------------------------------------------------------------------------- - public static void WriteAllBytes(string filePath, byte[] bytes) - { - try - { - int numBytesToRead = bytes.Length; - // Write the byte array to the other FileStream. - using FileStream fsNew = new(filePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); - fsNew.Write(bytes, 0, numBytesToRead); - } - catch (FileNotFoundException ioEx) - { - Console.WriteLine(ioEx.Message); - } - } - //----------------------------------------------------------------------------------- - public static async Task WriteAllBytesAsync(string filePath, byte[] bytes) - { - try - { - int numBytesToRead = bytes.Length; - // Write the byte array to the other FileStream. - using FileStream fsNew = new(filePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); - await fsNew.WriteAsync(bytes.AsMemory(0, numBytesToRead)); - } - catch (FileNotFoundException ioEx) + catch (Exception ex) { - Console.WriteLine(ioEx.Message); + Debug.WriteLine("FileDirectory FindFilesByPartialName: " + ex.Message); } + + return null; } //----------------------------------------------------------------------------------- } \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/HttpRequest.cs b/MsmhToolsClass/MsmhToolsClass/HttpRequest.cs new file mode 100644 index 0000000..ea5907d --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/HttpRequest.cs @@ -0,0 +1,586 @@ +using System.Collections.Specialized; +using System.Diagnostics; +using System.Net; +using System.Net.Http.Headers; +using System.Net.Security; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace MsmhToolsClass; + +public class HttpRequestResult +{ + public bool IsSuccess { get; internal set; } = false; + public HttpMethod Method { get; internal set; } = HttpMethod.Get; + public string RawURL { get; internal set; } = string.Empty; + public Uri? URI { get; internal set; } + public string ProtocolVersion { get; internal set; } = string.Empty; + public string ContentType { get; internal set; } = string.Empty; + public string UserAgent { get; internal set; } = string.Empty; + public NameValueCollection Headers { get; internal set; } = new(StringComparer.InvariantCultureIgnoreCase); + public byte[] PayLoad { get; internal set; } = Array.Empty(); + + public override string ToString() + { + string result = string.Empty; + + try + { + result += $"{nameof(IsSuccess)}: {IsSuccess}\r\n"; + result += $"{nameof(Method)}: {Method}\r\n"; + result += $"{nameof(RawURL)}: {RawURL}\r\n"; + if (URI != null) result += $"{nameof(URI)}: {URI}\r\n"; + result += $"{nameof(ProtocolVersion)}: {ProtocolVersion}\r\n"; + result += $"{nameof(ContentType)}: {ContentType}\r\n"; + result += $"{nameof(UserAgent)}: {UserAgent}\r\n"; + if (Headers.Count > 0) result += $"{nameof(Headers)}:\r\n"; + for (int n = 0; n < Headers.Count; n++) + { + string? key = Headers.GetKey(n); + string? val = Headers.Get(n); + if (string.IsNullOrEmpty(key)) continue; + if (string.IsNullOrEmpty(val)) continue; + result += $"{key}: {val}\r\n"; + } + if (PayLoad.Length > 0) result += $"{nameof(PayLoad)}: {BitConverter.ToString(PayLoad)}"; + if (result.EndsWith("\r\n")) result = result.TrimEnd("\r\n"); + } + catch (Exception) { } + + return result; + } +} + + +public class HttpRequestResponse +{ + public bool IsSuccess { get; internal set; } = false; + public string ContentEncoding { get; internal set; } = string.Empty; + public string ContentType { get; internal set; } = string.Empty; + public long ContentLength { get; internal set; } = 0; + public string ProtocolVersion { get; internal set; } = string.Empty; + public int StatusCode { get; internal set; } = (int)HttpStatusCode.NoContent; + public string StatusDescription { get; internal set; } = HttpStatusCode.NoContent.ToString(); + public NameValueCollection Headers { get; internal set; } = new(StringComparer.InvariantCultureIgnoreCase); + public byte[] Data { get; internal set; } = Array.Empty(); + + public override string ToString() + { + string result = string.Empty; + + try + { + result += $"{nameof(IsSuccess)}: {IsSuccess}\r\n"; + result += $"{nameof(ContentEncoding)}: {ContentEncoding}\r\n"; + result += $"{nameof(ContentType)}: {ContentType}\r\n"; + result += $"{nameof(ContentLength)}: {ContentLength}\r\n"; + result += $"{nameof(ProtocolVersion)}: {ProtocolVersion}\r\n"; + result += $"{nameof(StatusCode)}: {StatusCode}\r\n"; + result += $"{nameof(StatusDescription)}: {StatusDescription}\r\n"; + if (Headers.Count > 0) + result += $"{nameof(Headers)}:\r\n"; + for (int n = 0; n < Headers.Count; n++) + { + string? key = Headers.GetKey(n); + string? val = Headers.Get(n); + if (string.IsNullOrEmpty(key)) continue; + if (string.IsNullOrEmpty(val)) continue; + result += $"{key}: {val}\r\n"; + } + result += $"{nameof(Data)}: {Convert.ToHexString(Data)}"; + } + catch (Exception) { } + + return result; + } +} + +/// +/// Authorization Header Options +/// +public class HttpRequestAuthorizationHeader +{ + /// + /// The username to use in the authorization header, if any + /// + public string User { get; set; } = string.Empty; + + /// + /// The password to use in the authorization header, if any + /// + public string Password { get; set; } = string.Empty; + + /// + /// The bearer token to use in the authorization header, if any + /// + public string BearerToken { get; set; } = string.Empty; + + /// + /// Enable to encode credentials in the authorization header + /// + public bool EncodeCredentials { get; set; } = true; + + public override string ToString() + { + string result = string.Empty; + + try + { + result += $"{nameof(User)}: {User}\r\n"; + result += $"{nameof(Password)}: {Password}\r\n"; + result += $"{nameof(BearerToken)}: {BearerToken}\r\n"; + result += $"{nameof(EncodeCredentials)}: {EncodeCredentials}"; + } + catch (Exception) { } + + return result; + } +} + +public class HttpRequest +{ + public CancellationToken CT { get; set; } = default; + public bool IsForHttpProxy { get; set; } = false; + public Uri? URI { get; set; } + public byte[] DataToSend { get; set; } = Array.Empty(); + public HttpMethod Method { get; set; } = HttpMethod.Get; + public string ContentType { get; set; } = string.Empty; + /// + /// Default is "Other" + /// + public string UserAgent { get; set; } = "Other"; + /// + /// User-Agent Will Be Add Automatically (Don't Add To Header) + /// + public NameValueCollection Headers { get; set; } = new(StringComparer.InvariantCultureIgnoreCase); + public bool AllowAutoRedirect { get; set; } = true; + public bool AllowInsecure { get; set; } = true; + public int TimeoutMS { get; set; } = 30000; + public string? ProxyScheme { get; set; } + public string? ProxyUser { get; set; } + public string? ProxyPass { get; set; } + public HttpRequestAuthorizationHeader Authorization { get; set; } = new(); + public X509Certificate2? Certificate { get; set; } + + public override string ToString() + { + string result = string.Empty; + + try + { + result += $"{nameof(IsForHttpProxy)}: {IsForHttpProxy}\r\n"; + if (URI != null) result += $"{nameof(URI)}: {URI}\r\n"; + if (DataToSend.Length > 0) result += $"{nameof(DataToSend)}: {Convert.ToHexString(DataToSend)}\r\n"; + result += $"{nameof(Method)}: {Method}\r\n"; + result += $"{nameof(ContentType)}: {ContentType}\r\n"; + result += $"{nameof(UserAgent)}: {UserAgent}\r\n"; + if (Headers.Count > 0) result += $"{nameof(Headers)}:\r\n"; + for (int n = 0; n < Headers.Count; n++) + { + string? key = Headers.GetKey(n); + string? val = Headers.Get(n); + if (string.IsNullOrEmpty(key)) continue; + if (string.IsNullOrEmpty(val)) continue; + result += $"{key}: {val}\r\n"; + } + result += $"{nameof(AllowAutoRedirect)}: {AllowAutoRedirect}\r\n"; + result += $"{nameof(AllowInsecure)}: {AllowInsecure}\r\n"; + result += $"{nameof(TimeoutMS)}: {TimeoutMS}\r\n"; + if (!string.IsNullOrEmpty(ProxyScheme)) + result += $"{nameof(ProxyScheme)}: {ProxyScheme}\r\n"; + if (!string.IsNullOrEmpty(ProxyUser)) + result += $"{nameof(ProxyUser)}: {ProxyUser}\r\n"; + if (!string.IsNullOrEmpty(ProxyPass)) + result += $"{nameof(ProxyPass)}: {ProxyPass}\r\n"; + result += $"{nameof(Authorization)}:\r\n"; + result += Authorization.ToString() + "\r\n"; + if (Certificate != null) + result += $"{nameof(Certificate)}: {Certificate.SubjectName}\r\n"; + if (result.EndsWith("\r\n")) result = result.TrimEnd("\r\n"); + } + catch (Exception) { } + + return result; + } + + public static HttpRequestResult Read(byte[] buffer) + { + HttpRequestResult hrr = new(); + + try + { + string str = Encoding.UTF8.GetString(buffer); + str.ReplaceLineEndings(); + + string[] headers = str.Split(Environment.NewLine); // new string[] { "\r\n", "\n" } + + string scheme = string.Empty, host = string.Empty, path = string.Empty; + int port = -1; + string contentType = string.Empty; + int contentLength = 0; + + for (int n = 0; n < headers.Length; n++) + { + if (n == 0) + { + // First Line (Method, raw URL, ListenerProtocol/Version) + string[] requestLine = headers[n].Trim().Trim('\0').Split(' ', StringSplitOptions.RemoveEmptyEntries); + + if (requestLine.Length >= 3) + { + // 3 Parts Means Success + hrr.IsSuccess = true; + + // Get Method + hrr.Method = NetworkTool.ParseHttpMethod(requestLine[0]); + + // Get Raw URL + string rawUrl = requestLine[1]; + hrr.RawURL = rawUrl; + + // Get URI (For HTTP & HTTPS Proxies) + // For HTTP: Scheme: Yes, Port: No + // For HTTPS: Scheme: No, Port: Yes + NetworkTool.GetUrlDetails(rawUrl, -1, out scheme, out host, out _, out _, out port, out path, out _); + if (scheme.ToLower().Equals("http://") && port == -1) port = 80; + if (string.IsNullOrEmpty(scheme) && port != -1) scheme = "https://"; + if (string.IsNullOrEmpty(scheme) && port == -1) path = rawUrl; // e.g. DoH Request + + // If Host Is IPv6 + if (host.StartsWith('[')) host = host.TrimStart('['); + if (host.EndsWith(']')) host = host.TrimEnd(']'); + + // Get ProtocolVersion + hrr.ProtocolVersion = requestLine[2]; + } + } + else + { + if (headers[n].Contains(':')) + { + string[] headerLine = headers[n].Split(':'); // Shouldn't RemoveEmptyEntries [::] IPv6 + string key = string.Empty; + string val = string.Empty; + for (int i = 0; i < headerLine.Length; i++) + { + string kvs = headerLine[i]; + if (i == 0) key = kvs; + else val += $"{kvs}:"; + } + if (val.EndsWith(':')) val = val.TrimEnd(':'); + + key = key.Trim(); + val = val.Trim(); + + if (string.IsNullOrEmpty(key)) continue; + if (string.IsNullOrEmpty(val)) continue; + string keyEval = key.ToLower(); + + // Get Host + if (keyEval.Equals("host") && string.IsNullOrEmpty(host)) + { + host = val; + if (host.Contains(':')) + { + NetworkTool.GetUrlDetails(host, -1, out _, out string host2, out _, out _, out int port2, out _, out _); + + // If Host Is IPv6 + if (host2.StartsWith('[')) host2 = host2.TrimStart('['); + if (host2.EndsWith(']')) host2 = host2.TrimEnd(']'); + + host = host2; + if (port == -1) port = port2; + } + } + + // Get ContentType + if (keyEval.Equals("accept")) + { + if (string.IsNullOrEmpty(contentType)) contentType = val; + } + + if (keyEval.Equals("content-type")) + { + contentType = val; + } + + // Get ContentLength + if (keyEval.Equals("content-length")) + { + bool isIntSuccess = int.TryParse(val, out int cl); + if (isIntSuccess) contentLength = cl; + } + + // Get UserAgent + if (keyEval.Equals("user-agent")) + hrr.UserAgent = val; + else hrr.Headers.AddAndUpdate(key, val); // Get Other Headers + } + } + } + + // Set ContentType + hrr.ContentType = contentType; + + // Get PayLoad + if (contentLength > 0 && buffer.Length > contentLength && hrr.Method != HttpMethod.Get && hrr.Method != HttpMethod.Head) + { + int startIndex = buffer.Length - contentLength; + hrr.PayLoad = buffer[startIndex..]; + } + + // Generate URI + if (string.IsNullOrEmpty(scheme)) scheme = "https://"; + if (!string.IsNullOrEmpty(host) && port != -1) + { + UriBuilder uriBuilder = new() + { + Scheme = scheme.ToLower(), + Host = host.ToLower(), + Port = port, + Path = path + }; + hrr.URI = uriBuilder.Uri; + } + } + catch (Exception ex) + { + Debug.WriteLine("HttpRequest Read: " + ex.Message); + } + + return hrr; + } + + public static async Task SendAsync(HttpRequest hr) + { + HttpRequestResponse hrr = new(); + + Task task = Task.Run(async () => + { + try + { + static bool callback(object sender, X509Certificate? cert, X509Chain? chain, SslPolicyErrors sslPolicyErrors) => true; + SslProtocols protocols = SslProtocols.None | SslProtocols.Tls12 | SslProtocols.Tls13; + + HttpClientHandler handler = new() + { + SslProtocols = protocols, + AllowAutoRedirect = hr.AllowAutoRedirect + }; + + // Ignore Cert Check + if (hr.AllowInsecure) + { + handler.ClientCertificateOptions = ClientCertificateOption.Manual; + handler.ServerCertificateCustomValidationCallback = callback; + } + + if (!string.IsNullOrEmpty(hr.ProxyScheme)) + { + NetworkCredential credential = new(hr.ProxyUser, hr.ProxyPass); + handler.Proxy = new WebProxy(hr.ProxyScheme, true, null, credential); + handler.Credentials = credential; + handler.UseProxy = true; + } + else handler.UseProxy = false; + + if (hr.Certificate != null) + { + handler.ClientCertificateOptions = ClientCertificateOption.Manual; + handler.ClientCertificates.Add(hr.Certificate); + } + + HttpContent? content = null; + if (hr.DataToSend.Length > 0 && hr.Method != HttpMethod.Get && hr.Method != HttpMethod.Head) + { + content = new ReadOnlyMemoryContent(hr.DataToSend); + if (!string.IsNullOrEmpty(hr.ContentType)) + content.Headers.ContentType = new MediaTypeHeaderValue(hr.ContentType); + } + + HttpClient httpClient = new(handler); + httpClient.Timeout = TimeSpan.FromMilliseconds(hr.TimeoutMS); + httpClient.DefaultRequestHeaders.ExpectContinue = false; + httpClient.DefaultRequestHeaders.ConnectionClose = true; + + HttpRequestMessage message = new(hr.Method, hr.URI); + message.Content = content; + if (!string.IsNullOrEmpty(hr.ContentType)) + message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(hr.ContentType)); + message.Headers.TryAddWithoutValidation("User-Agent", hr.UserAgent); + + for (int n = 0; n < hr.Headers.Count; n++) + { + string? key = hr.Headers.GetKey(n); + string? value = hr.Headers.Get(n); + if (string.IsNullOrEmpty(key)) continue; + if (string.IsNullOrEmpty(value)) continue; + + if (key.ToLower().Trim().Equals("close")) + { + if (!hr.IsForHttpProxy) message.Headers.TryAddWithoutValidation(key, value); + } + else if (key.ToLower().Trim().Equals("connection")) + { + if (!hr.IsForHttpProxy) message.Headers.TryAddWithoutValidation(key, value); + } + else if (key.ToLower().Trim().Equals("content-length")) + { + if (!hr.IsForHttpProxy) message.Headers.TryAddWithoutValidation(key, value); + } + else if (key.ToLower().Trim().Equals("accept")) + { + if (!hr.IsForHttpProxy) message.Headers.TryAddWithoutValidation(key, value); + } + else if (key.ToLower().Trim().Equals("accept-encoding")) + { + if (!hr.IsForHttpProxy) message.Headers.TryAddWithoutValidation(key, value); + } + else if (key.ToLower().Trim().Equals("content-type")) + { + if (message.Content != null && hr.Method != HttpMethod.Get && hr.Method != HttpMethod.Head) + message.Content.Headers.ContentType = new MediaTypeHeaderValue(value); + } + else + { + message.Headers.TryAddWithoutValidation(key, value); + //Debug.WriteLine("==========> " + key + ": " + value); + } + } + + // Add Auth Info + if (!string.IsNullOrEmpty(hr.Authorization.User)) + { + if (hr.Authorization.EncodeCredentials) + { + string authInfo = $"{hr.Authorization.User}:{hr.Authorization.Password}"; + authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo)); + httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + authInfo); + } + else + { + httpClient.DefaultRequestHeaders.Add("Authorization", $"Basic {hr.Authorization.User}:{hr.Authorization.Password}"); + } + } + else if (!string.IsNullOrEmpty(hr.Authorization.BearerToken)) + { + httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {hr.Authorization.BearerToken}"); + } + + HttpResponseMessage response = await httpClient.SendAsync(message, hr.CT).ConfigureAwait(false); + + hrr.IsSuccess = response.IsSuccessStatusCode; + hrr.ProtocolVersion = $"HTTP/{response.Version}"; + hrr.StatusCode = (int)response.StatusCode; + hrr.StatusDescription = response.StatusCode.ToString(); + + hrr.ContentEncoding = string.Join(",", response.Content.Headers.ContentEncoding); + if (response.Content.Headers.ContentType != null) + hrr.ContentType = response.Content.Headers.ContentType.ToString(); + + if (response.Content.Headers.ContentLength != null) + hrr.ContentLength = response.Content.Headers.ContentLength.Value; + + foreach (var header in response.Headers) + { + string key = header.Key; + string value = string.Join(",", header.Value); + hrr.Headers.AddAndUpdate(key, value); + } + + if (hrr.IsSuccess) + { + hrr.Data = await response.Content.ReadAsByteArrayAsync(hr.CT).ConfigureAwait(false); + } + + try + { + _ = Task.Run(() => + { + handler.Dispose(); + content?.Dispose(); + message.Dispose(); + httpClient.Dispose(); + response.Dispose(); + }); + } + catch (Exception) { } + } + catch (WebException we) + { + if (we.Response is HttpWebResponse exceptionResponse) + { + hrr.IsSuccess = false; + hrr.ProtocolVersion = $"HTTP/{exceptionResponse.ProtocolVersion}"; + hrr.StatusCode = (int)exceptionResponse.StatusCode; + hrr.StatusDescription = exceptionResponse.StatusDescription; + hrr.ContentEncoding = exceptionResponse.ContentEncoding; + hrr.ContentType = exceptionResponse.ContentType; + hrr.ContentLength = exceptionResponse.ContentLength; + + if (exceptionResponse.Headers != null && exceptionResponse.Headers.Count > 0) + { + for (int n = 0; n < exceptionResponse.Headers.Count; n++) + { + string key = exceptionResponse.Headers.GetKey(n); + string val = string.Empty; + int valCount = 0; + + string[]? getValues = exceptionResponse.Headers.GetValues(n); + if (getValues != null) + { + foreach (string value in getValues) + { + if (valCount == 0) + { + val += value; + valCount++; + } + else + { + val += $",{value}"; + valCount++; + } + } + } + + hrr.Headers.AddAndUpdate(key, val); + } + } + + Stream? stream = null; + if (exceptionResponse.ContentLength > 0) + { + hrr.ContentLength = exceptionResponse.ContentLength; + stream = exceptionResponse.GetResponseStream(); + byte[] data = await ByteArrayTool.StreamToBytes(stream).ConfigureAwait(false); + if (data.Length > 0) + { + hrr.Data = data; + hrr.IsSuccess = true; + } + } + + try + { + _ = Task.Run(() => + { + exceptionResponse.Dispose(); + stream?.Dispose(); + }); + } + catch (Exception) { } + } + } + catch (Exception ex) + { + Debug.WriteLine("HttpRequest SendAsync: " + ex.GetInnerExceptions()); + hrr.IsSuccess = false; + } + }); + try { await task.WaitAsync(TimeSpan.FromMilliseconds(hr.TimeoutMS), hr.CT); } catch (Exception) { } + + return hrr; + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/AgnosticRequest.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/AgnosticRequest.cs new file mode 100644 index 0000000..0de1938 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/AgnosticRequest.cs @@ -0,0 +1,246 @@ +using System.Net.Sockets; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public enum RequestProtocol +{ + UDP, + TCP, + DoH, + HTTP_S, + SOCKS4_4A, + SOCKS5, + SniProxy, + Unknown +} + +public enum SslKind +{ + NonSSL, + SSL +} + +public class AgnosticResult +{ + public IPEndPoint Local_EndPoint { get; set; } = new(IPAddress.None, 0); + public IPEndPoint Remote_EndPoint { get; set; } = new(IPAddress.None, 0); + public Socket? Socket { get; internal set; } + public SslStream? Ssl_Stream { get; set; } + public SslKind Ssl_Kind { get; set; } = SslKind.NonSSL; + public byte[] FirstBuffer { get; internal set; } = Array.Empty(); + public RequestProtocol Protocol { get; internal set; } = RequestProtocol.Unknown; + public string SNI { get; internal set; } = string.Empty; // If RequestProtocol Is SniProxy +} + +public class AgnosticRequest +{ + public enum ListenerProtocol + { + UDP, + TCP + } + + public Socket? Udp_Socket { get; set; } + public TcpClient? Tcp_Client { get; set; } + public SslStream? Ssl_Stream { get; set; } + public IPEndPoint? Local_EndPoint { get; set; } + public IPEndPoint? Remote_EndPoint { get; set; } + public byte[] Peeked_Buffer { get; set; } = Array.Empty(); + public ListenerProtocol Protocol { get; set; } + + public async Task GetResultAsync(AgnosticSettingsSSL settingsSSL) + { + AgnosticResult ar = new(); + if (Local_EndPoint == null) return ar; + if (Remote_EndPoint == null) return ar; + if (Peeked_Buffer.Length < 2) return ar; + + try + { + ar.Local_EndPoint = Local_EndPoint; + ar.Remote_EndPoint = Remote_EndPoint; + + if (Protocol == ListenerProtocol.UDP && Udp_Socket != null) + { + ar.Socket = Udp_Socket; + ar.FirstBuffer = Peeked_Buffer; + ar.Protocol = RequestProtocol.UDP; + } + else if (Protocol == ListenerProtocol.TCP && Tcp_Client != null) + { + ByteArrayTool.TryConvertBytesToUInt16(Peeked_Buffer[..2], out ushort tcpLength); + if (tcpLength == Peeked_Buffer.Length - 2) + { + ar.Socket = Tcp_Client.Client; + ar.FirstBuffer = await HandleNonSslAsync().ConfigureAwait(false); + ar.Protocol = RequestProtocol.TCP; + } + else if (Peeked_Buffer[0] == 0x04) + { + ar.Socket = Tcp_Client.Client; + ar.FirstBuffer = await HandleNonSslAsync().ConfigureAwait(false); + ar.Protocol = RequestProtocol.SOCKS4_4A; + } + else if (Peeked_Buffer[0] == 0x05) + { + ar.Socket = Tcp_Client.Client; + ar.FirstBuffer = await HandleNonSslAsync().ConfigureAwait(false); + ar.Protocol = RequestProtocol.SOCKS5; + } + else + { + byte[] firstBuffer = Array.Empty(); + if (Peeked_Buffer[0] == 0x16) // SSL + { + SniReader sniReader = new(Peeked_Buffer); + if (sniReader.HasSni) + { + if (sniReader.SniList.Count != 0) + { + string sni = sniReader.SniList[0].ServerName; + ar.SNI = sni; + ar.Socket = Tcp_Client.Client; + ar.FirstBuffer = Peeked_Buffer; + ar.Protocol = RequestProtocol.SniProxy; + } + } + else + { + firstBuffer = await HandleSslAsync(settingsSSL).ConfigureAwait(false); + ar.Ssl_Kind = SslKind.SSL; + } + } + else + { + firstBuffer = await HandleNonSslAsync().ConfigureAwait(false); + } + + if (firstBuffer.Length > 0) + { + HttpRequestResult hrResult = HttpRequest.Read(firstBuffer); + if (hrResult.IsSuccess) + { + if (hrResult.ContentType.Equals(MsmhAgnosticServer.DnsMessageContentType)) + { + if (hrResult.Method == HttpMethod.Get && hrResult.RawURL.Contains("dns=", StringComparison.InvariantCultureIgnoreCase)) + { + // DoH Get Method + string[] split = hrResult.RawURL.Split("dns=", StringSplitOptions.RemoveEmptyEntries); + if (split.Length >= 2) + { + string base64UrlQuery = split[1]; + byte[] base64QueryBuffer = EncodingTool.UrlDecode(base64UrlQuery); + if (base64QueryBuffer.Length > 0) + { + ar.Socket = Tcp_Client.Client; + ar.Ssl_Stream = Ssl_Stream; + ar.FirstBuffer = base64QueryBuffer; + ar.Protocol = RequestProtocol.DoH; + } + } + } + else if (hrResult.Method == HttpMethod.Post && hrResult.PayLoad.Length > 0) + { + // DoH Post Method + ar.Socket = Tcp_Client.Client; + ar.Ssl_Stream = Ssl_Stream; + ar.FirstBuffer = hrResult.PayLoad; + ar.Protocol = RequestProtocol.DoH; + } + } + else + { + ar.Socket = Tcp_Client.Client; + ar.FirstBuffer = firstBuffer; + ar.Protocol = RequestProtocol.HTTP_S; + } + } + } + } + } + } + catch (Exception ex) + { + Debug.WriteLine("AgnosticRequest GetResultAsync: " + ex.Message); + } + + return ar; + } + + private async Task HandleNonSslAsync() + { + if (Tcp_Client != null) + { + try + { + byte[] buffer = new byte[MsmhAgnosticServer.MaxDataSize]; + int received = await Tcp_Client.Client.ReceiveAsync(buffer, SocketFlags.None).ConfigureAwait(false); + if (received > 0) + { + buffer = buffer[..received]; + return buffer; + } + } + catch (Exception ex) + { + Debug.WriteLine("AgnosticRequest HandleNonSslAsync: " + ex.Message); + } + } + return Array.Empty(); + } + + private async Task HandleSslAsync(AgnosticSettingsSSL settingsSSL) + { + if (Tcp_Client != null && settingsSSL.EnableSSL && settingsSSL.Cert != null) + { + try + { + Stopwatch stopwatch = Stopwatch.StartNew(); + + SslServerAuthenticationOptions optionsServer = new() + { + ServerCertificate = settingsSSL.Cert, + ClientCertificateRequired = false, + EnabledSslProtocols = MsmhAgnosticServer.SSL_Protocols, + CertificateRevocationCheckMode = X509RevocationMode.NoCheck, + RemoteCertificateValidationCallback = MsmhAgnosticServer.Callback + }; + + Ssl_Stream = new(Tcp_Client.GetStream(), false, MsmhAgnosticServer.Callback, null); + await Ssl_Stream.AuthenticateAsServerAsync(optionsServer, CancellationToken.None).ConfigureAwait(false); + + byte[] buffer = new byte[MsmhAgnosticServer.MaxDataSize]; + int received = await Ssl_Stream.ReadAsync(buffer, CancellationToken.None).ConfigureAwait(false); + if (received > 0) + { + buffer = buffer[..received]; + Debug.WriteLine($"SSL Handshake As Server Success. Took: {stopwatch.ElapsedMilliseconds}"); + stopwatch.Stop(); + return buffer; + } + stopwatch.Stop(); + } + catch (Exception ex) + { + Debug.WriteLine("AgnosticRequest HandleSslAsServerAsync: " + ex.GetInnerExceptions()); + } + } + return Array.Empty(); + } + + public void Disconnect() + { + try + { + Tcp_Client?.Client.Shutdown(SocketShutdown.Both); + Tcp_Client?.Dispose(); + Ssl_Stream?.Dispose(); + } + catch (Exception) { } + } + +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/AgnosticSettings.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/AgnosticSettings.cs new file mode 100644 index 0000000..98e2513 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/AgnosticSettings.cs @@ -0,0 +1,166 @@ +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; + +#nullable enable +namespace MsmhToolsClass.MsmhAgnosticServer; + +/// +/// Server Settings. +/// +public class AgnosticSettings +{ + public enum WorkingMode + { + Dns, + DnsAndProxy + } + + private int PListenerPort { get; set; } = 53; + /// + /// The Port On Which To Listen. + /// + public int ListenerPort + { + get => PListenerPort; + set + { + if (value < 0 || value > 65535) throw new ArgumentOutOfRangeException(nameof(ListenerPort)); + PListenerPort = value; + } + } + + public WorkingMode Working_Mode { get; set; } = WorkingMode.DnsAndProxy; + + private int PMaxRequests { get; set; } = 5000; + /// + /// Maximum Number Of Threads Per Second. (Min: 20) + /// + public int MaxRequests + { + get => PMaxRequests; + set + { + PMaxRequests = value >= MsmhAgnosticServer.MaxRequestsDivide ? value : MsmhAgnosticServer.MaxRequestsDivide; + } + } + + /// + /// Cancel Dns Request if didn't receive data for n seconds. Default: 5 Sec + /// + public int DnsTimeoutSec { get; set; } = 5; + + /// + /// Kill Proxy Request if didn't receive data for n seconds. Default: 40 Sec ( 0 = Disabled) + /// + public int ProxyTimeoutSec { get; set; } = 40; + + /// + /// Kill Proxy Requests If CPU Usage is Higher than this Value. (Windows Only) + /// + public float KillOnCpuUsage { get; set; } = 40; + + public bool BlockPort80 { get; set; } = false; + + public bool AllowInsecure { get; set; } = false; + + public List DNSs { get; set; } = new(); + public string? CloudflareCleanIP { get; set; } + + private IPAddress PBootstrapIpAddress { get; set; } = IPAddress.None; + public IPAddress BootstrapIpAddress + { + get => PBootstrapIpAddress; + set => PBootstrapIpAddress = value; + } + + private int PBootstrapPort { get; set; } = 53; + public int BootstrapPort + { + get => PBootstrapPort; + set + { + if (value < 0 || value > 65535) throw new ArgumentOutOfRangeException(nameof(BootstrapPort)); + PBootstrapPort = value; + } + } + + /// + /// Only HTTP And Socks5 Are Supported + /// + public string? UpstreamProxyScheme { get; set; } + public string? UpstreamProxyUser { get; set; } + public string? UpstreamProxyPass { get; set; } + public bool ApplyUpstreamOnlyToBlockedIps { get; set; } + + // Default DNSs + public static List DefaultDNSs() + { + return new List() + { + "tcp://8.8.8.8:53", + "tcp://1.1.1.1:53", + "udp://9.9.9.9:9953", + //"system" + }; + } + + public bool IsIPv4SupportedByOS { get; private set; } + public bool IsIPv6SupportedByOS { get; private set; } + public bool IsIPv4SupportedByISP { get; private set; } + public bool IsIPv6SupportedByISP { get; private set; } + public IPAddress? ListenerIP { get; private set; } + public IPEndPoint ServerEndPoint { get; internal set; } = new(IPAddress.None, 0); + public string ServerUdpDnsAddress { get; internal set; } = string.Empty; + public string ServerTcpDnsAddress { get; internal set; } = string.Empty; + public string ServerDohDnsAddress { get; internal set; } = string.Empty; + public string ServerHttpProxyAddress { get; internal set; } = string.Empty; + public string ServerSocks5ProxyAddress { get; internal set; } = string.Empty; + + public AgnosticSettings() + { + if (OperatingSystem.IsWindows()) + { + ProcessManager.ExecuteOnly("ipconfig", null, "/flushdns", true, true); + IsIPv4SupportedByISP = NetworkTool.IsIpProtocolReachable("8.8.8.8"); + IsIPv6SupportedByISP = NetworkTool.IsIpProtocolReachable("2001:4860:4860::8888"); + } + else + { + IsIPv4SupportedByISP = true; + IsIPv6SupportedByISP = false; + } + + IsIPv4SupportedByOS = NetworkTool.IsIPv4Supported(); + IsIPv6SupportedByOS = NetworkTool.IsIPv6Supported(); + + ListenerIP = IsIPv6SupportedByOS ? IPAddress.IPv6Any : IsIPv4SupportedByOS ? IPAddress.Any : null; + } + + public void Initialize() + { + try + { + if (ListenerIP != null) ServerEndPoint = new(ListenerIP, ListenerPort); + + ServerUdpDnsAddress = $"udp://{IPAddress.Loopback}:{ListenerPort}"; + ServerTcpDnsAddress = $"tcp://{IPAddress.Loopback}:{ListenerPort}"; + ServerDohDnsAddress = $"https://{IPAddress.Loopback}:{ListenerPort}/dns-query"; + ServerHttpProxyAddress = $"http://{IPAddress.Loopback}:{ListenerPort}"; + ServerSocks5ProxyAddress = $"socks5://{IPAddress.Loopback}:{ListenerPort}"; + + if (ServerEndPoint.AddressFamily == AddressFamily.InterNetworkV6) + { + ServerUdpDnsAddress = $"udp://[{IPAddress.IPv6Loopback}]:{ListenerPort}"; + ServerTcpDnsAddress = $"tcp://[{IPAddress.IPv6Loopback}]:{ListenerPort}"; + ServerDohDnsAddress = $"https://[{IPAddress.IPv6Loopback}]:{ListenerPort}/dns-query"; + ServerHttpProxyAddress = $"http://[{IPAddress.IPv6Loopback}]:{ListenerPort}"; + ServerSocks5ProxyAddress = $"socks5://[{IPAddress.IPv6Loopback}]:{ListenerPort}"; + } + } + catch (Exception ex) + { + Debug.WriteLine("AgnosticSettings Initialize: " + ex.Message); + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/AgnosticSettingsSSL.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/AgnosticSettingsSSL.cs new file mode 100644 index 0000000..4a74dc6 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/AgnosticSettingsSSL.cs @@ -0,0 +1,193 @@ +using System.Diagnostics; +using System.Net; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class AgnosticSettingsSSL +{ + public bool EnableSSL { get; set; } = false; + public X509Certificate2 RootCA { get; private set; } + public string? RootCA_Path { get; set; } + public string? RootCA_KeyPath { get; set; } + public X509Certificate2 Cert { get; private set; } + public string? Cert_Path { get; set; } + public string? Cert_KeyPath { get; set; } + public bool ChangeSni { get; set; } = true; + public string DefaultSni { get; set; } = string.Empty; + + public AgnosticSettingsSSL(bool enableSSL) + { + EnableSSL = enableSSL; + RootCA = new(Array.Empty()); + Cert = new(Array.Empty()); + if (!EnableSSL) + { + try + { + RootCA.Dispose(); + Cert.Dispose(); + } + catch (Exception) { } + return; + } + } + + public async Task Build() + { + if (!EnableSSL) return; + + try + { + if (!string.IsNullOrEmpty(RootCA_Path) && File.Exists(RootCA_Path) && + !string.IsNullOrEmpty(Cert_Path) && File.Exists(Cert_Path)) + { + // Read From File + X509Certificate2? rootCA = BuildByFile(RootCA_Path, RootCA_KeyPath, true); + if (rootCA != null) RootCA = new(rootCA); + + X509Certificate2? cert = BuildByFile(Cert_Path, Cert_KeyPath, false); + if (cert != null) Cert = new(cert); + } + else + { + string certificateDirPath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "certificate")); + string issuerCertPath = Path.GetFullPath(Path.Combine(certificateDirPath, "rootCA.crt")); + string issuerKeyPath = Path.GetFullPath(Path.Combine(certificateDirPath, "rootCA.key")); + string certPath = Path.GetFullPath(Path.Combine(certificateDirPath, "localhost.crt")); + string keyPath = Path.GetFullPath(Path.Combine(certificateDirPath, "localhost.key")); + + string issuerSubjectName = "Msmh Agnostic Server Authority"; + string subjectName = "Msmh Agnostic Server"; + + X509Certificate2? rootCACert = null; + X509Certificate2? cert = null; + + // Check IF Cert Exist + if (File.Exists(issuerCertPath) && File.Exists(issuerKeyPath) && + File.Exists(certPath) && File.Exists(keyPath)) + { + // Read From File + rootCACert = BuildByFile(issuerCertPath, issuerKeyPath, true); + cert = BuildByFile(certPath, keyPath, false); + } + else + { + try + { + if (File.Exists(issuerCertPath)) File.Delete(issuerCertPath); + if (File.Exists(issuerKeyPath)) File.Delete(issuerKeyPath); + if (File.Exists(certPath)) File.Delete(certPath); + if (File.Exists(keyPath)) File.Delete(keyPath); + } + catch (Exception) { } + + bool isInstalled = CertificateTool.IsCertificateInstalled(issuerSubjectName, StoreName.Root, StoreLocation.CurrentUser); + if (isInstalled) + { + while (true) + { + bool uninstalled = CertificateTool.UninstallCertificate(issuerSubjectName, StoreName.Root, StoreLocation.CurrentUser); + if (uninstalled) break; + } + } + + // Generate + // Create certificate directory + FileDirectory.CreateEmptyDirectory(certificateDirPath); + // It is overwritten, no need to delete. + IPAddress? gateway = NetworkTool.GetDefaultGateway() ?? IPAddress.Loopback; + await CertificateTool.GenerateCertificateAsync(certificateDirPath, gateway, issuerSubjectName, subjectName); + CertificateTool.CreateP12(issuerCertPath, issuerKeyPath); + CertificateTool.CreateP12(certPath, keyPath); + + if (File.Exists(issuerCertPath) && File.Exists(issuerKeyPath) && + File.Exists(certPath) && File.Exists(keyPath)) + { + // Read From File + rootCACert = BuildByFile(issuerCertPath, issuerKeyPath, true); + cert = BuildByFile(certPath, keyPath, false); + } + } + + if (rootCACert != null && cert != null) + { + RootCA_Path = issuerCertPath; + RootCA_KeyPath = issuerKeyPath; + Cert_Path = certPath; + Cert_KeyPath = keyPath; + + RootCA = new(rootCACert); + Cert = new(cert); + } + } + } + catch (Exception ex) + { + RootCA.Dispose(); + EnableSSL = false; + Debug.WriteLine("AgnosticSettingsSSL: " + ex.Message); + } + + // Check for "m_safeCertContext is an invalid handle" + try + { + _ = RootCA.Subject; + _ = Cert.Subject; + } + catch (Exception) + { + RootCA.Dispose(); + Cert.Dispose(); + EnableSSL = false; + } + + try + { + // Install RootCA + if (EnableSSL && !string.IsNullOrEmpty(RootCA_Path) && File.Exists(RootCA_Path)) + { + // Check If Cert is Installed + bool isInstalled = CertificateTool.IsCertificateInstalled(RootCA.Subject, StoreName.Root, StoreLocation.CurrentUser); + if (!isInstalled) + { + // Install Cert + bool certInstalled = CertificateTool.InstallCertificate(RootCA_Path, StoreName.Root, StoreLocation.CurrentUser); + if (!certInstalled) + { + // User refused to install cert + RootCA.Dispose(); + Cert.Dispose(); + EnableSSL = false; + } + } + } + } + catch (Exception) { } + } + + private static X509Certificate2? BuildByFile(string cert_Path, string? cert_KeyPath, bool isRootCA) + { + try + { + X509Certificate2 cert = new(X509Certificate2.CreateFromCertFile(cert_Path)); + + if (!cert.HasPrivateKey && !string.IsNullOrEmpty(cert_KeyPath) && File.Exists(cert_KeyPath)) + { + RSA key = RSA.Create(); + key.ImportFromPem(File.ReadAllText(cert_KeyPath).ToCharArray()); + cert = cert.CopyWithPrivateKey(key); + } + string pass = Guid.NewGuid().ToString(); + cert = new(cert.Export(X509ContentType.Pfx, pass), pass); + return new(cert); + } + catch (Exception ex) + { + Debug.WriteLine("AgnosticSettingsSSL BuildByFile: " + ex.Message); + return null; + } + } + +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/CommonTools.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/CommonTools.cs new file mode 100644 index 0000000..0b066e4 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/CommonTools.cs @@ -0,0 +1,134 @@ +using System.Net; +using System.Text; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public static class CommonTools +{ + //public static unsafe void SetReUsePort(this Socket socket) + //{ + // socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1); + // if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + // { + // // set SO_REUSEADDR (https://github.com/dotnet/corefx/issues/32027) + // var value = 1; + // setsockopt(socket.Handle.ToInt32(), 1, 2, &value, sizeof(int)); + // } + // else + // socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, 1); + + //} + + //[DllImport("libc", SetLastError = true)] + //private static extern unsafe int setsockopt(int socket, int level, int option_name, void* option_value, uint option_len); + + public static bool TryConvertToEnum(bool[] bits, out T result) where T : struct, IConvertible + { + try + { + int len = bits.Length; + StringBuilder sb = new(len); + + for (int n = 0; n < len; n++) sb.Append(bits[n] ? "1" : "0"); + + result = (T)Enum.Parse(typeof(T), sb.ToString()); + return true; + } + catch (Exception) + { + result = default; + return false; + } + } + + /// + /// Get root domain from a given hostname + /// + /// + /// + public static string GetWildCardDomainName(string hostname) + { + // Only for subdomains we need wild card + // Example www.google.com or gstatic.google.com + // But NOT for google.com or IP address + + if (NetworkTool.IsIp(hostname, out _)) return hostname; + + NetworkTool.GetHostDetails(hostname, 443, out _, out _, out string baseHost, out _, out _, out _); + + if (baseHost.Equals(hostname)) return hostname; + + return $"*.{baseHost}"; + } + + public static bool IsCfIP(string ipString) + { + try + { + List cloudflareIPs = new() + { + "103.21.244.0 - 103.21.244.255", + "103.22.200.0 - 103.22.200.255", + "103.31.4.0 - 103.31.5.255", + "104.16.0.0 - 104.31.255.255", + "108.162.192.0 - 108.162.207.255", + "131.0.72.0 - 131.0.75.255", + "141.101.64.0 - 141.101.65.255", + "162.158.0.0 - 162.158.3.255", + "172.64.0.0 - 172.67.255.255", + "173.245.48.0 - 173.245.48.255", + "188.114.96.0 - 188.114.99.255", + "190.93.240.0 - 190.93.243.255", + "197.234.240.0 - 197.234.243.255", + "198.41.128.0 - 198.41.143.255" + }; + + string[] ips = ipString.Split('.'); + int ip1 = int.Parse(ips[0]); + int ip2 = int.Parse(ips[1]); + int ip3 = int.Parse(ips[2]); + int ip4 = int.Parse(ips[3]); + + for (int n = 0; n < cloudflareIPs.Count; n++) + { + string ipRange = cloudflareIPs[n].Trim(); + + if (!string.IsNullOrEmpty(ipRange)) + { + string[] split = ipRange.Split('-', StringSplitOptions.TrimEntries); + string ipMin = split[0].Trim(); + string ipMax = split[1].Trim(); + + string[] ipMins = ipMin.Split('.'); + int ipMin1 = int.Parse(ipMins[0]); + int ipMin2 = int.Parse(ipMins[1]); + int ipMin3 = int.Parse(ipMins[2]); + int ipMin4 = int.Parse(ipMins[3]); + + string[] ipMaxs = ipMax.Split('.'); + int ipMax1 = int.Parse(ipMaxs[0]); + int ipMax2 = int.Parse(ipMaxs[1]); + int ipMax3 = int.Parse(ipMaxs[2]); + int ipMax4 = int.Parse(ipMaxs[3]); + + if (ip1 >= ipMin1 && ip1 <= ipMax1) + if (ip2 >= ipMin2 && ip2 <= ipMax2) + if (ip3 >= ipMin3 && ip3 <= ipMax3) + if (ip4 >= ipMin4 && ip4 <= ipMax4) + return true; + } + } + return false; + } + catch (Exception) + { + return false; + } + } + + public static bool IsCfIP(IPAddress ipv4) + { + return IsCfIP(ipv4.ToString()); + } + +} diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsCache.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsCache.cs new file mode 100644 index 0000000..21a0df6 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsCache.cs @@ -0,0 +1,206 @@ +using System.Collections.Concurrent; +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class DnsCache +{ + private readonly ConcurrentDictionary Caches = new(); + + public bool TryGet(DnsMessage dmQ, out DnsMessage dmR) + { + dmR = new DnsMessage(); + bool success = false; + + try + { + success = Caches.TryGetValue(dmQ.Questions.ToString(), out DnsMessage? dmROut); + if (success && dmROut != null) + { + dmR = CreateFromCache(dmQ, dmROut); + }; + } + catch (Exception) { } + + return success; + } + + public bool TryRemove(DnsMessage dmQ) + { + try + { + return Caches.TryRemove(dmQ.Questions.ToString(), out _); + } + catch (Exception) + { + return false; + } + } + + public bool TryAdd(DnsMessage dmQ, DnsMessage dmR) + { + try + { + bool canCache = CanCache(dmR); + //Debug.WriteLine("CAN CACHE: " + canCache); + if (canCache) return Caches.TryAdd(dmQ.Questions.ToString(), dmR); + else return false; + } + catch (Exception) + { + return false; + } + } + + public int CachedRequests + { + get + { + try + { + return Caches.ToList().Count; + } + catch (Exception) + { + return -1; + } + } + } + + public void Flush() + { + try + { + Caches.Clear(); + } + catch (Exception) { } + } + + private static DnsMessage CreateFromCache(DnsMessage dmQ, DnsMessage dmR) + { + try + { + dmR.DnsProtocol = dmQ.DnsProtocol; + dmR.Header.ID = dmQ.Header.ID; + modifyTTL(dmR.Answers.AnswerRecords); + modifyTTL(dmR.Authorities.AuthorityRecords); + modifyTTL(dmR.Additionals.AdditionalRecords); + return dmR; + + void modifyTTL(List rrs) + { + uint newTTL = 0; + foreach (ResourceRecord rr in rrs.Cast()) + { + try + { + //Debug.WriteLine("-=-==-= " + rr.TTLDateTime); + //Debug.WriteLine("-=-==-= " + rr.TimeToLive); + + if (newTTL != 0) + { + rr.TTLDateTime = DateTime.UtcNow; + rr.TimeToLive = newTTL; + } + + if (newTTL == 0 && rr.TimeToLive > 0) + { + double ttlDifferDouble = (DateTime.UtcNow - rr.TTLDateTime).TotalSeconds; + //Debug.WriteLine("----------- " + ttlDifferDouble); + uint ttlDiffer = ttlDifferDouble > 0 ? Convert.ToUInt32(ttlDifferDouble) : 0; + if (rr.TimeToLive > ttlDiffer) + { + rr.TimeToLive -= ttlDiffer; + rr.TTLDateTime = DateTime.UtcNow; + //newTTL = rr.TimeToLive; // Sometimes TTLs Are Different + } + else + { + rr.TimeToLive = 0; + rr.TTLDateTime = DateTime.UtcNow; + } + } + + if (rr.TimeToLive <= 0) dmR.IsSuccess = false; + + //Debug.WriteLine("-=-==-= " + rr.TTLDateTime); + //Debug.WriteLine("-=-==-= " + rr.TimeToLive); + } + catch (Exception) + { + rr.TimeToLive = 0; + rr.TTLDateTime = DateTime.UtcNow; + dmR.IsSuccess = false; + } + } + } + } + catch (Exception ex) + { + Debug.WriteLine("DnsMessage CreateFromCache: " + ex.Message); + return dmR; + } + } + + private static bool CanCache(DnsMessage dmR) + { + try + { + // https://datatracker.ietf.org/doc/html/rfc1035#section-7.4 + List rrs = new(); + rrs.AddRange(dmR.Answers.AnswerRecords); + rrs.AddRange(dmR.Authorities.AuthorityRecords); + rrs.AddRange(dmR.Additionals.AdditionalRecords); + + bool c1 = dmR.Header.QuestionsCount > 0; + //bool c2 = dmR.Header.TC != Enums.TC.Truncated; // DnsProxy By Adguard Doesn't Following This Rule!! + bool c3 = dmR.Header.OperationalCode != DnsEnums.OperationalCode.IQUERY; + + bool c4 = true; + foreach (Question question in dmR.Questions.QuestionRecords) + { + if (question.QNAME.Contains('*')) + { + c4 = false; + break; + } + } + + bool c5 = false; + foreach (Question question in dmR.Questions.QuestionRecords) + { + foreach (IResourceRecord rr in rrs) + { + if (question.QTYPE == rr.TYPE || question.QTYPE == DnsEnums.RRType.ANY) + { + c5 = true; + break; + } + } + if (c5) break; + } + + bool c6 = dmR.IsSuccess && dmR.Header.IsSuccess && dmR.Questions.IsSuccess; + bool c7 = dmR.Header.AnswersCount > 0 || dmR.Header.AuthoritiesCount > 0 || dmR.Header.AdditionalsCount > 0; + + bool c8 = true; + foreach (IResourceRecord rr in rrs) + { + if (rr is ARecord aRecord) + { + if (aRecord.IP.ToString().StartsWith("10.")) + { + c8 = false; + break; + } + } + } + + return c1 && c3 && c4 && c5 && c6 && c7 && c8; + } + catch (Exception) + { + return false; + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/Bootstrap.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/Bootstrap.cs new file mode 100644 index 0000000..8279fba --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/Bootstrap.cs @@ -0,0 +1,27 @@ +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public static class Bootstrap +{ + public static async Task GetDnsIpAsync(string domain, IPAddress bootstrapIP, int bootstrapPort, int timeoutSec, bool getIPv6, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) + { + string domainIP = domain; + bool isIP = NetworkTool.IsIp(domain, out _); + if (!isIP) + { + if (bootstrapIP == IPAddress.None || bootstrapIP == IPAddress.IPv6None || bootstrapPort < 1) + { + IPAddress ip = GetIP.GetIpFromSystem(domain, getIPv6, true); + if (ip != IPAddress.None && ip != IPAddress.IPv6None) domainIP = ip.ToString(); + } + else + { + string bootstrap = $"tcp://{bootstrapIP}:{bootstrapPort}"; // TCP Usually Don't Get Hijack! + IPAddress ip = await GetIP.GetIpFromDnsAddressAsync(domain, bootstrap, false, timeoutSec, getIPv6, IPAddress.None, 0, proxyScheme, proxyUser, proxyPass); + if (ip != IPAddress.None && ip != IPAddress.IPv6None) domainIP = ip.ToString(); + } + } + return domainIP; + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DNSCryptDns.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DNSCryptDns.cs new file mode 100644 index 0000000..8c6e705 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DNSCryptDns.cs @@ -0,0 +1,411 @@ +using MsmhToolsClass.ExternLibs; +using MsmhToolsClass.ProxifiedClients; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +// https://github.com/DNSCrypt/dnscrypt-protocol/blob/master/DNSCRYPT-V2-PROTOCOL.txt +public class DNSCryptDns +{ + private byte[] QueryBuffer { get; set; } = Array.Empty(); + private DnsReader Reader { get; set; } = new(); + private int TimeoutMS { get; set; } = 5; + private CancellationToken CT { get; set; } + private string? ProxyScheme { get; set; } + private string? ProxyUser { get; set; } + private string? ProxyPass { get; set; } + + private TextRecord.TXTCertificate Certificate { get; set; } = new(); + + public DNSCryptDns(byte[] queryBuffer, DnsReader reader, int timeoutMS, CancellationToken cT, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) + { + QueryBuffer = queryBuffer; + Reader = reader; + TimeoutMS = timeoutMS; + CT = cT; + ProxyScheme = proxyScheme; + ProxyUser = proxyUser; + ProxyPass = proxyPass; + } + + private async Task InitializeAsync(DnsEnums.DnsProtocol protocol) + { + bool result = false; + Task task = Task.Run(async () => + { + try + { + DnsMessage initializeQuery = DnsMessage.CreateQuery(protocol, Reader.StampReader.ProviderName, DnsEnums.RRType.TEXT, DnsEnums.CLASS.IN); + DnsMessage.TryWrite(initializeQuery, out byte[] initializeQueryBuffer); + + IPEndPoint ep = new(Reader.StampReader.IP, Reader.Port); + + bool upStreamProxyApplied = false; + TcpClient? tcpClient = null; + Socket? initSocket = null; + + try + { + if (protocol == DnsEnums.DnsProtocol.UDP) + initSocket = new(ep.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + else if (protocol == DnsEnums.DnsProtocol.TCP) + { + tcpClient = new(); + initSocket = tcpClient.Client; + + // Support Upstream Proxy + ProxifiedTcpClient proxifiedTcpClient = new(ProxyScheme, ProxyUser, ProxyPass); + var upstream = await proxifiedTcpClient.TryGetConnectedProxifiedTcpClient(ep); + if (upstream.isSuccess && upstream.proxifiedTcpClient != null) + { + tcpClient = upstream.proxifiedTcpClient; + initSocket = tcpClient.Client; + upStreamProxyApplied = true; + } + } + + if (initSocket != null) + { + initSocket.SendTimeout = TimeoutMS; + initSocket.ReceiveTimeout = TimeoutMS; + + if (!upStreamProxyApplied) await initSocket.ConnectAsync(ep, CT).ConfigureAwait(false); + await initSocket.SendAsync(initializeQueryBuffer, SocketFlags.None, CT).ConfigureAwait(false); + + byte[] initBuffer = new byte[MsmhAgnosticServer.MaxDataSize]; + int receivedLength = 0; + for (int i = 0; i < 5; i++) + { + receivedLength = await initSocket.ReceiveAsync(initBuffer, SocketFlags.None, CT).ConfigureAwait(false); + if (receivedLength > 0) break; + } + + //Debug.WriteLine("========= DnsCrypt ReceivedLength => " + receivedLength); + initBuffer = initBuffer[..receivedLength]; + + bool isCertValid = false; + uint serial = 0; + + DnsMessage verifyInitDM = DnsMessage.Read(initBuffer, protocol); + foreach (IResourceRecord answer in verifyInitDM.Answers.AnswerRecords) + { + if (answer is not TextRecord textRecord) continue; + //Debug.WriteLine("Number Of Certs: " + textRecord.TXTCertificates.Count); + foreach (var cert in textRecord.TXTCertificates) + { + if (cert.Serial > serial) + { + serial = cert.Serial; + Certificate = cert; + } + + byte[] serverSignature = Convert.FromHexString(cert.Signature); + TextRecord.TXTCertificate.TryWrite(cert, out byte[] certBuffer); + byte[] afterServerSignature = certBuffer[72..]; + byte[] providerPublicKey = Convert.FromHexString(Reader.StampReader.PublicKey); + + int verify = LibSodium.crypto_sign_verify_detached(serverSignature, afterServerSignature, afterServerSignature.Length, providerPublicKey); + if (verify == 0) isCertValid = true; + } + } + + result = isCertValid; + } + } + catch (Exception) { } + + initSocket?.Shutdown(SocketShutdown.Both); + initSocket?.Close(); + _ = Task.Run(() => initSocket?.Dispose()); + _ = Task.Run(() => tcpClient?.Dispose()); + } + catch (Exception) { } + }); + try { await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutMS), CT).ConfigureAwait(false); } catch (Exception) { } + + return result; + } + + public async Task GetResponseAsync() + { + byte[] result = Array.Empty(); + + Task task = Task.Run(async () => + { + try + { + bool isInitialized = await InitializeAsync(DnsEnums.DnsProtocol.UDP); + if (!isInitialized) isInitialized = await InitializeAsync(DnsEnums.DnsProtocol.TCP); + + //Debug.WriteLine("Is Certificate Valid: " + isInitialized); + if (isInitialized) + { + byte[] clientNonce = ByteArrayTool.GenerateRandom(12); + byte[] clientNoncePad = new byte[12]; + byte[] paddedClientNonce = clientNonce.Concat(clientNoncePad).ToArray(); + + byte[] queryPad = GenerateQueryPad(QueryBuffer.Length); + byte[] paddedQuery = QueryBuffer.Concat(queryPad).ToArray(); + + byte[] certPublicKey = Convert.FromHexString(Certificate.PublicKey); + + byte[] clientSecretKey = ByteArrayTool.GenerateRandom(32); + + bool isCreateSharedKeySuccess = CreateSharedKey(Certificate, certPublicKey, clientSecretKey, out byte[] sharedKeyBuffer); + //Debug.WriteLine("Is Create SharedKey Success: " + isCreateSharedKeySuccess); + //Debug.WriteLine("SharedKey: " + Convert.ToHexString(sharedKeyBuffer).ToLower()); + if (isCreateSharedKeySuccess) + { + bool isEncryptedQuerySuccess = Encrypt(Certificate, ref paddedQuery, ref paddedClientNonce, sharedKeyBuffer, out byte[] encryptedQuery); + //Debug.WriteLine("Is Encrypted Query Success: " + isEncryptedQuerySuccess); + //Debug.WriteLine("Encrypted Query: " + Convert.ToHexString(encryptedQuery).ToLower()); + if (isEncryptedQuerySuccess) + { + bool isCreateClientPublicKeySuccess = CreateClientPublicKey(clientSecretKey, out byte[] clientPublicKey); + //Debug.WriteLine("Is Create Client PublicKey Success: " + isCreateClientPublicKeySuccess); + //Debug.WriteLine("Client PublicKey: " + Convert.ToHexString(clientPublicKey).ToLower()); + if (isCreateClientPublicKeySuccess) + { + //Debug.WriteLine("Certificate ClientMagic: " + Certificate.ClientMagic); + List queryPacketList = new(); + queryPacketList.AddRange(Convert.FromHexString(Certificate.ClientMagic)); + queryPacketList.AddRange(clientPublicKey); + queryPacketList.AddRange(clientNonce); + queryPacketList.AddRange(encryptedQuery); + byte[] queryPacket = queryPacketList.ToArray(); + + IPEndPoint ep = new(Reader.StampReader.IP, Reader.Port); + + TcpClient tcpClient = new(); + tcpClient.SendTimeout = TimeoutMS; + tcpClient.ReceiveTimeout = TimeoutMS; + tcpClient.Client.NoDelay = true; + + // Support Upstream Proxy + ProxifiedTcpClient proxifiedTcpClient = new(ProxyScheme, ProxyUser, ProxyPass); + var upstream = await proxifiedTcpClient.TryGetConnectedProxifiedTcpClient(ep); + if (upstream.isSuccess && upstream.proxifiedTcpClient != null) tcpClient = upstream.proxifiedTcpClient; + + try + { + if (!upstream.isSuccess) + await tcpClient.Client.ConnectAsync(ep, CT).ConfigureAwait(false); + //Debug.WriteLine("Is TCP Socket Connected: " + tcpSocket.Connected); + + byte[] prefix = new byte[2]; + BinaryPrimitives.WriteUInt16BigEndian(prefix, (ushort)queryPacket.Length); + byte[] queryPacketToSend = prefix.Concat(queryPacket).ToArray(); + + await tcpClient.Client.SendAsync(queryPacketToSend, SocketFlags.None).ConfigureAwait(false); + + int lengthP = await tcpClient.Client.ReceiveAsync(prefix, SocketFlags.None).ConfigureAwait(false); + //Debug.WriteLine("=== Received Prefix Length: " + lengthP); + ushort size = BinaryPrimitives.ReadUInt16BigEndian(prefix); + + if (size > 0) + { + byte[] answerPacket = new byte[size]; + int lengthR = await tcpClient.Client.ReceiveAsync(answerPacket, SocketFlags.None).ConfigureAwait(false); + if (answerPacket[0] == 0) + lengthR = await tcpClient.Client.ReceiveAsync(answerPacket, SocketFlags.None).ConfigureAwait(false); + if (answerPacket[0] == 0) + lengthR = await tcpClient.Client.ReceiveAsync(answerPacket, SocketFlags.None).ConfigureAwait(false); + if (answerPacket[0] == 0) + lengthR = await tcpClient.Client.ReceiveAsync(answerPacket, SocketFlags.None).ConfigureAwait(false); + + //Debug.WriteLine("=== Received Answer Length: " + lengthR); + + string clientMagic = Encoding.UTF8.GetString(answerPacket[..8]); + //Debug.WriteLine("Received Client Magic: " + clientMagic); + + string constClientMagic = "r6fnvWj8"; + if (clientMagic != constClientMagic) + Debug.WriteLine("Invalid DNSCrypt Client Magic Received."); + + if (!clientNonce.SequenceEqual(answerPacket[8..20])) + Debug.WriteLine("Invalid DNSCrypt Client Nonce Received."); + + byte[] serverNonce = answerPacket[20..32]; + byte[] nonce = clientNonce.Concat(serverNonce).ToArray(); + + byte[] encryptedAnswer = answerPacket[32..]; + + bool isDecryptedAnswerSuccess = Decrypt(Certificate, ref encryptedAnswer, ref nonce, sharedKeyBuffer, out byte[] decryptedAnswer); + //Debug.WriteLine("Is Decrypted Answer Success: " + isDecryptedAnswerSuccess); + if (isDecryptedAnswerSuccess) + result = decryptedAnswer; + } + } + catch (Exception) { } + + tcpClient.Client.Shutdown(SocketShutdown.Both); + tcpClient.Client.Close(); + _ = Task.Run(() => tcpClient.Dispose()); + } + } + } + } + } + catch (Exception) { } + }); + try { await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutMS), CT).ConfigureAwait(false); } catch (Exception) { } + + return result; + } + + private static bool CreateClientPublicKey(byte[] clientSecretKey, out byte[] clientPublicKey) + { + try + { + byte[] publicKey = new byte[clientSecretKey.Length]; + int result = LibSodium.crypto_scalarmult_base(publicKey, clientSecretKey); + if (result == 0) + { + clientPublicKey = publicKey; + return true; + } + clientPublicKey = Array.Empty(); + return false; + } + catch (Exception ex) + { + Debug.WriteLine("DNSCryptDns CreateClientPublicKey: " + ex.Message); + clientPublicKey = Array.Empty(); + return false; + } + } + + private static bool CreateSharedKey(TextRecord.TXTCertificate certificate, byte[] certPublicKey, byte[] secretKey, out byte[] sharedKey) + { + try + { + byte[] sharedKeyText = new byte[32]; + if (certificate.Version == TextRecord.ESVersion.X25519_XSalsa20Poly1305) + { + int result = LibSodium.crypto_box_beforenm(sharedKeyText, certPublicKey, secretKey); + if (result == 0) + { + sharedKey = sharedKeyText; + return true; + } + } + else if (certificate.Version == TextRecord.ESVersion.X25519_XChacha20Poly1305) + { + int result = LibSodium.crypto_box_curve25519xchacha20poly1305_beforenm(sharedKeyText, certPublicKey, secretKey); + if (result == 0) + { + sharedKey = sharedKeyText; + return true; + } + } + sharedKey = Array.Empty(); + return false; + } + catch (Exception ex) + { + Debug.WriteLine("DNSCryptDns CreateSharedKey: " + ex.Message); + sharedKey = Array.Empty(); + return false; + } + } + + private static bool Encrypt(TextRecord.TXTCertificate certificate, ref byte[] paddedQuery, ref byte[] clientNonce, byte[] sharedKey, out byte[] encrypted) + { + try + { + byte[] encryptedText = new byte[paddedQuery.Length + 16]; + if (certificate.Version == TextRecord.ESVersion.X25519_XSalsa20Poly1305) + { + int result = LibSodium.crypto_box_easy_afternm(encryptedText, paddedQuery, paddedQuery.Length, clientNonce, sharedKey); + if (result == 0) + { + encrypted = encryptedText; + return true; + } + } + else if (certificate.Version == TextRecord.ESVersion.X25519_XChacha20Poly1305) + { + int result = LibSodium.crypto_box_curve25519xchacha20poly1305_easy_afternm(encryptedText, paddedQuery, paddedQuery.Length, clientNonce, sharedKey); + if (result == 0) + { + encrypted = encryptedText; + return true; + } + } + encrypted = Array.Empty(); + return false; + } + catch (Exception ex) + { + Debug.WriteLine("DNSCryptDns Encrypt: " + ex.Message); + encrypted = Array.Empty(); + return false; + } + } + + private static bool Decrypt(TextRecord.TXTCertificate certificate, ref byte[] encryptedAnswer, ref byte[] serverNonce, byte[] sharedKey, out byte[] decrypted) + { + try + { + byte[] decryptedText = new byte[encryptedAnswer.Length - 16]; + if (certificate.Version == TextRecord.ESVersion.X25519_XSalsa20Poly1305) + { + int result = LibSodium.crypto_box_open_easy_afternm(decryptedText, encryptedAnswer, encryptedAnswer.Length, serverNonce, sharedKey); + if (result == 0) + { + decrypted = decryptedText; + return true; + } + } + else if (certificate.Version == TextRecord.ESVersion.X25519_XChacha20Poly1305) + { + int result = LibSodium.crypto_box_curve25519xchacha20poly1305_open_easy_afternm(decryptedText, encryptedAnswer, encryptedAnswer.Length, serverNonce, sharedKey); + if (result == 0) + { + decrypted = decryptedText; + return true; + } + } + decrypted = Array.Empty(); + return false; + } + catch (Exception ex) + { + Debug.WriteLine("DNSCryptDns Decrypt: " + ex.Message); + decrypted = Array.Empty(); + return false; + } + } + + private static byte[] GenerateQueryPad(int queryLength) + { + byte[] pad = Array.Empty(); + + try + { + if (queryLength < 256) pad = new byte[256 - queryLength]; + + if (queryLength > 256) + { + int paddingLength = 256 + 64; + while (paddingLength < queryLength) + { + paddingLength += 64; + } + pad = new byte[paddingLength - queryLength]; + } + + pad[0] = 0x80; + } + catch (Exception ex) + { + Debug.WriteLine("DNSCryptDns GenerateQueryPad: " + ex.Message); + } + + return pad; + } +} diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DnsClient.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DnsClient.cs new file mode 100644 index 0000000..12b200a --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DnsClient.cs @@ -0,0 +1,209 @@ +using System.Diagnostics; +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class DnsClient +{ + public static byte[] ConvertQueryBufferProtocol(byte[] buffer, DnsEnums.DnsProtocol fromProtocol, DnsEnums.DnsProtocol toProtocol) + { + try + { + if (buffer.Length < 3) return buffer; + if (fromProtocol == toProtocol) return buffer; + // UDP => UDP = Same + // UDP => TCP + if (fromProtocol == DnsEnums.DnsProtocol.UDP && toProtocol == DnsEnums.DnsProtocol.TCP) + return AddTcpMessageLength(buffer); + // UDP => DoH = Same + // UDP => DoT + if (fromProtocol == DnsEnums.DnsProtocol.UDP && toProtocol == DnsEnums.DnsProtocol.DoT) + return AddTcpMessageLength(buffer); + // UDP => DNSCrypt = Same + + // TCP => UDP + else if (fromProtocol == DnsEnums.DnsProtocol.TCP && toProtocol == DnsEnums.DnsProtocol.UDP) + return RemoveTcpMessageLength(buffer); + // TCP => TCP = Same + // TCP => DoH + else if (fromProtocol == DnsEnums.DnsProtocol.TCP && toProtocol == DnsEnums.DnsProtocol.DoH) + return RemoveTcpMessageLength(buffer); + // TCP => DoT = Same + // TCP => DNSCrypt + else if (fromProtocol == DnsEnums.DnsProtocol.TCP && toProtocol == DnsEnums.DnsProtocol.DnsCrypt) + return RemoveTcpMessageLength(buffer); + + // DoH => UDP = Same + // DoH => TCP + else if (fromProtocol == DnsEnums.DnsProtocol.DoH && toProtocol == DnsEnums.DnsProtocol.TCP) + return AddTcpMessageLength(buffer); + // DoH => DoH = Same + // DoH => DoT + else if (fromProtocol == DnsEnums.DnsProtocol.DoH && toProtocol == DnsEnums.DnsProtocol.DoT) + return AddTcpMessageLength(buffer); + // DoH => DNSCrypt = Same + + // DoT => UDP + else if (fromProtocol == DnsEnums.DnsProtocol.DoT && toProtocol == DnsEnums.DnsProtocol.UDP) + return RemoveTcpMessageLength(buffer); + // DoT => TCP = Same + // DoT => DoH + else if (fromProtocol == DnsEnums.DnsProtocol.DoT && toProtocol == DnsEnums.DnsProtocol.DoH) + return RemoveTcpMessageLength(buffer); + // DoT => DoT = Same + // DoT => DNSCrypt + else if (fromProtocol == DnsEnums.DnsProtocol.DoT && toProtocol == DnsEnums.DnsProtocol.DnsCrypt) + return RemoveTcpMessageLength(buffer); + + // DNSCrypt => UDP = Same + // DNSCrypt => TCP + else if (fromProtocol == DnsEnums.DnsProtocol.DnsCrypt && toProtocol == DnsEnums.DnsProtocol.TCP) + return AddTcpMessageLength(buffer); + // DNSCrypt => DoH = Same + // DNSCrypt => DoT + else if (fromProtocol == DnsEnums.DnsProtocol.DnsCrypt && toProtocol == DnsEnums.DnsProtocol.DoT) + return AddTcpMessageLength(buffer); + // DNSCrypt => DNSCrypt = Same + else return buffer; + + static byte[] AddTcpMessageLength(byte[] buffer) + { + ushort tcpMessageLength = Convert.ToUInt16(buffer.Length); + ByteArrayTool.TryConvertUInt16ToBytes(tcpMessageLength, out byte[] tcpMessageLengthBytes); + return tcpMessageLengthBytes.Concat(buffer).ToArray(); + } + + static byte[] RemoveTcpMessageLength(byte[] buffer) => buffer[2..]; + } + catch (Exception ex) + { + Debug.WriteLine("DNS DnsClient ConvertQueryBufferProtocol: " + ex.Message); + return buffer; + } + } + + public async static Task QueryAsync(byte[] queryBuffer, DnsEnums.DnsProtocol bufferProtocol, string dnsServer, bool allowInsecure, IPAddress bootstrapIP, int bootstrapPort, int timeoutMS, CancellationToken ct, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) + { + byte[] result = Array.Empty(); + + try + { + if (queryBuffer.Length <= 0) return result; + if (string.IsNullOrEmpty(dnsServer)) return result; + + // From System + if (dnsServer.ToLower().Equals("system")) + { + // Convert Buffer ListenerProtocol To System Protocol + queryBuffer = ConvertQueryBufferProtocol(queryBuffer, bufferProtocol, SystemDns.Protocol); + + SystemDns systemDns = new(queryBuffer, timeoutMS, ct); + result = await systemDns.GetResponseAsync(); + + // Convert System Protocol To Buffer ListenerProtocol + return ConvertQueryBufferProtocol(result, SystemDns.Protocol, bufferProtocol); + } + + // From Servers + DnsReader dnsReader = new(dnsServer, null); + + // Convert Buffer ListenerProtocol To Dns Server ListenerProtocol + queryBuffer = ConvertQueryBufferProtocol(queryBuffer, bufferProtocol, dnsReader.Protocol); + + //Stopwatch sw = Stopwatch.StartNew(); + + if (dnsReader.Protocol == DnsEnums.DnsProtocol.UDP) + { + UdpPlainDns udpPlainDns = new(queryBuffer, dnsReader, timeoutMS, ct); + result = await udpPlainDns.GetResponseAsync().ConfigureAwait(false); + } + else if (dnsReader.Protocol == DnsEnums.DnsProtocol.TCP) + { + TcpPlainDns tcpPlainDns = new(queryBuffer, dnsReader, timeoutMS, ct, proxyScheme, proxyUser, proxyPass); + result = await tcpPlainDns.GetResponseAsync().ConfigureAwait(false); + } + else if (dnsReader.Protocol == DnsEnums.DnsProtocol.DoH) + { + DoHDns doHDns = new(queryBuffer, dnsReader, allowInsecure, bootstrapIP, bootstrapPort, timeoutMS, ct, proxyScheme, proxyUser, proxyPass); + result = await doHDns.GetResponseAsync().ConfigureAwait(false); + } + else if (dnsReader.Protocol == DnsEnums.DnsProtocol.DoT) + { + DoTDns doTDns = new(queryBuffer, dnsReader, allowInsecure, bootstrapIP, bootstrapPort, timeoutMS, ct, proxyScheme, proxyUser, proxyPass); + result = await doTDns.GetResponseAsync().ConfigureAwait(false); + } + else if (dnsReader.Protocol == DnsEnums.DnsProtocol.DnsCrypt) + { + DNSCryptDns dnsCryptDns = new(queryBuffer, dnsReader, timeoutMS, ct, proxyScheme, proxyUser, proxyPass); + result = await dnsCryptDns.GetResponseAsync().ConfigureAwait(false); + } + + //sw.Stop(); + //Debug.WriteLine($"========= {dnsReader.Protocol} => " + sw.ElapsedMilliseconds); + + // Convert Dns Server ListenerProtocol To Buffer ListenerProtocol + return ConvertQueryBufferProtocol(result, dnsReader.Protocol, bufferProtocol); + } + catch (Exception ex) + { + Debug.WriteLine("DNS DnsClient QueryAsync 1: " + ex.Message); + return result; + } + } + + public async static Task QueryAsync(byte[] queryBuffer, DnsEnums.DnsProtocol bufferProtocol, List dnsServers, bool allowInsecure, IPAddress bootstrapIP, int bootstrapPort, int timeoutMS, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) + { + return await Task.Run(async () => + { + byte[] result = Array.Empty(); + if (!dnsServers.Any()) return result; + + try + { + if (dnsServers.Count > 1) + { + List> tasks = new(); + + CancellationTokenSource cts = new(); + for (int n = 0; n < dnsServers.Count; n++) + { + string dns = dnsServers[n]; + Task task = QueryAsync(queryBuffer, bufferProtocol, dns, allowInsecure, bootstrapIP, bootstrapPort, timeoutMS, cts.Token, proxyScheme, proxyUser, proxyPass); + tasks.Add(task); + } + + while (true) + { + if (tasks.Count == 0) break; + Task taskResult = await Task.WhenAny(tasks).ConfigureAwait(false); + byte[] bytes = taskResult.Result; + DnsMessage dm = DnsMessage.Read(bytes, bufferProtocol); + if (dm.IsSuccess) + { + result = bytes; + break; + } + tasks.Remove(taskResult); + } + _ = Task.Run(() => cts.Cancel()); + } + else if (dnsServers.Count == 1) + { + return await QueryAsync(queryBuffer, bufferProtocol, dnsServers[0], allowInsecure, bootstrapIP, bootstrapPort, timeoutMS, CancellationToken.None, proxyScheme, proxyUser, proxyPass); + } + } + catch (Exception ex) + { + Debug.WriteLine("DNS DnsClient QueryAsync 2: " + ex.Message); + } + + return result; + }).ConfigureAwait(false); + } + + public async static Task QueryAsync(byte[] queryBuffer, DnsEnums.DnsProtocol bufferProtocol, AgnosticSettings ds) + { + return await QueryAsync(queryBuffer, bufferProtocol, ds.DNSs, ds.AllowInsecure, ds.BootstrapIpAddress, ds.BootstrapPort, ds.DnsTimeoutSec * 1000, ds.UpstreamProxyScheme, ds.UpstreamProxyUser, ds.UpstreamProxyPass); + } + +} diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DoHDns.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DoHDns.cs new file mode 100644 index 0000000..b85c7b2 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DoHDns.cs @@ -0,0 +1,78 @@ +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class DoHDns +{ + private byte[] QueryBuffer { get; set; } = Array.Empty(); + private DnsReader Reader { get; set; } = new(); + private bool AllowInsecure { get; set; } + private IPAddress BootstrapIP { get; set; } + private int BootstrapPort { get; set; } + private int TimeoutMS { get; set; } = 5; + private CancellationToken CT { get; set; } + private string? ProxyScheme { get; set; } + private string? ProxyUser { get; set; } + private string? ProxyPass { get; set; } + + public DoHDns(byte[] queryBuffer, DnsReader reader, bool allowInsecure, IPAddress bootstrapIP, int bootstrapPort, int timeoutMS, CancellationToken ct, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) + { + QueryBuffer = queryBuffer; + Reader = reader; + AllowInsecure = allowInsecure; + BootstrapIP = bootstrapIP; + BootstrapPort = bootstrapPort; + TimeoutMS = timeoutMS; + CT = ct; + ProxyScheme = proxyScheme; + ProxyUser = proxyUser; + ProxyPass = proxyPass; + } + + public async Task GetResponseAsync() + { + byte[] result = Array.Empty(); + + Task task = Task.Run(async () => + { + try + { + string dnsServerIP = await Bootstrap.GetDnsIpAsync(Reader.Host, BootstrapIP, BootstrapPort, 3, false, ProxyScheme, ProxyUser, ProxyPass); + if (dnsServerIP.Equals(Reader.Host)) + dnsServerIP = await Bootstrap.GetDnsIpAsync(Reader.Host, BootstrapIP, BootstrapPort, 3, true, ProxyScheme, ProxyUser, ProxyPass); + + UriBuilder uriBuilder = new() + { + Scheme = Reader.Scheme, + Host = dnsServerIP, + Port = Reader.Port, + Path = Reader.Path + }; + + Uri uri = uriBuilder.Uri; + + HttpRequest hr = new() + { + CT = CT, + URI = uri, + Method = HttpMethod.Post, + ContentType = MsmhAgnosticServer.DnsMessageContentType, + DataToSend = QueryBuffer, + TimeoutMS = TimeoutMS, + AllowInsecure = AllowInsecure, + ProxyScheme = ProxyScheme, + ProxyUser = ProxyUser, + ProxyPass = ProxyPass, + }; + hr.Headers.Add("host", Reader.Host); // In Case Of Using Bootstrap + + HttpRequestResponse hrr = await HttpRequest.SendAsync(hr).ConfigureAwait(false); + result = hrr.Data; + } + catch (Exception) { } + }); + try { await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutMS), CT).ConfigureAwait(false); } catch (Exception) { } + + return result; + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DoQDns.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DoQDns.cs new file mode 100644 index 0000000..0d629c8 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DoQDns.cs @@ -0,0 +1,52 @@ +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class DoQDns +{ + private byte[] QueryBuffer { get; set; } = Array.Empty(); + private DnsReader Reader { get; set; } = new(); + private IPAddress BootstrapIP { get; set; } + private int BootstrapPort { get; set; } + private int TimeoutSec { get; set; } = 5; + private CancellationToken CT { get; set; } + private string? ProxyScheme { get; set; } + private string? ProxyUser { get; set; } + private string? ProxyPass { get; set; } + + public DoQDns(byte[] queryBuffer, DnsReader reader, IPAddress bootstrapIP, int bootstrapPort, int timeoutSec, CancellationToken cT, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) + { + QueryBuffer = queryBuffer; + Reader = reader; + BootstrapIP = bootstrapIP; + BootstrapPort = bootstrapPort; + TimeoutSec = timeoutSec; + CT = cT; + ProxyScheme = proxyScheme; + ProxyUser = proxyUser; + ProxyPass = proxyPass; + } + + public async Task GetResponseAsync() + { + byte[] result = Array.Empty(); + + Task task = Task.Run(() => + { + try + { + // Reserved For .NET 8 + //QuicClientConnectionOptions options = new(); + //options.RemoteEndPoint + + //QuicConnection quic = await QuicConnection.ConnectAsync(); + + + } + catch (Exception) { } + }); + try { await task.WaitAsync(TimeSpan.FromSeconds(TimeoutSec), CT).ConfigureAwait(false); } catch (Exception) { } + + return result; + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DoTDns.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DoTDns.cs new file mode 100644 index 0000000..5e05c0f --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/DoTDns.cs @@ -0,0 +1,106 @@ +using MsmhToolsClass.ProxifiedClients; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class DoTDns +{ + private byte[] QueryBuffer { get; set; } = Array.Empty(); + private DnsReader Reader { get; set; } = new(); + private bool AllowInsecure { get; set; } + private IPAddress BootstrapIP { get; set; } + private int BootstrapPort { get; set; } + private int TimeoutMS { get; set; } = 5; + private CancellationToken CT { get; set; } + private string? ProxyScheme { get; set; } + private string? ProxyUser { get; set; } + private string? ProxyPass { get; set; } + + public DoTDns(byte[] queryBuffer, DnsReader reader, bool allowInsecure, IPAddress bootstrapIP, int bootstrapPort, int timeoutMS, CancellationToken ct, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) + { + QueryBuffer = queryBuffer; + Reader = reader; + AllowInsecure = allowInsecure; + BootstrapIP = bootstrapIP; + BootstrapPort = bootstrapPort; + TimeoutMS = timeoutMS; + CT = ct; + ProxyScheme = proxyScheme; + ProxyUser = proxyUser; + ProxyPass = proxyPass; + } + + public async Task GetResponseAsync() + { + byte[] result = Array.Empty(); + + Task task = Task.Run(async () => + { + TcpClient? tcpClient = null; + SslStream? sslStream = null; + + try + { + string dnsServerIP = await Bootstrap.GetDnsIpAsync(Reader.Host, BootstrapIP, BootstrapPort, 3, false, ProxyScheme, ProxyUser, ProxyPass); + if (dnsServerIP.Equals(Reader.Host)) + dnsServerIP = await Bootstrap.GetDnsIpAsync(Reader.Host, BootstrapIP, BootstrapPort, 3, true, ProxyScheme, ProxyUser, ProxyPass); + + tcpClient = new(); + tcpClient.SendTimeout = TimeoutMS; + tcpClient.ReceiveTimeout = TimeoutMS; + tcpClient.Client.NoDelay = true; + + // Support Upstream Proxy + ProxifiedTcpClient proxifiedTcpClient = new(ProxyScheme, ProxyUser, ProxyPass); + var upstream = await proxifiedTcpClient.TryGetConnectedProxifiedTcpClient(dnsServerIP, Reader.Port); + if (upstream.isSuccess && upstream.proxifiedTcpClient != null) tcpClient = upstream.proxifiedTcpClient; + + if (!upstream.isSuccess) + await tcpClient.Client.ConnectAsync(dnsServerIP, Reader.Port, CT).ConfigureAwait(false); + + SslClientAuthenticationOptions optionsClient = new(); + optionsClient.TargetHost = Reader.Host; + optionsClient.EnabledSslProtocols = MsmhAgnosticServer.SSL_Protocols; + if (AllowInsecure) + { + optionsClient.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; + optionsClient.RemoteCertificateValidationCallback = MsmhAgnosticServer.Callback; + } + + sslStream = new(tcpClient.GetStream(), false, MsmhAgnosticServer.Callback, null); + await sslStream.AuthenticateAsClientAsync(optionsClient, CT).ConfigureAwait(false); + + if (sslStream.IsAuthenticated && sslStream.CanWrite) + { + await sslStream.WriteAsync(QueryBuffer, CT).ConfigureAwait(false); + + if (sslStream.CanRead) + { + byte[] buffer = new byte[MsmhAgnosticServer.MaxDataSize]; + int receivedLength = await sslStream.ReadAsync(buffer, CT).ConfigureAwait(false); + + if (receivedLength > 0) result = buffer[..receivedLength]; + } + } + } + catch (Exception) { } + finally + { + try + { + tcpClient?.Client.Shutdown(SocketShutdown.Both); + tcpClient?.Client.Close(); + tcpClient?.Dispose(); + sslStream?.Dispose(); + } + catch (Exception) { } + } + }); + try { await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutMS), CT).ConfigureAwait(false); } catch (Exception) { } + + return result; + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/SystemDns.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/SystemDns.cs new file mode 100644 index 0000000..9172152 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/SystemDns.cs @@ -0,0 +1,55 @@ +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class SystemDns +{ + private byte[] QueryBuffer { get; set; } = Array.Empty(); + private int TimeoutMS { get; set; } = 5; + private CancellationToken CT { get; set; } + public static DnsEnums.DnsProtocol Protocol { get; private set; } = DnsEnums.DnsProtocol.UDP; + + public SystemDns(byte[] queryBuffer, int timeoutMS, CancellationToken cT) + { + QueryBuffer = queryBuffer; + TimeoutMS = timeoutMS; + CT = cT; + } + + public async Task GetResponseAsync() + { + byte[] result = Array.Empty(); + + Task task = Task.Run(() => + { + try + { + DnsMessage dmQ = DnsMessage.Read(QueryBuffer, Protocol); + if (dmQ.IsSuccess && dmQ.Header.QuestionsCount > 0) + { + string host = dmQ.Questions.QuestionRecords[0].QNAME; + DnsEnums.RRType typeQ = dmQ.Questions.QuestionRecords[0].QTYPE; + bool getIpv6 = typeQ == DnsEnums.RRType.AAAA; + List ips = GetIP.GetIpsFromSystem(host, getIpv6); + + if (ips.Count != 0) + { + DnsMessage dmR = DnsMessage.CreateResponse(dmQ, (ushort)ips.Count, 0, 0); + if (dmR.IsSuccess) + { + if (getIpv6) foreach (IPAddress ip in ips) dmR.Answers.AnswerRecords.Add(new AaaaRecord(host, 30, ip)); + else foreach (IPAddress ip in ips) dmR.Answers.AnswerRecords.Add(new ARecord(host, 30, ip)); + + bool isWriteSuccess = DnsMessage.TryWrite(dmR, out byte[] aBuffer); + if (isWriteSuccess) result = aBuffer; + } + } + } + } + catch (Exception) { } + }); + try { await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutMS), CT).ConfigureAwait(false); } catch (Exception) { } + + return result; + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/TcpPlainDns.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/TcpPlainDns.cs new file mode 100644 index 0000000..b24b3ea --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/TcpPlainDns.cs @@ -0,0 +1,79 @@ +using System.Net.Sockets; +using System.Net; +using MsmhToolsClass.ProxifiedClients; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class TcpPlainDns +{ + private byte[] QueryBuffer { get; set; } = Array.Empty(); + private DnsReader Reader { get; set; } = new(); + private int TimeoutMS { get; set; } = 5; + private CancellationToken CT { get; set; } + private string? ProxyScheme { get; set; } + private string? ProxyUser { get; set; } + private string? ProxyPass { get; set; } + + public TcpPlainDns(byte[] queryBuffer, DnsReader reader, int timeoutMS, CancellationToken cT, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) + { + QueryBuffer = queryBuffer; + Reader = reader; + TimeoutMS = timeoutMS; + CT = cT; + ProxyScheme = proxyScheme; + ProxyUser = proxyUser; + ProxyPass = proxyPass; + } + + public async Task GetResponseAsync() + { + byte[] result = Array.Empty(); + + Task task = Task.Run(async () => + { + try + { + IPEndPoint ep = new(IPAddress.Parse(Reader.Host), Reader.Port); + + TcpClient tcpClient = new(); + tcpClient.SendTimeout = TimeoutMS; + tcpClient.ReceiveTimeout = TimeoutMS; + tcpClient.Client.NoDelay = true; + + // Support Upstream Proxy + ProxifiedTcpClient proxifiedTcpClient = new(ProxyScheme, ProxyUser, ProxyPass); + var upstream = await proxifiedTcpClient.TryGetConnectedProxifiedTcpClient(ep); + if (upstream.isSuccess && upstream.proxifiedTcpClient != null) tcpClient = upstream.proxifiedTcpClient; + + try + { + if (!upstream.isSuccess) + await tcpClient.Client.ConnectAsync(ep, CT).ConfigureAwait(false); + + await tcpClient.Client.SendAsync(QueryBuffer, SocketFlags.None, CT).ConfigureAwait(false); + + byte[] buffer = new byte[MsmhAgnosticServer.MaxDataSize]; + int receivedLength = await tcpClient.Client.ReceiveAsync(buffer, SocketFlags.None, CT).ConfigureAwait(false); + + ByteArrayTool.TryConvertBytesToUInt16(buffer[0..2], out ushort answerLength); + + while (receivedLength < answerLength) + { + receivedLength += await tcpClient.Client.ReceiveAsync(buffer.AsMemory()[receivedLength..], SocketFlags.None, CT).ConfigureAwait(false); + } + + if (receivedLength > 0) result = buffer[..receivedLength]; + } + catch (Exception) { } + + tcpClient.Client.Shutdown(SocketShutdown.Both); + tcpClient.Client.Close(); + tcpClient.Dispose(); + } + catch (Exception) { } + }); + try { await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutMS), CT).ConfigureAwait(false); } catch (Exception) { } + + return result; + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/UdpPlainDns.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/UdpPlainDns.cs new file mode 100644 index 0000000..16e8803 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsClient/UdpPlainDns.cs @@ -0,0 +1,58 @@ +using System.Net.Sockets; +using System.Net; +using System.Diagnostics; +using System.Formats.Asn1; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class UdpPlainDns +{ + private byte[] QueryBuffer { get; set; } = Array.Empty(); + private DnsReader Reader { get; set; } = new(); + private int TimeoutMS { get; set; } = 5; + private CancellationToken CT { get; set; } + + public UdpPlainDns(byte[] queryBuffer, DnsReader reader, int timeoutMS, CancellationToken cT) + { + QueryBuffer = queryBuffer; + Reader = reader; + TimeoutMS = timeoutMS; + CT = cT; + } + + public async Task GetResponseAsync() + { + byte[] result = Array.Empty(); + + Task task = Task.Run(async () => + { + try + { + IPEndPoint ep = new(IPAddress.Parse(Reader.Host), Reader.Port); + + Socket socket = new(ep.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + socket.SendTimeout = TimeoutMS; + socket.ReceiveTimeout = TimeoutMS; + + try + { + await socket.ConnectAsync(ep, CT).ConfigureAwait(false); + await socket.SendAsync(QueryBuffer, SocketFlags.None, CT).ConfigureAwait(false); + byte[] buffer = new byte[MsmhAgnosticServer.MaxDataSize]; + int receivedLength = await socket.ReceiveAsync(buffer, SocketFlags.None, CT).ConfigureAwait(false); + + if (receivedLength > 0) result = buffer[..receivedLength]; + } + catch (Exception) { } + + socket.Shutdown(SocketShutdown.Both); + socket.Close(); + socket.Dispose(); + } + catch (Exception) { } + }); + try { await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutMS), CT).ConfigureAwait(false); } catch (Exception) { } + + return result; + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/DnsEnums.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/DnsEnums.cs new file mode 100644 index 0000000..6b82d38 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/DnsEnums.cs @@ -0,0 +1,423 @@ +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class DnsEnums +{ + public enum DnsProtocol + { + System, + UDP, + TCP, + DnsCrypt, + DoH, + DoT, + DoQ, + ObliviousDohTarget, + AnonymizedDNSCryptRelay, + ObliviousDohRelay, + Unknown + } + + public readonly struct DnsProtocolName + { + public static readonly string UDP = "UDP Plain DNS"; + public static readonly string TCP = "TCP Plain DNS"; + public static readonly string DnsCrypt = "DNSCrypt"; + public static readonly string DoH = "DNS-Over-HTTPS"; + public static readonly string DoT = "DNS-Over-TLS"; + public static readonly string DoQ = "DNS-Over-Quic"; + public static readonly string ObliviousDohTarget = "Oblivious DoH Target"; + public static readonly string AnonymizedDNSCryptRelay = "Anonymized DNSCrypt Relay"; + public static readonly string ObliviousDohRelay = "Oblivious DoH Relay"; + public static readonly string Unknown = "Unknown"; + } + + public enum QR : ushort // 1 Bit + { + Query = 0, + Response = 1 + } + + /// + /// DNS Operation Codes. See https://www.iana.org/assignments/dns-parameters/dns-parameters.xml#dns-parameters-5 + /// + public enum OperationalCode : ushort // 4 Bits + { + /// + /// Query + /// + QUERY = 0000, // 0 + /// + /// IQuery (Inverse Query, OBSOLETE) + /// + IQUERY = 0001, // 1 + /// + /// Status + /// + STATUS = 0010, // 2 + /// + /// Notify + /// + NOTIFY = 0100, // 4 + /// + /// Update + /// + UPDATE = 0101, // 5 + /// + /// DNS Stateful Operations (DSO) + /// + DSO = 0110 // 6 + // 3 and 7-15 Unassigned + } + + /// + /// Authoritative Answer. This flag is only valid for responses. + /// + public enum AA : ushort // 1 Bit + { + NonAuthoritive = 0, + Authoritive = 1 + } + + public enum TC : ushort // 1 Bit + { + NotTruncated = 0, + Truncated = 1 + } + + public enum RD : ushort // 1 Bit + { + RecursionIsNotDesired = 0, + RecursionIsDesired = 1 + } + + public enum RA : ushort // 1 Bit + { + RecursionIsNotAvailable = 0, + RecursionIsAvailable = 1 + } + + public enum Z : ushort // 2 Bits + { + Reserved = 00 // Reserved for future use. Must be zero in all queries and responses + } + + public enum AnswerAuthenticated : ushort // 1 Bit + { + False = 0, + True = 1 + } + + public enum NonAuthenticatedData : ushort // 1 Bit + { + False = 0, + True = 1 + } + + public enum ResponseCode : ushort // 4 Bits + { + NoError = 0000, // 0 + FormatError = 0001, // 1 + ServerFailure = 0010, // 2 + NameError = 0011, // 3 + NotImplemented = 0100, // 4 + Refused = 0101 // 5 + // 6-15 Reserved for future use + } + + /// + /// DNS Resource Record Type. See https://www.iana.org/assignments/dns-parameters/dns-parameters.xml#dns-parameters-4 + /// + public enum RRType : ushort + { + /// + /// A host address + /// + A = 1, + /// + /// An authoritative name server + /// + NS = 2, + /// + /// A mail destination (OBSOLETE - use MX) + /// + MD = 3, + /// + /// A mail forwarder (OBSOLETE - use MX) + /// + MF = 4, + /// + /// The canonical name for an alias + /// + CNAME = 5, + /// + /// Marks the start of a zone of authority + /// + SOA = 6, + /// + /// A mailbox domain name (EXPERIMENTAL) + /// + MB = 7, + /// + /// A mail group member (EXPERIMENTAL) + /// + MG = 8, + /// + /// A mail rename domain name (EXPERIMENTAL) + /// + MR = 9, + /// + /// A null RR (EXPERIMENTAL) + /// + NULL = 10, + /// + /// A well known service description + /// + WKS = 11, + /// + /// A domain name pointer + /// + PTR = 12, + /// + /// Host information + /// + HINFO = 13, + /// + /// Mailbox or mail list information + /// + MINFO = 14, + /// + /// Mail exchange + /// + MX = 15, + /// + /// Text strings + /// + TEXT = 16, + /// + /// For Responsible Person + /// + RP = 17, + /// + /// For AFS Data Base location + /// + AFSDB = 18, + /// + /// For X.25 PSDN address + /// + X25 = 19, + /// + /// For ISDN address + /// + ISDN = 20, + /// + /// For Route Through + /// + RT = 21, + /// + /// For NSAP address, NSAP style A record + /// + NSAP = 22, + /// + /// For domain name pointer, NSAP style + /// + NSAPPTR = 23, + /// + /// For security signature + /// + SIG = 24, + /// + /// For security key + /// + KEY = 25, + /// + /// X.400 mail mapping information + /// + PX = 26, + /// + /// Geographical Position + /// + GPOS = 27, + /// + /// IP6 Address + /// + AAAA = 28, + /// + /// Location Information + /// + LOC = 29, + /// + /// Next Domain (OBSOLETE) + /// + NXT = 30, + /// + /// Endpoint Identifier + /// + EID = 31, + /// + /// Nimrod Locator + /// + NIMLOC = 32, + /// + /// Server Selection + /// + SRV = 33, + /// + /// ATM Address + /// + ATMA = 34, + /// + /// Naming Authority Pointer + /// + NAPTR = 35, + /// + /// Key Exchanger + /// + KX = 36, + CERT = 37, + /// + /// A6 (OBSOLETE - use AAAA) + /// + A6 = 38, + DNAME = 39, + SINK = 40, + OPT = 41, + APL = 42, + /// + /// Delegation Signer + /// + DS = 43, + /// + /// SSH Key Fingerprint + /// + SSHFP = 44, + IPSECKEY = 45, + RRSIG = 46, + NSEC = 47, + DNSKEY = 48, + DHCID = 49, + NSEC3 = 50, + NSEC3PARAM = 51, + TLSA = 52, + /// + /// S/MIME cert association + /// + SMIMEA = 53, + /// + /// Host Identity ListenerProtocol + /// + HIP = 55, + NINFO = 56, + RKEY = 57, + /// + /// Trust Anchor LINK + /// + TALINK = 58, + /// + /// Child DS + /// + CDS = 59, + /// + /// DNSKEY(s) the Child wants reflected in DS + /// + CDNSKEY = 60, + /// + /// OpenPGP Key + /// + OPENPGPKEY = 61, + /// + /// Child-To-Parent Synchronization + /// + CSYNC = 62, + /// + /// Message digest for DNS zone + /// + ZONEMD = 63, + /// + /// Service Binding + /// + SVCB = 64, + /// + /// HTTPS Binding + /// + HTTPS = 65, + SPF = 99, + UINFO = 100, + UID = 101, + GID = 102, + UNSPEC = 103, + NID = 104, + L32 = 105, + L64 = 106, + LP = 107, + /// + /// An EUI-48 address + /// + EUI48 = 108, + /// + /// An EUI-64 address + /// + EUI64 = 109, + /// + /// Transaction Key + /// + TKEY = 249, + /// + /// Transaction Signature + /// + TSIG = 250, + /// + /// Incremental transfer + /// + IXFR = 251, + /// + /// Transfer of an entire zone + /// + AXFR = 252, + /// + /// Mailbox-related RRs (MB, MG or MR) + /// + MAILB = 253, + /// + /// Mail agent RRs (OBSOLETE - see MX) + /// + MAILA = 254, + /// + /// A request for some or all records the server has available + /// + ANY = 255, + URI = 256, + /// + /// Certification Authority Restriction + /// + CAA = 257, + /// + /// Application Visibility and Control + /// + AVC = 258, + /// + /// Digital Object Architecture + /// + DOA = 259, + /// + /// Automatic Multicast Tunneling Relay + /// + AMTRELAY = 260, + /// + /// DNSSEC Trust Authorities + /// + TA = 32768, + /// + /// DNSSEC Lookaside Validation (OBSOLETE) + /// + DLV = 32769 + } + + public enum CLASS : ushort + { + IN = 1, // The Internet system + CS = 2, // The CSNET class (Obsolete - used only for examples in some obsolete RFCs) + CH = 3, // The Chaos system + HS = 4, // Hesiod [Dyer 87] + } + +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/DnsMessage.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/DnsMessage.cs new file mode 100644 index 0000000..ddaf7f0 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/DnsMessage.cs @@ -0,0 +1,257 @@ +using System.Diagnostics; +using System.Text; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class DnsMessage +{ + public bool IsSuccess { get; internal set; } = false; + public byte[] DnsMessageBuffer { get; internal set; } = Array.Empty(); + public DnsEnums.DnsProtocol DnsProtocol { get; set; } + public ushort TcpMessageLength { get; internal set; } + public Header Header { get; set; } = new(); + public Questions Questions { get; set; } = new(); + public Answers Answers { get; set; } = new(); + public Authorities Authorities { get; set; } = new(); + public Additionals Additionals { get; set; } = new(); + internal List> LabelPositions { get; set; } = new(); + private static readonly string DnsMessageContentType = "application/dns-message"; + + public DnsMessage() { } + + /// + /// Clone With An Empty And New LabelPositions + /// + public DnsMessage(DnsMessage dm) + { + IsSuccess = dm.IsSuccess; + DnsMessageBuffer = dm.DnsMessageBuffer; + DnsProtocol = dm.DnsProtocol; + TcpMessageLength = dm.TcpMessageLength; + Header = dm.Header; + Questions = dm.Questions; + Answers = dm.Answers; + Authorities = dm.Authorities; + Additionals = dm.Additionals; + LabelPositions = new(); + } + + public override string ToString() + { + string result = "DNS Message:\n"; + result += $"{nameof(IsSuccess)}: {IsSuccess}\n"; + result += $"{nameof(DnsProtocol)}: {DnsProtocol}\n"; + result += $"{nameof(TcpMessageLength)}: {TcpMessageLength}"; + result += "\n\n"; + result += Header.ToString(); + result += "\n\n"; + if (Header.QuestionsCount > 0) + { + result += Questions.ToString(); + result += "\n\n"; + } + if (Header.AnswersCount > 0) + { + result += Answers.ToString(); + result += "\n\n"; + } + if (Header.AuthoritiesCount > 0) + { + result += Authorities.ToString(); + result += "\n\n"; + } + if (Header.AdditionalsCount > 0) + { + result += Additionals.ToString(); + } + return result; + } + + public static DnsMessage Read(byte[] buffer, DnsEnums.DnsProtocol dnsProtocol) + { + DnsMessage dnsMessage = new(); + if (buffer.Length <= 3) return dnsMessage; + + try + { + dnsMessage.DnsMessageBuffer = buffer; + dnsMessage.DnsProtocol = dnsProtocol; + + int pos = 0; + // https://datatracker.ietf.org/doc/html/rfc1035#section-4.2.2 + if (dnsProtocol == DnsEnums.DnsProtocol.TCP) + { + bool tcpMessageLengthBool = ByteArrayTool.TryConvertBytesToUInt16(buffer[0..2], out ushort tcpMessageLength); + if (!tcpMessageLengthBool) return dnsMessage; + dnsMessage.TcpMessageLength = tcpMessageLength; + buffer = buffer[2..]; // The Start Of Message Does Not Include TCP Message Length + dnsMessage.DnsMessageBuffer = buffer; + } + + dnsMessage.Header = Header.Read(buffer, ref pos); + if (!dnsMessage.Header.IsSuccess) return dnsMessage; + + if (dnsMessage.Header.QuestionsCount > 0) + { + dnsMessage.Questions = Questions.Read(buffer, ref pos, dnsMessage); + if (!dnsMessage.Questions.IsSuccess) return dnsMessage; + + if (dnsMessage.Header.AnswersCount > 0) + dnsMessage.Answers = Answers.Read(buffer, ref pos, dnsMessage); + + if (dnsMessage.Header.AuthoritiesCount > 0) + dnsMessage.Authorities = Authorities.Read(buffer, ref pos, dnsMessage); + + if (dnsMessage.Header.AdditionalsCount > 0) + dnsMessage.Additionals = Additionals.Read(buffer, ref pos, dnsMessage); + + dnsMessage.IsSuccess = dnsMessage.Header.IsSuccess && dnsMessage.Questions.IsSuccess; + } + } + catch (Exception ex) + { + Debug.WriteLine("DnsMessage Read: " + ex.Message); + } + + return dnsMessage; + } + + public static bool TryWrite(DnsMessage dnsMessage, out byte[] buffer) + { + dnsMessage = new DnsMessage(dnsMessage); // It's Important To Clear LabelPositions On New Write + + buffer = Array.Empty(); + + try + { + int pos = 0; + bool headerBool = Header.TryWrite(dnsMessage, ref pos, out byte[] header); + if (!headerBool) return false; + + bool questionBool = Questions.TryWrite(dnsMessage, ref pos, out byte[] questions); + if (!questionBool) return false; + + bool answersBool = Answers.TryWrite(dnsMessage, ref pos, out byte[] answers); + if (!answersBool) return false; + + bool authoritiesBool = Authorities.TryWrite(dnsMessage, ref pos, out byte[] authorities); + if (!authoritiesBool) return false; + + bool additionalsBool = Additionals.TryWrite(dnsMessage, ref pos, out byte[] additionals); + if (!additionalsBool) return false; + + List bufferList = new(); + if (dnsMessage.DnsProtocol == DnsEnums.DnsProtocol.TCP) + { + dnsMessage.TcpMessageLength = Convert.ToUInt16(header.Length + questions.Length + answers.Length + authorities.Length + additionals.Length); + bool tcpMessageLengthBool = ByteArrayTool.TryConvertUInt16ToBytes(dnsMessage.TcpMessageLength, out byte[] tcpMessageLength); + if (!tcpMessageLengthBool) return false; + bufferList.AddRange(tcpMessageLength); + } + + bufferList.AddRange(header); + bufferList.AddRange(questions); + bufferList.AddRange(answers); + bufferList.AddRange(authorities); + bufferList.AddRange(additionals); + buffer = bufferList.ToArray(); + return true; + } + catch (Exception ex) + { + Debug.WriteLine("DNS TryWrite DnsMessage: " + ex.Message); + return false; + } + } + + public static DnsMessage CreateQuery(DnsEnums.DnsProtocol dnsProtocol, string domain, DnsEnums.RRType rrType, DnsEnums.CLASS qClass) + { + DnsMessage dm = new() + { + DnsProtocol = dnsProtocol, + Header = new() + { + ID = Header.GenerateId(), + QR = DnsEnums.QR.Query, + OperationalCode = DnsEnums.OperationalCode.QUERY, + AA = DnsEnums.AA.NonAuthoritive, + TC = DnsEnums.TC.NotTruncated, + RD = DnsEnums.RD.RecursionIsDesired, + RA = DnsEnums.RA.RecursionIsNotAvailable, + AnswerAuthenticated = DnsEnums.AnswerAuthenticated.False, + NonAuthenticatedData = DnsEnums.NonAuthenticatedData.False, + ResponseCode = DnsEnums.ResponseCode.NoError, + QuestionsCount = 1, + AnswersCount = 0, + AuthoritiesCount = 0, + AdditionalsCount = 0 + }, + Questions = new() + { + QuestionRecords = new() + { + new Question() + { + QNAME = domain, + QTYPE = rrType, + QCLASS = qClass + } + } + } + }; + + return dm; + } + + public static DnsMessage CreateResponse(DnsMessage dnsMessage, ushort answersCount, ushort authoritiesCount, ushort additionalsCount) + { + dnsMessage.Header.QR = DnsEnums.QR.Response; + dnsMessage.Header.ResponseCode = DnsEnums.ResponseCode.NoError; + dnsMessage.Header.AnswersCount = answersCount; + dnsMessage.Header.AuthoritiesCount = authoritiesCount; + dnsMessage.Header.AdditionalsCount = additionalsCount; + return dnsMessage; + } + + public static DnsMessage CreateFailedResponse(DnsMessage dnsMessage) + { + dnsMessage.Header.QR = DnsEnums.QR.Response; + dnsMessage.Header.ResponseCode = DnsEnums.ResponseCode.ServerFailure; + dnsMessage.Header.AnswersCount = 0; + dnsMessage.Header.AuthoritiesCount = 0; + dnsMessage.Header.AdditionalsCount = 0; + return dnsMessage; + } + + public static bool TryWriteDoHResponse(byte[] aBuffer, out byte[] result) + { + // https://datatracker.ietf.org/doc/html/rfc8484#section-4.2.2 + try + { + List bufferList = new(); + string statusLine = $"HTTP/1.1 200 OK\r\n"; + bufferList.AddRange(Encoding.UTF8.GetBytes(statusLine)); + + string contentTypeLine = "Content-Type: " + DnsMessageContentType + "\r\n"; + bufferList.AddRange(Encoding.UTF8.GetBytes(contentTypeLine)); + + string contentLenLine = "Content-Length: " + aBuffer.Length + "\r\n"; + bufferList.AddRange(Encoding.UTF8.GetBytes(contentLenLine)); + + bufferList.AddRange(Encoding.UTF8.GetBytes("\r\n")); + + // Merge Headers and Body + bufferList.AddRange(aBuffer); + + result = bufferList.ToArray(); + return true; + } + catch (Exception ex) + { + Debug.WriteLine("DnsMessage TryWriteDoHPostResponse: " + ex.Message); + result = Array.Empty(); + return false; + } + } + +} diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Header.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Header.cs new file mode 100644 index 0000000..d06b5d5 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Header.cs @@ -0,0 +1,191 @@ +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class Header +{ + public bool IsSuccess { get; private set; } = false; + public ushort ID { get; set; } // 2 Bytes + public DnsEnums.QR QR { get; set; } // 1 Bit / Query = 0 / Response = 1 + public DnsEnums.OperationalCode OperationalCode { get; set; } // 4 Bits / Query Type, 0 = Standard, 1 = Inverse, 2 = Server Status Request, 3-15 = Reserved For Future Use + public DnsEnums.AA AA { get; set; } // 1 Bit / Authoritative Answer + public DnsEnums.TC TC { get; set; } // 1 Bit / Truncate, 0 = Not Truncated, 1 = Truncated + public DnsEnums.RD RD { get; set; } // 1 Bit / Recursion Is Desired, 0 = False, 1 = True (Must Be True For a Query) + public DnsEnums.RA RA { get; set; } // 1 Bit / Recursion Is Available, 0 = False, 1 = True + public DnsEnums.Z Z { get; set; } = DnsEnums.Z.Reserved; // 2 Bits / Reserved for future use. Must be zero in all queries and responses + public DnsEnums.AnswerAuthenticated AnswerAuthenticated { get; set; } // 1 Bit + public DnsEnums.NonAuthenticatedData NonAuthenticatedData { get; set; } // 1 Bit + public DnsEnums.ResponseCode ResponseCode { get; set; } // 4 Bits + public ushort QuestionsCount { get; set; } // 2 Bytes / Number Of Questions + public ushort AnswersCount { get; set; } // 2 Bytes / Number Of Answers + public ushort AuthoritiesCount { get; set; } // 2 Bytes / Number Of Authorities + public ushort AdditionalsCount { get; set; } // 2 Bytes / Number Of Additional Records + + public override string ToString() + { + string result = "DNS Header:\n"; + result += $"{nameof(IsSuccess)}: {IsSuccess}\n"; + result += $"{nameof(ID)}: {ID}\n"; + result += $"{nameof(QR)}: {QR}\n"; + result += $"{nameof(OperationalCode)}: {OperationalCode}\n"; + result += $"{nameof(AA)}: {AA}\n"; + result += $"{nameof(TC)}: {TC}\n"; + result += $"{nameof(RD)}: {RD}\n"; + result += $"{nameof(RA)}: {RA}\n"; + result += $"{nameof(AnswerAuthenticated)}: {AnswerAuthenticated}\n"; + result += $"{nameof(NonAuthenticatedData)}: {NonAuthenticatedData}\n"; + result += $"{nameof(ResponseCode)}: {ResponseCode}\n"; + result += $"{nameof(QuestionsCount)}: {QuestionsCount}\n"; + result += $"{nameof(AnswersCount)}: {AnswersCount}\n"; + result += $"{nameof(AuthoritiesCount)}: {AuthoritiesCount}\n"; + result += $"{nameof(AdditionalsCount)}: {AdditionalsCount}"; + return result; + } + + internal static ushort GenerateId() + { + try + { + bool idBool = ByteArrayTool.TryConvertBytesToUInt16(Guid.NewGuid().ToByteArray(), out ushort id); + return idBool ? id : Convert.ToUInt16(12345); + } + catch (Exception ex) + { + Debug.WriteLine("DNS Header GenerateId: " + ex.Message); + return 12345; + } + } + + public static Header Read(byte[] buffer, ref int pos) + { + try + { + if (buffer.Length <= pos + MsmhAgnosticServer.DNS_HEADER_LENGTH) return new Header(); + + bool idBool = ByteArrayTool.TryConvertBytesToUInt16(buffer[pos..(pos + 2)], out ushort id); + pos += 2; + + bool convertToBinaryBool = ByteArrayTool.TryConvertToBinary(buffer[pos..(pos + 2)], out string bitsStr); + bool splitBinaryBool = ByteArrayTool.TrySplitBinary(bitsStr, out bool[] bits); + + pos += 2; + bool qrBool = CommonTools.TryConvertToEnum(new[] { bits[0] }, out DnsEnums.QR qr); + bool opCodeBool = CommonTools.TryConvertToEnum(new[] { bits[1], bits[2], bits[3], bits[4] }, out DnsEnums.OperationalCode opCode); + bool aaBool = CommonTools.TryConvertToEnum(new[] { bits[5] }, out DnsEnums.AA aa); + bool tcBool = CommonTools.TryConvertToEnum(new[] { bits[6] }, out DnsEnums.TC tc); + bool rdBool = CommonTools.TryConvertToEnum(new[] { bits[7] }, out DnsEnums.RD rd); + bool raBool = CommonTools.TryConvertToEnum(new[] { bits[8] }, out DnsEnums.RA ra); + // 9-10 Reserved + bool answerAuthenticatedBool = CommonTools.TryConvertToEnum(new[] { bits[11] }, out DnsEnums.AnswerAuthenticated answerAuthenticated); + bool nonAuthenticatedDataBool = CommonTools.TryConvertToEnum(new[] { bits[12] }, out DnsEnums.NonAuthenticatedData nonAuthenticatedData); + bool rCodeBool = CommonTools.TryConvertToEnum(new[] { bits[^4], bits[^3], bits[^2], bits[^1] }, out DnsEnums.ResponseCode rCode); + + bool questionsCountBool = ByteArrayTool.TryConvertBytesToUInt16(buffer[pos..(pos + 2)], out ushort questionsCount); + pos += 2; + bool answersCountBool = ByteArrayTool.TryConvertBytesToUInt16(buffer[pos..(pos + 2)], out ushort answersCount); + pos += 2; + bool authoritiesCountBool = ByteArrayTool.TryConvertBytesToUInt16(buffer[pos..(pos + 2)], out ushort authoritiesCount); + pos += 2; + bool additionalsCountBool = ByteArrayTool.TryConvertBytesToUInt16(buffer[pos..(pos + 2)], out ushort additionalsCount); + pos += 2; + bool isSuccess = idBool && convertToBinaryBool && splitBinaryBool && + qrBool && opCodeBool && aaBool && tcBool && rdBool && raBool && rCodeBool && + answerAuthenticatedBool && nonAuthenticatedDataBool && + questionsCountBool && answersCountBool && authoritiesCountBool && additionalsCountBool; + + if (!isSuccess) return new Header(); + + Header header = new() + { + IsSuccess = isSuccess, + ID = id, + QR = qr, + OperationalCode = opCode, + AA = aa, + TC = tc, + RD = rd, + RA = ra, + Z = DnsEnums.Z.Reserved, + AnswerAuthenticated = answerAuthenticated, + NonAuthenticatedData = nonAuthenticatedData, + ResponseCode = rCode, + QuestionsCount = questionsCount, + AnswersCount = answersCount, + AuthoritiesCount = authoritiesCount, + AdditionalsCount = additionalsCount + }; + + return header; + } + catch (Exception ex) + { + Debug.WriteLine("DNS Read Header: " + ex.Message); + return new Header(); + } + } + + public static bool TryWrite(DnsMessage dnsMessage, ref int pos, out byte[] buffer) + { + try + { + Header header = dnsMessage.Header; + bool idBool = ByteArrayTool.TryConvertUInt16ToBytes(header.ID, out byte[] id); + + bool[] bits = new bool[16]; // 16 Bits (2 Bytes) + bits[0] = Convert.ToBoolean(header.QR); // 0 + bool opCodeBool = ByteArrayTool.TrySplitBinary((int)header.OperationalCode, out bool[] opCode); + if (opCodeBool) opCode.CopyTo(bits, 1); // 1-4 + bits[5] = Convert.ToBoolean(header.AA); // 5 + bits[6] = Convert.ToBoolean(header.TC); // 6 + bits[7] = Convert.ToBoolean(header.RD); // 7 + bits[8] = Convert.ToBoolean(header.RA); // 8 + bool zBool = ByteArrayTool.TrySplitBinary((int)header.Z, out bool[] z); + if (zBool) z.CopyTo(bits, 9); // 9-10, ZZ, Reserved + bits[11] = Convert.ToBoolean(header.AnswerAuthenticated); // 11 + bits[12] = Convert.ToBoolean(header.NonAuthenticatedData); // 12 + bool rCodeBool = ByteArrayTool.TrySplitBinary((int)header.ResponseCode, out bool[] rCode); + if (rCodeBool) rCode.CopyTo(bits, 13); // 13-16 + bool bitsBool = ByteArrayTool.TryConvertSplittedBinaryToBytes(bits, out byte[] bitsBytes); + + bool questionsCountBool = ByteArrayTool.TryConvertUInt16ToBytes(header.QuestionsCount, out byte[] questionsCount); + bool answersCountBool = ByteArrayTool.TryConvertUInt16ToBytes(header.AnswersCount, out byte[] answersCount); + bool authoritiesCountBool = ByteArrayTool.TryConvertUInt16ToBytes(header.AuthoritiesCount, out byte[] authoritiesCount); + bool additionalRecordsCountBool = ByteArrayTool.TryConvertUInt16ToBytes(header.AdditionalsCount, out byte[] additionalRecordsCount); + + bool isSuccess = idBool && + opCodeBool && zBool && rCodeBool && bitsBool && + questionsCountBool && answersCountBool && authoritiesCountBool && additionalRecordsCountBool; + + if (!isSuccess) + { + buffer = Array.Empty(); + return false; + } + + List bufferList = new(); + bufferList.AddRange(id); + bufferList.AddRange(bitsBytes); + bufferList.AddRange(questionsCount); + bufferList.AddRange(answersCount); + bufferList.AddRange(authoritiesCount); + bufferList.AddRange(additionalRecordsCount); + buffer = bufferList.ToArray(); + + bool totalSuccess = buffer.Length == MsmhAgnosticServer.DNS_HEADER_LENGTH; + if (!totalSuccess) + { + buffer = Array.Empty(); + return false; + } + + pos += MsmhAgnosticServer.DNS_HEADER_LENGTH; + return true; + } + catch (Exception ex) + { + Debug.WriteLine("DNS TryWrite Header: " + ex.Message); + buffer = Array.Empty(); + return false; + } + } +} diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Questions.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Questions.cs new file mode 100644 index 0000000..1c1bd95 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Questions.cs @@ -0,0 +1,136 @@ +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class Question +{ + public string QNAME { get; set; } = string.Empty; // URL Encoded Host, Terminates With 0x00 (e.g. 0x07 example 0x03 com 0x00) + public int QNamePosition { get; protected set; } = MsmhAgnosticServer.DNS_HEADER_LENGTH; + public DnsEnums.RRType QTYPE { get; set; } // 2 Bytes / DNS Record, A Record = 1 + public DnsEnums.CLASS QCLASS { get; set; } // 2 Bytes / DNS Class, IN = 1 + + public Question() { } + + public Question(string qNAME, int qNamePosition, DnsEnums.RRType qTYPE, DnsEnums.CLASS qCLASS) + { + QNAME = qNAME; + QNamePosition = qNamePosition; + QTYPE = qTYPE; + QCLASS = qCLASS; + } +} + +public class Questions +{ + public bool IsSuccess { get; private set; } = false; + public List QuestionRecords { get; set; } = new(); + + public override string ToString() + { + string result = "DNS Questions:\n"; + result += $"{nameof(IsSuccess)}: {IsSuccess}\n"; + for (int n = 0; n < QuestionRecords.Count; n++) + { + result += $"Question Number {n + 1}:\n"; + Question question = QuestionRecords[n]; + result += $"{nameof(question.QNAME)}: {question.QNAME}\n"; + result += $"{nameof(question.QTYPE)}: {question.QTYPE}\n"; + result += $"{nameof(question.QCLASS)}: {question.QCLASS}"; + } + return result; + } + + public static Questions Read(byte[] buffer, ref int pos, DnsMessage dnsMessage) + { + try + { + Questions questions = new(); + + for (int n = 0; n < dnsMessage.Header.QuestionsCount; n++) + { + if (pos > buffer.Length) break; + if (buffer.Length < pos + 6) break; + + // QNAME + string domain = ResourceRecord.ReadRecordName(buffer, pos, out int qLength).ToString(); + if (string.IsNullOrEmpty(domain)) return questions; + int qNamePosition = pos; + pos += qLength; + + // QTYPE + if (pos + 2 > buffer.Length) return questions; + bool qTypeBool = ByteArrayTool.TryConvertBytesToUInt16(buffer[pos..(pos + 2)], out ushort qType); + pos += 2; + + // QCLASS + if (pos + 2 > buffer.Length) return questions; + bool qClassBool = ByteArrayTool.TryConvertBytesToUInt16(buffer[pos..(pos + 2)], out ushort qClass); + pos += 2; + + if (!qTypeBool || !qClassBool) return questions; + + DnsEnums.RRType typeEnum = (DnsEnums.RRType)Enum.Parse(typeof(DnsEnums.RRType), qType.ToString()); + DnsEnums.CLASS classEnum = (DnsEnums.CLASS)Enum.Parse(typeof(DnsEnums.CLASS), qClass.ToString()); + + Question question = new(domain, qNamePosition, typeEnum, classEnum); + questions.QuestionRecords.Add(question); + questions.IsSuccess = true; + } + + return questions; + } + catch (Exception ex) + { + Debug.WriteLine("DNS Read Questions: " + ex.Message); + return new Questions(); + } + } + + public static bool TryWrite(DnsMessage dnsMessage, ref int pos, out byte[] buffer) + { + try + { + Questions questions = dnsMessage.Questions; + List bufferList = new(); + + for (int n = 0; n < questions.QuestionRecords.Count; n++) + { + Question question = questions.QuestionRecords[n]; + + if (string.IsNullOrEmpty(question.QNAME.Trim()) || !NetworkTool.IsDomainNameValid(question.QNAME)) + { + buffer = Array.Empty(); + return false; + } + + bool qTypeBool = ByteArrayTool.TryConvertUInt16ToBytes((ushort)question.QTYPE, out byte[] qType); + bool qClassBool = ByteArrayTool.TryConvertUInt16ToBytes((ushort)question.QCLASS, out byte[] qClass); + + bool isSuccess = qTypeBool && qClassBool; + + if (!isSuccess) + { + buffer = Array.Empty(); + return false; + } + + // QNAME + byte[] qName = ResourceRecord.WriteRecordName(dnsMessage, question.QNAME, question.QNamePosition); + + bufferList.AddRange(qName); + bufferList.AddRange(qType); // QTYPE + bufferList.AddRange(qClass); // QCLASS + } + + buffer = bufferList.ToArray(); + pos += buffer.Length; + return true; + } + catch (Exception ex) + { + Debug.WriteLine("DNS TryWrite Questions: " + ex.Message); + buffer = Array.Empty(); + return false; + } + } +} diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/ARecord.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/ARecord.cs new file mode 100644 index 0000000..d854037 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/ARecord.cs @@ -0,0 +1,79 @@ +using System.Diagnostics; +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +// https://datatracker.ietf.org/doc/html/rfc1035#section-3.4.1 +public class ARecord : ResourceRecord +{ + public IPAddress IP { get; private set; } = IPAddress.None; + + public override string ToString() + { + string result = base.ToString() + "\n"; + result += $"{nameof(IP)}: {IP}\n"; + return result; + } + + public ARecord() { } + + public ARecord(string domain, uint ttl, IPAddress ipv4) + { + Name = domain; + TYPE = DnsEnums.RRType.A; + CLASS = DnsEnums.CLASS.IN; + TimeToLive = ttl; + TTLDateTime = DateTime.UtcNow; + IP = ipv4; + } + + public ARecord(ResourceRecord resourceRecord, IPAddress ipv4) + { + Name = resourceRecord.Name; + TYPE = resourceRecord.TYPE; + CLASS = resourceRecord.CLASS; + TimeToLive = resourceRecord.TimeToLive; + TTLDateTime = resourceRecord.TTLDateTime; + IP = ipv4; + } + + public static ResourceRecord Parse(ResourceRecord resourceRecord, byte[] buffer, int pos) + { + try + { + IPAddress ip = new(buffer[pos..(pos + 4)]); + return new ARecord(resourceRecord, ip); + } + catch (Exception ex) + { + Debug.WriteLine("DNS ARecord Parse: " + ex.Message); + return new ARecord(); + } + } + + public static bool TryWrite(IResourceRecord resourceRecord, List bufferList, ref int pos) + { + try + { + // RDLENGTH & RDDATA + if (resourceRecord is not ARecord aRecord) return false; + byte[] ipBytes = new byte[4]; + bool success = aRecord.IP.TryWriteBytes(ipBytes, out _); + if (success) + { + bool rdLengthBool = ByteArrayTool.TryConvertUInt16ToBytes(Convert.ToUInt16(ipBytes.Length), out byte[] rdLength); // 2 Bytes + if (!rdLengthBool) return false; + bufferList.AddRange(rdLength); + bufferList.AddRange(ipBytes); + pos += 6; + return true; + } + return false; + } + catch (Exception ex) + { + Debug.WriteLine("DNS ARecord TryWrite: " + ex.Message); + return false; + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/AaaaRecord.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/AaaaRecord.cs new file mode 100644 index 0000000..5274a89 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/AaaaRecord.cs @@ -0,0 +1,79 @@ +using System.Diagnostics; +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +// https://datatracker.ietf.org/doc/html/rfc3596 +public class AaaaRecord : ResourceRecord +{ + public IPAddress IP { get; private set; } = IPAddress.None; + + public override string ToString() + { + string result = base.ToString() + "\n"; + result += $"{nameof(IP)}: {IP}\n"; + return result; + } + + public AaaaRecord() { } + + public AaaaRecord(string domain, uint ttl, IPAddress ipv6) + { + Name = domain; + TYPE = DnsEnums.RRType.AAAA; + CLASS = DnsEnums.CLASS.IN; + TimeToLive = ttl; + TTLDateTime = DateTime.UtcNow; + IP = ipv6; + } + + public AaaaRecord(ResourceRecord resourceRecord, IPAddress ipv6) + { + Name = resourceRecord.Name; + TYPE = resourceRecord.TYPE; + CLASS = resourceRecord.CLASS; + TimeToLive = resourceRecord.TimeToLive; + TTLDateTime = resourceRecord.TTLDateTime; + IP = ipv6; + } + + public static ResourceRecord Parse(ResourceRecord resourceRecord, byte[] buffer, int pos) + { + try + { + IPAddress ip = new(buffer[pos..(pos + 16)]); + return new AaaaRecord(resourceRecord, ip); + } + catch (Exception ex) + { + Debug.WriteLine("DNS AaaaRecord Parse: " + ex.Message); + return new AaaaRecord(); + } + } + + public static bool TryWrite(IResourceRecord resourceRecord, List bufferList, ref int pos) + { + try + { + // RDLENGTH & RDDATA + if (resourceRecord is not AaaaRecord aaaaRecord) return false; + byte[] ipBytes = new byte[16]; + bool success = aaaaRecord.IP.TryWriteBytes(ipBytes, out _); + if (success) + { + bool rdLengthBool = ByteArrayTool.TryConvertUInt16ToBytes(Convert.ToUInt16(ipBytes.Length), out byte[] rdLength); // 2 Bytes + if (!rdLengthBool) return false; + bufferList.AddRange(rdLength); + bufferList.AddRange(ipBytes); + pos += 18; + return true; + } + return false; + } + catch (Exception ex) + { + Debug.WriteLine("DNS AaaaRecord TryWrite: " + ex.Message); + return false; + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/CNameRecord.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/CNameRecord.cs new file mode 100644 index 0000000..65fdf01 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/CNameRecord.cs @@ -0,0 +1,76 @@ +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +// https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.1 +public class CNameRecord : ResourceRecord +{ + public string CName { get; private set; } = string.Empty; + + public override string ToString() + { + string result = base.ToString() + "\n"; + result += $"{nameof(CName)}: {CName}\n"; + return result; + } + + public CNameRecord() { } + + public CNameRecord(string domain, uint ttl, string cName) + { + Name = domain; + TYPE = DnsEnums.RRType.CNAME; + CLASS = DnsEnums.CLASS.IN; + TimeToLive = ttl; + TTLDateTime = DateTime.UtcNow; + CName = cName; + } + + public CNameRecord(ResourceRecord resourceRecord, string domain) + { + Name = resourceRecord.Name; + TYPE = resourceRecord.TYPE; + CLASS = resourceRecord.CLASS; + TimeToLive = resourceRecord.TimeToLive; + TTLDateTime = resourceRecord.TTLDateTime; + CName = domain; + } + + public static ResourceRecord Parse(ResourceRecord resourceRecord, byte[] buffer, int pos) + { + try + { + string domain = ReadRecordName(buffer, pos, out _).ToString(); + if (string.IsNullOrEmpty(domain)) return new CNameRecord(); + return new CNameRecord(resourceRecord, domain); + } + catch (Exception ex) + { + Debug.WriteLine("DNS CNameRecord Parse: " + ex.Message); + return new CNameRecord(); + } + } + + public static bool TryWrite(IResourceRecord resourceRecord, List bufferList, DnsMessage dnsMessage, ref int pos) + { + try + { + // RDLENGTH & RDDATA + if (resourceRecord is not CNameRecord cNameRecord) return false; + byte[] domainArray = WriteRecordName(dnsMessage, cNameRecord.CName, pos + 2); + + bool rdLengthBool = ByteArrayTool.TryConvertUInt16ToBytes(Convert.ToUInt16(domainArray.Length), out byte[] rdLength); // 2 Bytes + if (!rdLengthBool) return false; + bufferList.AddRange(rdLength); + bufferList.AddRange(domainArray); + pos += 2; + pos += domainArray.Length; + return true; + } + catch (Exception ex) + { + Debug.WriteLine("DNS CNameRecord TryWrite: " + ex.Message); + return false; + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/MxRecord.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/MxRecord.cs new file mode 100644 index 0000000..d4f066f --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/MxRecord.cs @@ -0,0 +1,89 @@ +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +// https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.9 +public class MxRecord : ResourceRecord +{ + public ushort Preference { get; private set; } + /// + /// A host willing to act as a mail exchange + /// + public string Domain { get; private set; } = string.Empty; + + public override string ToString() + { + string result = base.ToString() + "\n"; + result += $"{nameof(Preference)}: {Preference}\n"; + result += $"{nameof(Domain)}: {Domain}\n"; + return result; + } + + public MxRecord() { } + + public MxRecord(string domain, uint ttl, ushort preference, string mailDomain) + { + Name = domain; + TYPE = DnsEnums.RRType.MX; + CLASS = DnsEnums.CLASS.IN; + TimeToLive = ttl; + TTLDateTime = DateTime.UtcNow; + Preference = preference; + Domain = mailDomain; + } + + public MxRecord(ResourceRecord resourceRecord, ushort preference, string domain) + { + Name = resourceRecord.Name; + TYPE = resourceRecord.TYPE; + CLASS = resourceRecord.CLASS; + TimeToLive = resourceRecord.TimeToLive; + TTLDateTime = resourceRecord.TTLDateTime; + Preference = preference; + Domain = domain; + } + + public static ResourceRecord Parse(ResourceRecord resourceRecord, byte[] buffer, int pos) + { + try + { + ByteArrayTool.TryConvertBytesToUInt16(buffer[pos..(pos + 2)], out ushort preference); + pos += 2; + + string domain = ReadRecordName(buffer, pos, out _, false).ToString(); + if (string.IsNullOrEmpty(domain)) return new MxRecord(); + return new MxRecord(resourceRecord, preference, domain); + } + catch (Exception ex) + { + Debug.WriteLine("DNS MxRecord Parse: " + ex.Message); + return new MxRecord(); + } + } + + public static bool TryWrite(IResourceRecord resourceRecord, List bufferList, DnsMessage dnsMessage, ref int pos) + { + try + { + // RDLENGTH & RDDATA + if (resourceRecord is not MxRecord mxRecord) return false; + ByteArrayTool.TryConvertUInt16ToBytes(mxRecord.Preference, out byte[] preferenceArray); + byte[] domainArray = WriteRecordName(dnsMessage, mxRecord.Domain, pos + 2 + preferenceArray.Length); + + int len = preferenceArray.Length + domainArray.Length; + bool rdLengthBool = ByteArrayTool.TryConvertUInt16ToBytes(Convert.ToUInt16(len), out byte[] rdLength); // 2 Bytes + if (!rdLengthBool) return false; + bufferList.AddRange(rdLength); + bufferList.AddRange(preferenceArray); + bufferList.AddRange(domainArray); + pos += 2; + pos += len; + return true; + } + catch (Exception ex) + { + Debug.WriteLine("DNS MxRecord TryWrite: " + ex.Message); + return false; + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/NsRecord.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/NsRecord.cs new file mode 100644 index 0000000..5351599 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/NsRecord.cs @@ -0,0 +1,76 @@ +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +// https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.11 +public class NsRecord : ResourceRecord +{ + public string NS { get; private set; } = string.Empty; + + public override string ToString() + { + string result = base.ToString() + "\n"; + result += $"{nameof(NS)}: {NS}\n"; + return result; + } + + public NsRecord() { } + + public NsRecord(string domain, uint ttl, string ns) + { + Name = domain; + TYPE = DnsEnums.RRType.NS; + CLASS = DnsEnums.CLASS.IN; + TimeToLive = ttl; + TTLDateTime = DateTime.UtcNow; + NS = ns; + } + + public NsRecord(ResourceRecord resourceRecord, string domain) + { + Name = resourceRecord.Name; + TYPE = resourceRecord.TYPE; + CLASS = resourceRecord.CLASS; + TimeToLive = resourceRecord.TimeToLive; + TTLDateTime = resourceRecord.TTLDateTime; + NS = domain; + } + + public static ResourceRecord Parse(ResourceRecord resourceRecord, byte[] buffer, int pos) + { + try + { + string domain = ReadRecordName(buffer, pos, out _).ToString(); + if (string.IsNullOrEmpty(domain)) return new NsRecord(); + return new NsRecord(resourceRecord, domain); + } + catch (Exception ex) + { + Debug.WriteLine("DNS NsRecord Parse: " + ex.Message); + return new NsRecord(); + } + } + + public static bool TryWrite(IResourceRecord resourceRecord, List bufferList, DnsMessage dnsMessage, ref int pos) + { + try + { + // RDLENGTH & RDDATA + if (resourceRecord is not NsRecord nsRecord) return false; + byte[] domainArray = WriteRecordName(dnsMessage, nsRecord.NS, pos + 2); + + bool rdLengthBool = ByteArrayTool.TryConvertUInt16ToBytes(Convert.ToUInt16(domainArray.Length), out byte[] rdLength); // 2 Bytes + if (!rdLengthBool) return false; + bufferList.AddRange(rdLength); + bufferList.AddRange(domainArray); + pos += 2; + pos += domainArray.Length; + return true; + } + catch (Exception ex) + { + Debug.WriteLine("DNS NsRecord TryWrite: " + ex.Message); + return false; + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/SoaRecord.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/SoaRecord.cs new file mode 100644 index 0000000..85b6cd8 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/SoaRecord.cs @@ -0,0 +1,150 @@ +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +// https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.13 +// https://www.tcpipguide.com/free/t_DNSMessageResourceRecordFieldFormats-4.htm +public class SoaRecord : ResourceRecord +{ + public string PrimaryNameServer { get; private set; } = string.Empty; + /// + /// Authorities Mailbox (Use . instead of @) + /// + public string ResponsibleAuthoritiesMailbox { get; private set; } = string.Empty; + public uint SerialNumber { get; private set; } + public uint RefreshInterval { get; private set; } + public uint RetryInterval { get; private set; } + public uint ExpireLimit { get; private set; } + public uint MinTtl { get; private set; } + + public override string ToString() + { + string result = base.ToString() + "\n"; + result += $"{nameof(PrimaryNameServer)}: {PrimaryNameServer}\n"; + result += $"{nameof(ResponsibleAuthoritiesMailbox)}: {ResponsibleAuthoritiesMailbox}\n"; + result += $"{nameof(SerialNumber)}: {SerialNumber}\n"; + result += $"{nameof(RefreshInterval)}: {RefreshInterval}\n"; + result += $"{nameof(RetryInterval)}: {RetryInterval}\n"; + result += $"{nameof(ExpireLimit)}: {ExpireLimit}\n"; + result += $"{nameof(MinTtl)}: {MinTtl}\n"; + return result; + } + + public SoaRecord() { } + + public SoaRecord(string domain, uint ttl, string primaryNameServer, + string responsibleAuthoritiesMailbox, + uint serialNumber, + uint refreshInterval, + uint retryInterval, + uint expireLimit, + uint minTtl) + { + Name = domain; + TYPE = DnsEnums.RRType.SOA; + CLASS = DnsEnums.CLASS.IN; + TimeToLive = ttl; + TTLDateTime = DateTime.UtcNow; + PrimaryNameServer = primaryNameServer; + ResponsibleAuthoritiesMailbox = responsibleAuthoritiesMailbox; + SerialNumber = serialNumber; + RefreshInterval = refreshInterval; + RetryInterval = retryInterval; + ExpireLimit = expireLimit; + MinTtl = minTtl; + } + + public SoaRecord(ResourceRecord resourceRecord, string primaryNameServer, + string responsibleAuthoritiesMailbox, + uint serialNumber, + uint refreshInterval, + uint retryInterval, + uint expireLimit, + uint minTtl) + { + Name = resourceRecord.Name; + TYPE = resourceRecord.TYPE; + CLASS = resourceRecord.CLASS; + TimeToLive = resourceRecord.TimeToLive; + TTLDateTime = resourceRecord.TTLDateTime; + PrimaryNameServer = primaryNameServer; + ResponsibleAuthoritiesMailbox = responsibleAuthoritiesMailbox; + SerialNumber = serialNumber; + RefreshInterval = refreshInterval; + RetryInterval = retryInterval; + ExpireLimit = expireLimit; + MinTtl = minTtl; + } + + public static ResourceRecord Parse(ResourceRecord resourceRecord, byte[] buffer, int pos) + { + try + { + string priNS = ReadRecordName(buffer, pos, out int length).ToString(); + if (string.IsNullOrEmpty(priNS)) return new SoaRecord(); + pos += length; + + string ram = ReadRecordName(buffer, pos, out length, true).ToString(); + if (string.IsNullOrEmpty(ram)) return new SoaRecord(); + pos += length; + + ByteArrayTool.TryConvertBytesToUInt32(buffer[pos..(pos + 4)], out uint serial); + pos += 4; + + ByteArrayTool.TryConvertBytesToUInt32(buffer[pos..(pos + 4)], out uint refresh); + pos += 4; + + ByteArrayTool.TryConvertBytesToUInt32(buffer[pos..(pos + 4)], out uint retry); + pos += 4; + + ByteArrayTool.TryConvertBytesToUInt32(buffer[pos..(pos + 4)], out uint expire); + pos += 4; + + ByteArrayTool.TryConvertBytesToUInt32(buffer[pos..(pos + 4)], out uint ttl); + pos += 4; + + return new SoaRecord(resourceRecord, priNS, ram, serial, refresh, retry, expire, ttl); + } + catch (Exception ex) + { + Debug.WriteLine("DNS SoaRecord Parse: " + ex.Message); + return new SoaRecord(); + } + } + + public static bool TryWrite(IResourceRecord resourceRecord, List bufferList, DnsMessage dnsMessage, ref int pos) + { + try + { + // RDLENGTH & RDDATA + if (resourceRecord is not SoaRecord soaRecord) return false; + byte[] priNS = WriteRecordName(dnsMessage, soaRecord.PrimaryNameServer, pos + 2); + byte[] ram = WriteRecordName(dnsMessage, soaRecord.ResponsibleAuthoritiesMailbox, pos + 2 + priNS.Length); + ByteArrayTool.TryConvertUInt32ToBytes(soaRecord.SerialNumber, out byte[] serial); + ByteArrayTool.TryConvertUInt32ToBytes(soaRecord.RefreshInterval, out byte[] refresh); + ByteArrayTool.TryConvertUInt32ToBytes(soaRecord.RetryInterval, out byte[] retry); + ByteArrayTool.TryConvertUInt32ToBytes(soaRecord.ExpireLimit, out byte[] expire); + ByteArrayTool.TryConvertUInt32ToBytes(soaRecord.MinTtl, out byte[] ttl); + + int len = priNS.Length + ram.Length + serial.Length + refresh.Length + retry.Length + expire.Length + ttl.Length; + bool rdLengthBool = ByteArrayTool.TryConvertUInt16ToBytes(Convert.ToUInt16(len), out byte[] rdLength); // 2 Bytes + if (!rdLengthBool) return false; + bufferList.AddRange(rdLength); + bufferList.AddRange(priNS); + bufferList.AddRange(ram); + bufferList.AddRange(serial); + bufferList.AddRange(refresh); + bufferList.AddRange(retry); + bufferList.AddRange(expire); + bufferList.AddRange(ttl); + pos += 2; + pos += len; + return true; + } + catch (Exception ex) + { + Debug.WriteLine("DNS SoaRecord TryWrite: " + ex.Message); + return false; + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/TextRecord.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/TextRecord.cs new file mode 100644 index 0000000..2c99f78 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/TextRecord.cs @@ -0,0 +1,241 @@ +using System.Diagnostics; +using System.Text; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +// https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.14 +public class TextRecord : ResourceRecord +{ + public List Texts { get; private set; } = new(); + public List TXTCertificates { get; private set; } = new(); + + public enum ESVersion : ushort + { + X25519_XSalsa20Poly1305 = 1, + X25519_XChacha20Poly1305 = 2 + } + + public class TXTCertificate + { + public string Magic { get; private set; } = "DNSC"; + public ESVersion Version { get; private set; } + public ushort MinorVersion { get; private set; } + /// + /// HEX String (128 Char) + /// + public string Signature { get; private set; } = string.Empty; + /// + /// HEX String (64 Char) + /// + public string PublicKey { get; private set; } = string.Empty; + /// + /// HEX String (16 Char) + /// + public string ClientMagic { get; private set; } = string.Empty; + public uint Serial { get; private set; } + public uint StartTimeStampInSec { get; private set; } + public uint EndTimeStampInSec { get; private set; } + + public static TXTCertificate Read(byte[] buffer) + { + TXTCertificate txtCertificate = new(); + if (buffer.Length != 124) return txtCertificate; + + try + { + int pos = 0; + txtCertificate.Magic = Encoding.UTF8.GetString(buffer[pos..(pos + 4)]); + pos += 4; + ByteArrayTool.TryConvertBytesToUInt16(buffer[pos..(pos + 2)], out ushort version); + txtCertificate.Version = (ESVersion)Enum.Parse(typeof(ESVersion), version.ToString()); + pos += 2; + ByteArrayTool.TryConvertBytesToUInt16(buffer[pos..(pos + 2)], out ushort minorVersion); + pos += 2; + txtCertificate.MinorVersion = minorVersion; + txtCertificate.Signature = Convert.ToHexString(buffer[pos..(pos + 64)]).ToLower(); + pos += 64; + txtCertificate.PublicKey = Convert.ToHexString(buffer[pos..(pos + 32)]).ToLower(); + pos += 32; + txtCertificate.ClientMagic = Convert.ToHexString(buffer[pos..(pos + 8)]).ToLower(); + pos += 8; + ByteArrayTool.TryConvertBytesToUInt32(buffer[pos..(pos + 4)], out uint serial); + txtCertificate.Serial = serial; + pos += 4; + ByteArrayTool.TryConvertBytesToUInt32(buffer[pos..(pos + 4)], out uint startTimeStampInSec); + txtCertificate.StartTimeStampInSec = startTimeStampInSec; + pos += 4; + ByteArrayTool.TryConvertBytesToUInt32(buffer[pos..(pos + 4)], out uint endTimeStampInSec); + txtCertificate.EndTimeStampInSec = endTimeStampInSec; + } + catch (Exception ex) + { + Debug.WriteLine("DNS TextRecord TXTCertificate Read: " + ex.Message); + } + + return txtCertificate; + } + + public static bool TryWrite(TXTCertificate txtCertificate, out byte[] buffer) + { + try + { + List bufferList = new(); + bufferList.AddRange(Encoding.UTF8.GetBytes(txtCertificate.Magic)); + ByteArrayTool.TryConvertUInt16ToBytes((ushort)txtCertificate.Version, out byte[] version); + bufferList.AddRange(version); + ByteArrayTool.TryConvertUInt16ToBytes(txtCertificate.MinorVersion, out byte[] minorVersion); + bufferList.AddRange(minorVersion); + bufferList.AddRange(Convert.FromHexString(txtCertificate.Signature)); + bufferList.AddRange(Convert.FromHexString(txtCertificate.PublicKey)); + bufferList.AddRange(Convert.FromHexString(txtCertificate.ClientMagic)); + ByteArrayTool.TryConvertUInt32ToBytes(txtCertificate.Serial, out byte[] serial); + bufferList.AddRange(serial); + ByteArrayTool.TryConvertUInt32ToBytes(txtCertificate.StartTimeStampInSec, out byte[] startTimeStampInSec); + bufferList.AddRange(startTimeStampInSec); + ByteArrayTool.TryConvertUInt32ToBytes(txtCertificate.EndTimeStampInSec, out byte[] endTimeStampInSec); + bufferList.AddRange(endTimeStampInSec); + buffer = bufferList.ToArray(); + return true; + } + catch (Exception ex) + { + Debug.WriteLine("DNS TextRecord TXTCertificate TryWrite: " + ex.Message); + buffer = Array.Empty(); + return false; + } + } + } + + public override string ToString() + { + string result = base.ToString() + "\n"; + for (int i = 0; i < Texts.Count; i++) + { + string text = Texts[i]; + result += $"Text Number {i + 1}: {text}\n"; + } + for (int j = 0; j < TXTCertificates.Count; j++) + { + TXTCertificate cert = TXTCertificates[j]; + result += $"Certificate Number {j + 1}:\n"; + result += $"{nameof(cert.Magic)}: {cert.Magic}\n"; + result += $"{nameof(cert.Version)}: {cert.Version}\n"; + result += $"{nameof(cert.MinorVersion)}: {cert.MinorVersion}\n"; + result += $"{nameof(cert.Signature)}: {cert.Signature}\n"; + result += $"{nameof(cert.PublicKey)}: {cert.PublicKey}\n"; + result += $"{nameof(cert.ClientMagic)}: {cert.ClientMagic}\n"; + result += $"{nameof(cert.Serial)}: {cert.Serial}\n"; + result += $"{nameof(cert.StartTimeStampInSec)}: {cert.StartTimeStampInSec}\n"; + result += $"{nameof(cert.EndTimeStampInSec)}: {cert.EndTimeStampInSec}\n"; + } + return result; + } + + public TextRecord() { } + + public TextRecord(string domain, uint ttl, List texts, List txtCertificates) + { + Name = domain; + TYPE = DnsEnums.RRType.TEXT; + CLASS = DnsEnums.CLASS.IN; + TimeToLive = ttl; + TTLDateTime = DateTime.UtcNow; + Texts = texts; + TXTCertificates = txtCertificates; + } + + public TextRecord(ResourceRecord resourceRecord, List texts, List txtCertificates) + { + Name = resourceRecord.Name; + TYPE = resourceRecord.TYPE; + CLASS = resourceRecord.CLASS; + TimeToLive = resourceRecord.TimeToLive; + TTLDateTime = resourceRecord.TTLDateTime; + Texts = texts; + TXTCertificates = txtCertificates; + } + + public static ResourceRecord Parse(ResourceRecord resourceRecord, byte[] buffer, int pos, ushort rLength) + { + try + { + List texts = new(); + List txtCertificates = new(); + + int currentPos = 0; + while (true) + { + int len = buffer[pos]; + pos++; + currentPos++; + + string magic = Encoding.UTF8.GetString(buffer, pos, 4); + if (magic.Equals("DNSC")) + { + txtCertificates.Add(TXTCertificate.Read(buffer[pos..(pos + len)])); + } + else + { + texts.Add(Encoding.UTF8.GetString(buffer, pos, len)); + } + + pos += len; + currentPos += len; + if (currentPos >= rLength) break; + } + + return new TextRecord(resourceRecord, texts, txtCertificates); + } + catch (Exception ex) + { + Debug.WriteLine("DNS TextRecord Parse: " + ex.Message); + return new TextRecord(); + } + } + + public static bool TryWrite(IResourceRecord resourceRecord, List bufferList, ref int pos) + { + try + { + // RDLENGTH & RDDATA + if (resourceRecord is not TextRecord textRecord) return false; + + List textsList = new(); + for (int i = 0; i < textRecord.Texts.Count; i++) + { + string text = textRecord.Texts[i]; + byte[] textArray = Encoding.UTF8.GetBytes(text); + byte textLen = (byte)textArray.Length; + textsList.Add(textLen); + textsList.AddRange(textArray); + } + byte[] textsArray = textsList.ToArray(); + + List txtCertificatesList = new(); + for (int j = 0; j < textRecord.TXTCertificates.Count; j++) + { + TXTCertificate txtCertificate = textRecord.TXTCertificates[j]; + TXTCertificate.TryWrite(txtCertificate, out byte[] txtCertificateArray); + byte txtCertificateLen = (byte)txtCertificateArray.Length; // 124 + txtCertificatesList.Add(txtCertificateLen); + txtCertificatesList.AddRange(txtCertificateArray); + } + byte[] txtCertificatesArray = txtCertificatesList.ToArray(); + + int len = textsArray.Length + txtCertificatesArray.Length; + bool rdLengthBool = ByteArrayTool.TryConvertUInt16ToBytes(Convert.ToUInt16(len), out byte[] rdLength); // 2 Bytes + if (!rdLengthBool) return false; + bufferList.AddRange(rdLength); + bufferList.AddRange(textsArray); + bufferList.AddRange(txtCertificatesArray); + pos += 2; + pos += len; + return true; + } + catch (Exception ex) + { + Debug.WriteLine("DNS TextRecord TryWrite: " + ex.Message); + return false; + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/UnknownRecord.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/UnknownRecord.cs new file mode 100644 index 0000000..f41cd17 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/Records/UnknownRecord.cs @@ -0,0 +1,73 @@ +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class UnknownRecord : ResourceRecord +{ + public byte[] RDDATA { get; private set; } = Array.Empty(); + + public override string ToString() + { + string result = base.ToString() + "\n"; + try { result += $"{nameof(RDDATA)}: {BitConverter.ToString(RDDATA)}\n"; } catch (Exception) { } + return result; + } + + public UnknownRecord() { } + + public UnknownRecord(ResourceRecord resourceRecord, byte[] rdData) + { + Name = resourceRecord.Name; + TYPE = resourceRecord.TYPE; + CLASS = resourceRecord.CLASS; + TimeToLive = resourceRecord.TimeToLive; + TTLDateTime = resourceRecord.TTLDateTime; + RDDATA = rdData; + } + + public static ResourceRecord Parse(ResourceRecord resourceRecord, byte[] buffer, int pos, ushort rdLength) + { + try + { + int count = pos + rdLength; + if (count <= buffer.Length) + { + byte[] rdData = buffer[pos..count]; + return new UnknownRecord(resourceRecord, rdData); + } + else return new UnknownRecord(); + } + catch (Exception ex) + { + Debug.WriteLine("DNS UnknownRecord Parse: " + ex.Message); + return new UnknownRecord(); + } + } + + public static bool TryWrite(IResourceRecord resourceRecord, List bufferList, DnsMessage dnsMessage, ref int pos) + { + try + { + // RDLENGTH & RDDATA + if (resourceRecord is not ResourceRecord record) return false; + + byte[] name = WriteRecordName(dnsMessage, resourceRecord.Name); + int lenOfTCT = 8; // TYPE(2), CLASS(2), TTL(4) + int iRecordLen = name.Length + lenOfTCT; + + //bufferList.Clear(); // We Don't Replace The Whole Record To Be Able Modify TTL, etc. + byte[] rDataBuffer = record.RecordBuffer[10..]; + bufferList.AddRange(rDataBuffer); + + // Adjust Pos (Just In Case) + pos += rDataBuffer.Length; + + return true; + } + catch (Exception ex) + { + Debug.WriteLine("DNS UnknownRecord TryWrite: " + ex.Message); + return false; + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/ResourceRecord.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/ResourceRecord.cs new file mode 100644 index 0000000..9332eaa --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsMessage/ResourceRecord.cs @@ -0,0 +1,608 @@ +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class Answers +{ + public List AnswerRecords = new(); + + public override string ToString() + { + if (!AnswerRecords.Any()) return string.Empty; + string result = "DNS Answer:\n"; + foreach (IResourceRecord r in AnswerRecords) result += r.ToString() + "\n"; + return result; + } + + public static Answers Read(byte[] buffer, ref int pos, DnsMessage dnsMessage) + { + Answers answers = new() + { + AnswerRecords = ResourceRecord.Read(buffer, ref pos, dnsMessage.Header.AnswersCount) + }; + return answers; + } + + public static bool TryWrite(DnsMessage dnsMessage, ref int pos, out byte[] buffer) + { + return ResourceRecord.TryWrite(dnsMessage, dnsMessage.Answers.AnswerRecords, ref pos, out buffer); + } +} + +public class Authorities +{ + public List AuthorityRecords = new(); + + public override string ToString() + { + if (!AuthorityRecords.Any()) return string.Empty; + string result = "DNS Authority:\n"; + foreach (IResourceRecord r in AuthorityRecords) result += r.ToString() + "\n"; + return result; + } + + public static Authorities Read(byte[] buffer, ref int pos, DnsMessage dnsMessage) + { + Authorities authorities = new() + { + AuthorityRecords = ResourceRecord.Read(buffer, ref pos, dnsMessage.Header.AuthoritiesCount) + }; + return authorities; + } + + public static bool TryWrite(DnsMessage dnsMessage, ref int pos, out byte[] buffer) + { + return ResourceRecord.TryWrite(dnsMessage, dnsMessage.Authorities.AuthorityRecords, ref pos, out buffer); + } +} + +public class Additionals +{ + public List AdditionalRecords = new(); + + public override string ToString() + { + if (!AdditionalRecords.Any()) return string.Empty; + string result = "DNS Additional:\n"; + foreach (IResourceRecord r in AdditionalRecords) result += r.ToString() + "\n"; + return result; + } + + public static Additionals Read(byte[] buffer, ref int pos, DnsMessage dnsMessage) + { + Additionals additionals = new() + { + AdditionalRecords = ResourceRecord.Read(buffer, ref pos, dnsMessage.Header.AdditionalsCount) + }; + return additionals; + } + + public static bool TryWrite(DnsMessage dnsMessage, ref int pos, out byte[] buffer) + { + return ResourceRecord.TryWrite(dnsMessage, dnsMessage.Additionals.AdditionalRecords, ref pos, out buffer); + } +} + +public interface IResourceRecord +{ + string Name { get; } + DnsEnums.RRType TYPE { get; } + DnsEnums.CLASS CLASS { get; } + uint TimeToLive { get; } + DateTime TTLDateTime { get; } + string ToString(); +} + +public class ResourceRecord : IResourceRecord +{ + public string Name { get; set; } = string.Empty; // Vary Bytes / Compressed Host, Contains Pointers + public DnsEnums.RRType TYPE { get; set; } // 2 Bytes / DNS Record, A Record = 1 + public DnsEnums.CLASS CLASS { get; set; } // 2 Bytes / DNS Class, IN = 1 + public uint TimeToLive { get; set; } // 4 Bytes / TTL In Sec + public DateTime TTLDateTime { get; set; } + internal byte[] RecordBuffer { get; set; } = Array.Empty(); + + public override string ToString() + { + string result = $"{nameof(Name)}: {Name}\n"; + result += $"{nameof(TYPE)}: {TYPE}\n"; + result += $"{nameof(CLASS)}: {CLASS}\n"; + result += $"{nameof(TimeToLive)}: {TimeToLive}"; + return result; + } + + public static List Read(byte[] buffer, ref int pos, int resourceCount) + { + List resourceRecords = new(); + + try + { + int currentPos = pos; + for (int n = 0; n < resourceCount; n++) + { + int recordStartPos = pos; + ResourceRecord resourceRecord = new(); + + if (buffer.Length <= pos + 12) return resourceRecords; + + string name = ReadRecordName(buffer, pos, out int length).ToString().Trim(); // Name Can Be Empty Here + resourceRecord.Name = name; + currentPos += length; + pos += length; + + // TYPE + if (currentPos + 2 > buffer.Length) return resourceRecords; + bool rrTypeBool = ByteArrayTool.TryConvertBytesToUInt16(buffer[currentPos..(currentPos + 2)], out ushort rrType); + if (!rrTypeBool) return resourceRecords; + resourceRecord.TYPE = (DnsEnums.RRType)Enum.Parse(typeof(DnsEnums.RRType), rrType.ToString()); + currentPos += 2; + pos += 2; + + // CLASS + if (currentPos + 2 > buffer.Length) return resourceRecords; + bool rClassBool = ByteArrayTool.TryConvertBytesToUInt16(buffer[currentPos..(currentPos + 2)], out ushort rClass); + if (!rClassBool) return resourceRecords; + resourceRecord.CLASS = (DnsEnums.CLASS)Enum.Parse(typeof(DnsEnums.CLASS), rClass.ToString()); + currentPos += 2; + pos += 2; + + // TTL + if (currentPos + 4 > buffer.Length) return resourceRecords; + bool ttlBool = ByteArrayTool.TryConvertBytesToUInt32(buffer[currentPos..(currentPos + 4)], out uint ttl); + if (!ttlBool) return resourceRecords; + resourceRecord.TimeToLive = ttl; + resourceRecord.TTLDateTime = DateTime.UtcNow; + currentPos += 4; + pos += 4; + + // RDLENGTH - The Length Of RDDATA + if (currentPos + 2 > buffer.Length) return resourceRecords; + bool rdLengthBool = ByteArrayTool.TryConvertBytesToUInt16(buffer[currentPos..(currentPos + 2)], out ushort rdLength); + if (!rdLengthBool) return resourceRecords; + currentPos += 2; + pos += 2; + + // RDDATA + resourceRecord = ReadRDDATA(resourceRecord, buffer, pos, rdLength); + int rdLengthInt = Convert.ToInt32(rdLength); + currentPos += rdLengthInt; + pos += rdLengthInt; + + if (pos <= buffer.Length && rdLengthInt != 0 && resourceRecord.TimeToLive != 0) + { + resourceRecord.RecordBuffer = buffer[recordStartPos..pos]; + resourceRecords.Add(resourceRecord); + } + else + { + Debug.WriteLine("DNS ResourceRecord: Empty Record Received"); + Debug.WriteLine($"Buffer Length: {buffer.Length} Position: {pos}"); + Debug.WriteLine($"RDData Length: {rdLengthInt} TTL: {resourceRecord.TimeToLive}"); + } + } + } + catch (Exception ex) + { + Debug.WriteLine("DNS ResourceRecord Read: " + ex.Message); + } + + return resourceRecords; + } + + public static bool TryWrite(DnsMessage dnsMessage, List resourceRecords, ref int pos, out byte[] buffer) + { + buffer = Array.Empty(); + if (!resourceRecords.Any()) return true; + List rrsBufferList = new(); + + try + { + for (int n = 0; n < resourceRecords.Count; n++) + { + List rrBufferList = new(); + IResourceRecord resourceRecord = resourceRecords[n]; + + byte[] name = WriteRecordName(dnsMessage, resourceRecord.Name, pos); + + bool rTypeBool = ByteArrayTool.TryConvertUInt16ToBytes((ushort)resourceRecord.TYPE, out byte[] rType); // 2 Bytes + bool rClassBool = ByteArrayTool.TryConvertUInt16ToBytes((ushort)resourceRecord.CLASS, out byte[] rClass); // 2 Bytes + bool ttlBool = ByteArrayTool.TryConvertUInt32ToBytes(resourceRecord.TimeToLive, out byte[] ttl); // 4 Bytes + + if (!rTypeBool || !rClassBool || !ttlBool) return false; + + rrBufferList.AddRange(name); + rrBufferList.AddRange(rType); + rrBufferList.AddRange(rClass); + rrBufferList.AddRange(ttl); + + pos += name.Length; // QNAME(Vari) + pos += 8; // TYPE(2), CLASS(2), TTL(4) + + // RDLENGTH & RDDATA + bool writeRdDataBool = TryWriteRDDATA(resourceRecord, rrBufferList, dnsMessage, ref pos); + if (!writeRdDataBool) return false; + + rrsBufferList.AddRange(rrBufferList); + } + + buffer = rrsBufferList.ToArray(); + return true; + } + catch (Exception ex) + { + Debug.WriteLine("DNS ResourceRecord TryWrite: " + ex.Message); + buffer = Array.Empty(); + return false; + } + } + + private enum CompressionPointer : ushort + { + NormalPointer = 11, // 2 Bytes + ExtendedPointer_8Bit_Offset = 01, // 3 Bytes + ExtendedPointer_16Bit_Offset = 10, // 4 Bytes + Label = 00, + Unknown = 99 + } + + private static bool TryGetPointer(byte pointerByte, out CompressionPointer pointer) + { + bool binaryStrBool = ByteArrayTool.TryConvertToBinary(pointerByte, out string binaryStr); + bool bitsBool = ByteArrayTool.TrySplitBinary(binaryStr, out bool[] bits); + bool pointBool = CommonTools.TryConvertToEnum(new[] { bits[0], bits[1] }, out pointer); + return binaryStrBool && bitsBool && pointBool; + } + + private static int GetLabelPositionByPoint(CompressionPointer pointer, byte[] pointerBuffer, out int length) + { + // 0x3F To Convert Two First Bits to 00 + if (pointer == CompressionPointer.NormalPointer) + { + length = 2; + int refPos = ((pointerBuffer[0] & 0x3F) << 8) | pointerBuffer[1]; + return refPos; + } + else if (pointer == CompressionPointer.ExtendedPointer_8Bit_Offset) + { + length = 3; + int refPos = ((pointerBuffer[0] & 0x3F) << 8) | pointerBuffer[1]; + int offset = pointerBuffer[2]; + return refPos + offset; + } + else if (pointer == CompressionPointer.ExtendedPointer_16Bit_Offset) + { + length = 4; + int refPos = ((pointerBuffer[0] & 0x3F) << 8) | pointerBuffer[1]; + int offset = (pointerBuffer[2] << 8) + pointerBuffer[3]; + return refPos + offset; + } + else + { + length = 0; + return 0; + } + } + + private static byte[] WritePointer(int labelPos) + { + try + { + // 00111111 = 63, 11111111 = 255, 0011111111111111 = 16383 + int in14Bits = 16383; // Max: 16383 + if (labelPos <= in14Bits) // 16383 + { + // NormalPointer 11 + //Debug.WriteLine("======= WritePointer: NormalPointer"); + byte[] bytes = new byte[2]; + bytes[0] = (byte)(labelPos >> 8); + bytes[1] = (byte)(labelPos & 255); + bytes[0] &= 63; // 00 + bytes[0] |= 192; // 11 + return bytes; + } + else if (labelPos <= in14Bits + 255) // 16383 + 255 + { + // ExtendedPointer_8Bit_Offset 01 + Debug.WriteLine("======= WritePointer: ExtendedPointer_8Bit_Offset " + labelPos); + byte[] bytes = new byte[3]; + int offset = labelPos - in14Bits; + bytes[0] = (byte)(in14Bits >> 8); + bytes[1] = (byte)(in14Bits & 255); + bytes[2] = (byte)offset; + bytes[0] &= 63; // 00 + bytes[0] |= 64; // 01 + return bytes; + } + else if (labelPos <= in14Bits + 65535) // 16383 + 65535 + { + // ExtendedPointer_16Bit_Offset 10 + Debug.WriteLine("======= WritePointer: ExtendedPointer_16Bit_Offset " + labelPos); + byte[] bytes = new byte[4]; + int offset = labelPos - in14Bits; + bytes[0] = (byte)(in14Bits >> 8); + bytes[1] = (byte)(in14Bits & 255); + bytes[2] = (byte)(offset >> 8); + bytes[3] = (byte)offset; + bytes[0] &= 63; // 00 + bytes[0] |= 128; // 10 + return bytes; + } + else return Array.Empty(); + } + catch (Exception ex) + { + Debug.WriteLine("DNS ResourceRecord WritePointer: " + ex.Message); + return Array.Empty(); + } + } + + public static StringBuilder ReadRecordName(byte[] buffer, int startPostion, out int length, bool isEmail = false) + { + int overflow = 0; + return ReadRecordNameInternal(ref overflow, buffer, startPostion, out length, isEmail); + } + + public static StringBuilder ReadRecordNameInternal(ref int innerOverflow, byte[] buffer, int startPostion, out int length, bool isEmail = false) + { + // https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.4 + // A sequence of labels ending in a zero octet + // A pointer + // A sequence of labels ending with a pointer + length = 0; + int pos = startPostion; + StringBuilder sb = new(); + innerOverflow++; + + try + { + int stackOverflowMax = 10; + int stackOverflow = 0; + bool isPointer = false; + while (true) + { + stackOverflow++; + // Detect Infinite Pointer To Pointer + if (stackOverflow > stackOverflowMax || innerOverflow > stackOverflowMax) + { + Debug.WriteLine("======== StackOverflowException ======="); + break; + } + + if (pos > buffer.Length) break; + + bool pointBool = TryGetPointer(buffer[pos], out CompressionPointer pointer); + if (!pointBool) + { + Debug.WriteLine("DNS TryGetPoint Failed"); + break; + } + + if (pointer == CompressionPointer.NormalPointer) + { + if (pos + 2 > buffer.Length) break; + int refPos = GetLabelPositionByPoint(pointer, buffer[pos..(pos + 2)], out int len); + string recordName = ReadRecordNameInternal(ref innerOverflow, buffer, refPos, out _).ToString(); + if (string.IsNullOrEmpty(recordName)) break; + sb.Append(recordName); + pos += len; + isPointer = true; + } + else if (pointer == CompressionPointer.ExtendedPointer_8Bit_Offset) + { + if (pos + 3 > buffer.Length) break; + int refPos = GetLabelPositionByPoint(pointer, buffer[pos..(pos + 3)], out int len); + string recordName = ReadRecordNameInternal(ref innerOverflow, buffer, refPos, out _).ToString(); + if (string.IsNullOrEmpty(recordName)) break; + sb.Append(recordName); + pos += len; + isPointer = true; + Debug.WriteLine("=============================== " + pointer + " " + sb.ToString()); + } + else if (pointer == CompressionPointer.ExtendedPointer_16Bit_Offset) + { + if (pos + 4 > buffer.Length) break; + int refPos = GetLabelPositionByPoint(pointer, buffer[pos..(pos + 4)], out int len); + string recordName = ReadRecordNameInternal(ref innerOverflow, buffer, refPos, out _).ToString(); + if (string.IsNullOrEmpty(recordName)) break; + sb.Append(recordName); + pos += len; + isPointer = true; + Debug.WriteLine("=============================== " + pointer); + } + else if (pointer == CompressionPointer.Label) + { + int len = buffer[pos]; + pos++; + if (pos + len >= buffer.Length) break; + string label = Encoding.UTF8.GetString(buffer[pos..(pos + len)]) + "."; + sb.Append(label); + pos += len; + } + else + { + Debug.WriteLine("DNS Unknown CompressionPointer: " + pointer); + break; + } + + if (pos >= buffer.Length) break; // End Of Buffer + + if (buffer[pos] == 0 && !isPointer) + { + pos++; // Label Terminator 0x00 + break; + } + + if (isPointer) break; + } + + //Debug.WriteLine($"======== Found {sb} In {innerOverflow + 1} Loops ======="); + if (sb.Length > 0 && sb[^1] == '.') sb.Remove(sb.Length - 1, 1); + + StringBuilder sbResult = new(); + bool email = false; + for (int n = 0; n < sb.Length; n++) + { + char c = sb[n]; + if (isEmail && !email && c.Equals('.')) + { + sbResult.Append('@'); + email = true; + } + else sbResult.Append(c); + } + + string nameResult = sbResult.ToString().Trim(); + if (string.IsNullOrEmpty(nameResult)) + { + length = 1; + } + else + { + Regex regex = new("^[0-9a-zA-Z-.@:]+$"); // Domain / IPv4 / IPv6 / Email (IP Is An Invalid Question) + if (stackOverflow <= stackOverflowMax && innerOverflow <= stackOverflowMax && regex.IsMatch(nameResult)) + { + length = pos - startPostion; + } + else + { + // Unexpected EOF + length = MsmhAgnosticServer.MaxDataSize - buffer.Length; + sbResult.Clear(); + } + } + + return sbResult; + } + catch (Exception ex) + { + Debug.WriteLine("DNS ReadRecordName: " + ex.Message); + return sb; + } + } + + public static byte[] WriteRecordName(DnsMessage dnsMessage, string domainName, int domainPos = -1) + { + // https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.4 + // A sequence of labels ending in a zero octet + // A pointer + // A sequence of labels ending with a pointer + // Limitation: https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.4 + List bufferList = new(); + + try + { + if (string.IsNullOrEmpty(domainName)) + { + bufferList.Add(0x00); // Terminator + return bufferList.ToArray(); + } + + // Convert @ To Dot + string domainNameConverted = string.Empty; + for (int n = 0; n < domainName.Length; n++) + { + char c = domainName[n]; + if (c.Equals('@')) c = '.'; + domainNameConverted += c; + } + + bool isPointer = false; + int labelSequencePos = domainPos; + string[] labels = domainNameConverted.Split('.'); + for (int n = 0; n < labels.Length; n++) + { + string label = labels[n]; + + if (string.IsNullOrEmpty(label)) + { + bufferList.Add(0x00); + continue; + } + + string labelSequence = string.Join('.', labels[n..]); + bool exist = isExist(labelSequence, out Tuple? lp); + if (exist && lp != null) + { + // Create Pointer + byte[] pointerBytes = WritePointer(lp.Item2); + bufferList.AddRange(pointerBytes); + isPointer = true; + //Debug.WriteLine($"======= Used Pointer For: {lp.Item1} Pos: {lp.Item2}"); + break; + } + else + { + // Create Label + bufferList.Add(Convert.ToByte(label.Length)); + bufferList.AddRange(Encoding.UTF8.GetBytes(label)); + //Debug.WriteLine($"------- Label Created: {label}"); + + // Add Current Domain Name Labels To Compress DnsMessage + if (domainPos != -1 && !string.IsNullOrEmpty(label) && !string.IsNullOrEmpty(labelSequence)) + { + if (n > 0) + { + string preLabel = labels[n - 1]; + labelSequencePos = labelSequencePos + Encoding.UTF8.GetBytes(preLabel).Length + 1; // 1 Byte Of Label Length + } + + dnsMessage.LabelPositions.Add(new Tuple(labelSequence, labelSequencePos)); + //Debug.WriteLine($"======= Label Sequence Saved: {labelSequence} Pos: {labelSequencePos}"); + } + } + } + + if (!isPointer) bufferList.Add(0x00); // Domain Terminator If Doesn't End With A Pointer + + bool isExist(string newLabel, out Tuple? lpOut) + { + lpOut = null; + foreach (Tuple lp in dnsMessage.LabelPositions.ToList()) + if (lp != null && newLabel.Equals(lp.Item1)) + { + lpOut = lp; + return true; + } + return false; + } + } + catch (Exception ex) + { + Debug.WriteLine("DNS ResourceRecord WriteRecordLabel: " + ex.Message); + } + + return bufferList.ToArray(); + } + + protected static ResourceRecord ReadRDDATA(ResourceRecord resourceRecord, byte[] buffer, int pos, ushort rLength) + { + return resourceRecord.TYPE switch + { + DnsEnums.RRType.A => ARecord.Parse(resourceRecord, buffer, pos), + DnsEnums.RRType.AAAA => AaaaRecord.Parse(resourceRecord, buffer, pos), + DnsEnums.RRType.CNAME => CNameRecord.Parse(resourceRecord, buffer, pos), + DnsEnums.RRType.MX => MxRecord.Parse(resourceRecord, buffer, pos), + DnsEnums.RRType.NS => NsRecord.Parse(resourceRecord, buffer, pos), + DnsEnums.RRType.SOA => SoaRecord.Parse(resourceRecord, buffer, pos), + DnsEnums.RRType.TEXT => TextRecord.Parse(resourceRecord, buffer, pos, rLength), + _ => UnknownRecord.Parse(resourceRecord, buffer, pos, rLength) + }; + } + + protected static bool TryWriteRDDATA(IResourceRecord resourceRecord, List bufferList, DnsMessage dnsMessage, ref int pos) + { + return resourceRecord.TYPE switch + { + DnsEnums.RRType.A => ARecord.TryWrite(resourceRecord, bufferList, ref pos), + DnsEnums.RRType.AAAA => AaaaRecord.TryWrite(resourceRecord, bufferList, ref pos), + DnsEnums.RRType.CNAME => CNameRecord.TryWrite(resourceRecord, bufferList, dnsMessage, ref pos), + DnsEnums.RRType.MX => MxRecord.TryWrite(resourceRecord, bufferList, dnsMessage, ref pos), + DnsEnums.RRType.NS => NsRecord.TryWrite(resourceRecord, bufferList, dnsMessage, ref pos), + DnsEnums.RRType.SOA => SoaRecord.TryWrite(resourceRecord, bufferList, dnsMessage, ref pos), + DnsEnums.RRType.TEXT => TextRecord.TryWrite(resourceRecord, bufferList, ref pos), + _ => UnknownRecord.TryWrite(resourceRecord, bufferList, dnsMessage, ref pos) + }; + } +} diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsRequest.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsRequest.cs new file mode 100644 index 0000000..4b669a5 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsRequest.cs @@ -0,0 +1,90 @@ +using System.Diagnostics; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class DnsRequest +{ + public Socket? Socket_ { get; set; } + public SslStream? Ssl_Stream { get; set; } + public SslKind Ssl_Kind { get; set; } = SslKind.NonSSL; + public EndPoint? LocalEndPoint { get; set; } + public EndPoint? RemoteEndPoint { get; set; } + public byte[] Buffer { get; set; } = Array.Empty(); + public DnsEnums.DnsProtocol Protocol { get; set; } + + public DnsRequest() { } + + public DnsRequest(Socket? socket, SslStream? sslStream, SslKind sslKind, EndPoint? localEndPoint, EndPoint? remoteEndPoint, byte[] buffer, DnsEnums.DnsProtocol protocol) + { + Socket_ = socket; + Ssl_Stream = sslStream; + Ssl_Kind = sslKind; + LocalEndPoint = localEndPoint; + RemoteEndPoint = remoteEndPoint; + Buffer = buffer; + Protocol = protocol; + } + + public async Task SendToAsync(byte[] aBuffer) + { + try + { + if (Buffer.Length == 0 || LocalEndPoint == null || RemoteEndPoint == null) + { + Disconnect(); + return; + } + + if (Ssl_Kind == SslKind.NonSSL && Socket_ != null) + await Socket_.SendToAsync(aBuffer, SocketFlags.None, RemoteEndPoint); + + if (Ssl_Kind == SslKind.SSL && Ssl_Stream != null && Protocol == DnsEnums.DnsProtocol.DoH) + { + bool isDohWriteSuccess = DnsMessage.TryWriteDoHResponse(aBuffer, out byte[] result); + if (isDohWriteSuccess) await Ssl_Stream.WriteAsync(result); + } + } + catch (Exception ex) + { + Debug.WriteLine("DNS DnsRequest SendToAsync: " + ex.Message); + } + } + + public async Task SendFailedResponseAsync() + { + try + { + if (Socket_ == null || Buffer.Length == 0 || LocalEndPoint == null || RemoteEndPoint == null) + { + Disconnect(); + return; + } + DnsMessage dm = DnsMessage.Read(Buffer, Protocol); + dm = DnsMessage.CreateFailedResponse(dm); + DnsMessage.TryWrite(dm, out byte[] failedBuffer); + await SendToAsync(failedBuffer); + } + catch (Exception ex) + { + Debug.WriteLine("DNS ProxyRequest SendFailedResponseAsync: " + ex.Message); + } + } + + public void Disconnect() + { + if (Protocol != DnsEnums.DnsProtocol.UDP) + { + try + { + Socket_?.Shutdown(SocketShutdown.Both); + Socket_?.Dispose(); + } + catch (Exception) { } + } + + try { Ssl_Stream?.Dispose(); } catch (Exception) { } + } +} diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DNSCryptConfigEditor.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DNSCryptConfigEditor.cs similarity index 99% rename from MsmhToolsClass/MsmhToolsClass/DnsTool/DNSCryptConfigEditor.cs rename to MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DNSCryptConfigEditor.cs index 548cac0..c771c85 100644 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DNSCryptConfigEditor.cs +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DNSCryptConfigEditor.cs @@ -1,9 +1,8 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using System.Net; using System.Text; -namespace MsmhToolsClass.DnsTool; +namespace MsmhToolsClass.MsmhAgnosticServer; public class DNSCryptConfigEditor { @@ -248,4 +247,4 @@ public async Task WriteAsync() Debug.WriteLine("DNSCryptConfigEditor WriteAsync: " + ex.Message); } } -} \ No newline at end of file +} diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DNSCryptStampGenerator.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DNSCryptStampGenerator.cs similarity index 99% rename from MsmhToolsClass/MsmhToolsClass/DnsTool/DNSCryptStampGenerator.cs rename to MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DNSCryptStampGenerator.cs index f6b8cdb..a58b4f6 100644 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DNSCryptStampGenerator.cs +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DNSCryptStampGenerator.cs @@ -1,8 +1,7 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using System.Text; -namespace MsmhToolsClass.DnsTool; +namespace MsmhToolsClass.MsmhAgnosticServer; public class DNSCryptStampGenerator { diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DNSCryptStampReader.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DNSCryptStampReader.cs similarity index 59% rename from MsmhToolsClass/MsmhToolsClass/DnsTool/DNSCryptStampReader.cs rename to MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DNSCryptStampReader.cs index c7581ec..6f23fcd 100644 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DNSCryptStampReader.cs +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DNSCryptStampReader.cs @@ -1,15 +1,15 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; +using System.Net; using System.Text; -namespace MsmhToolsClass.DnsTool; +namespace MsmhToolsClass.MsmhAgnosticServer; public class DNSCryptStampReader { // More info: https://dnscrypt.info/stamps-specifications/ public bool IsDecryptionSuccess { get; private set; } = false; public string Stamp { get; private set; } = string.Empty; - public string IP { get; private set; } = string.Empty; + public IPAddress IP { get; private set; } = IPAddress.None; public int Port { get; private set; } = -1; public string Host { get; private set; } = string.Empty; public string Path { get; private set; } = string.Empty; @@ -63,6 +63,7 @@ private struct StampProtocolName public DNSCryptStampReader(string stamp) { + if (string.IsNullOrEmpty(stamp)) return; Stamp = stamp; if (stamp.StartsWith("sdns://")) @@ -75,7 +76,7 @@ public DNSCryptStampReader(string stamp) // Get Stamp Binary byte[] stampBinary = EncodingTool.UrlDecode(stamp); - // Get Protocol + // Get ListenerProtocol if (stampBinary.Length > 0) { Protocol = GetProtocol(stampBinary, out string protocolName); @@ -95,12 +96,19 @@ public DNSCryptStampReader(string stamp) } // Get IP, Port, Host, Path, PublicKey, ProviderName, Hashi, Bootstraps - bool isOk = DecryptTheRest(stampBinary, out string ip, out int port, out string host, out string path, out string publicKey, + bool isOk = DecryptTheRest(stampBinary, out string ipStr, out int port, out string host, out string path, out string publicKey, out string providerName, out List hashi, out List bootstraps); if (!isOk) return; - IP = ip; + bool isIpOk = IPAddress.TryParse(ipStr, out IPAddress? ip); + IP = isIpOk && ip != null ? ip : IPAddress.None; + if (IP == IPAddress.None && !string.IsNullOrEmpty(host)) + { + isIpOk = IPAddress.TryParse(host, out ip); + IP = isIpOk && ip != null ? ip : IPAddress.None; + } + Port = port; Host = host; Path = path; @@ -111,10 +119,7 @@ public DNSCryptStampReader(string stamp) IsDecryptionSuccess = true; } - catch (Exception) - { - // do nothing - } + catch (Exception) { } } else { @@ -124,6 +129,12 @@ public DNSCryptStampReader(string stamp) private static StampProtocol GetProtocol(byte[] stampBinary, out string protocolName) { + if (stampBinary.Length < 1) + { + protocolName = StampProtocolName.Unknown; + return StampProtocol.Unknown; + } + byte stampProtocol = stampBinary[0]; if (stampProtocol == 0x00) @@ -175,11 +186,21 @@ private static StampProtocol GetProtocol(byte[] stampBinary, out string protocol private static void GetStampProperties(byte[] stampBinary, out bool isDNSSec, out bool isNoLog, out bool isNoFilter) { - byte dnsCryptProperties = stampBinary[1]; + try + { + byte dnsCryptProperties = stampBinary[1]; - isDNSSec = Convert.ToBoolean((dnsCryptProperties >> 0) & 1); - isNoLog = Convert.ToBoolean((dnsCryptProperties >> 1) & 1); - isNoFilter = Convert.ToBoolean((dnsCryptProperties >> 2) & 1); + isDNSSec = Convert.ToBoolean((dnsCryptProperties >> 0) & 1); + isNoLog = Convert.ToBoolean((dnsCryptProperties >> 1) & 1); + isNoFilter = Convert.ToBoolean((dnsCryptProperties >> 2) & 1); + } + catch (Exception ex) + { + isDNSSec = false; + isNoLog = false; + isNoFilter = false; + Debug.WriteLine("DNSCryptStampReader GetStampProperties: " + ex.Message); + } } private bool DecryptTheRest(byte[] stampBinary, out string ip, out int port, out string host, out string path, out string publicKey, @@ -194,153 +215,160 @@ private bool DecryptTheRest(byte[] stampBinary, out string ip, out int port, out hashi = new List(); bootstraps = new List(); - int position = 0; - position += 1; // Skip Protocol - if (Protocol == StampProtocol.PlainDNS) + try { - position += 8; // Skip Properties + int position = 0; + position += 1; // Skip ListenerProtocol + if (Protocol == StampProtocol.PlainDNS) + { + position += 8; // Skip Properties - // LP(addr [:port]) - position = LPHostIpPort(position, stampBinary, DefaultPort.PlainDNS, out ip, out port); - if (position == -1) return false; - return true; - } - else if (Protocol == StampProtocol.DnsCrypt) - { - position += 8; // Skip Properties + // LP(addr [:port]) + position = LPHostIpPort(position, stampBinary, DefaultPort.PlainDNS, out ip, out port); + if (position == -1) return false; + return true; + } + else if (Protocol == StampProtocol.DnsCrypt) + { + position += 8; // Skip Properties - // LP(addr [:port]) - position = LPHostIpPort(position, stampBinary, DefaultPort.DnsCrypt, out ip, out port); - if (position == -1) return false; + // LP(addr [:port]) + position = LPHostIpPort(position, stampBinary, DefaultPort.DnsCrypt, out ip, out port); + if (position == -1) return false; - // LP(pk) - position = LPPublicKey(position, stampBinary, out publicKey); - if (position == -1) return false; + // LP(pk) + position = LPPublicKey(position, stampBinary, out publicKey); + if (position == -1) return false; - // LP(providerName)) - position = LP(position, stampBinary, out providerName); - if (position == -1) return false; - return true; - } - else if (Protocol == StampProtocol.DoH) - { - position += 8; // Skip Properties + // LP(providerName)) + position = LP(position, stampBinary, out providerName); + if (position == -1) return false; + return true; + } + else if (Protocol == StampProtocol.DoH) + { + position += 8; // Skip Properties - // LP(addr) - position = LPHostIpPort(position, stampBinary, DefaultPort.DoH, out ip, out port); - if (position == -1) return false; + // LP(addr) + position = LPHostIpPort(position, stampBinary, DefaultPort.DoH, out ip, out port); + if (position == -1) return false; - // VLP(hash1, hash2, ...hashn) - position = VLPHash(position, stampBinary, out hashi); - if (position == -1) return false; + // VLP(hash1, hash2, ...hashn) + position = VLPHash(position, stampBinary, out hashi); + if (position == -1) return false; - // LP(hostname[:port]) - position = LPHostIpPort(position, stampBinary, port, out host, out port); - if (position == -1) return false; + // LP(hostname[:port]) + position = LPHostIpPort(position, stampBinary, port, out host, out port); + if (position == -1) return false; - // LP(path) - position = LP(position, stampBinary, out path); - if (position == -1) return false; + // LP(path) + position = LP(position, stampBinary, out path); + if (position == -1) return false; - // VLP(bootstrap_ip1, bootstrap_ip2, ...bootstrap_ipn) - Optional - position = VLPBootstrap(position, stampBinary, out bootstraps); - if (position == -1) return false; - return true; - } - else if (Protocol == StampProtocol.DoT) - { - position += 8; // Skip Properties + // VLP(bootstrap_ip1, bootstrap_ip2, ...bootstrap_ipn) - Optional + position = VLPBootstrap(position, stampBinary, out bootstraps); + if (position == -1) return false; + return true; + } + else if (Protocol == StampProtocol.DoT) + { + position += 8; // Skip Properties - // LP(addr) - position = LPHostIpPort(position, stampBinary, DefaultPort.DoT, out ip, out port); - if (position == -1) return false; + // LP(addr) + position = LPHostIpPort(position, stampBinary, DefaultPort.DoT, out ip, out port); + if (position == -1) return false; - // VLP(hash1, hash2, ...hashn) - position = VLPHash(position, stampBinary, out hashi); - if (position == -1) return false; + // VLP(hash1, hash2, ...hashn) + position = VLPHash(position, stampBinary, out hashi); + if (position == -1) return false; - // LP(hostname [:port]) - position = LPHostIpPort(position, stampBinary, port, out host, out port); - if (position == -1) return false; + // LP(hostname [:port]) + position = LPHostIpPort(position, stampBinary, port, out host, out port); + if (position == -1) return false; - // VLP(bootstrap_ip1, bootstrap_ip2, ...bootstrap_ipn) - Optional - position = VLPBootstrap(position, stampBinary, out bootstraps); - if (position == -1) return false; - return true; - } - else if (Protocol == StampProtocol.DoQ) - { - position += 8; // Skip Properties + // VLP(bootstrap_ip1, bootstrap_ip2, ...bootstrap_ipn) - Optional + position = VLPBootstrap(position, stampBinary, out bootstraps); + if (position == -1) return false; + return true; + } + else if (Protocol == StampProtocol.DoQ) + { + position += 8; // Skip Properties - // LP(addr) - position = LPHostIpPort(position, stampBinary, DefaultPort.DoQ, out ip, out port); - if (position == -1) return false; + // LP(addr) + position = LPHostIpPort(position, stampBinary, DefaultPort.DoQ, out ip, out port); + if (position == -1) return false; - // VLP(hash1, hash2, ...hashn) - position = VLPHash(position, stampBinary, out hashi); - if (position == -1) return false; + // VLP(hash1, hash2, ...hashn) + position = VLPHash(position, stampBinary, out hashi); + if (position == -1) return false; - // LP(hostname [:port]) - position = LPHostIpPort(position, stampBinary, port, out host, out port); - if (position == -1) return false; + // LP(hostname [:port]) + position = LPHostIpPort(position, stampBinary, port, out host, out port); + if (position == -1) return false; - // VLP(bootstrap_ip1, bootstrap_ip2, ...bootstrap_ipn) - Optional - position = VLPBootstrap(position, stampBinary, out bootstraps); - if (position == -1) return false; - return true; - } - else if (Protocol == StampProtocol.ObliviousDohTarget) - { - position += 8; // Skip Properties + // VLP(bootstrap_ip1, bootstrap_ip2, ...bootstrap_ipn) - Optional + position = VLPBootstrap(position, stampBinary, out bootstraps); + if (position == -1) return false; + return true; + } + else if (Protocol == StampProtocol.ObliviousDohTarget) + { + position += 8; // Skip Properties - // LP(hostname [:port]) - position = LPHostIpPort(position, stampBinary, DefaultPort.ObliviousDohTarget, out host, out port); - if (position == -1) return false; + // LP(hostname [:port]) + position = LPHostIpPort(position, stampBinary, DefaultPort.ObliviousDohTarget, out host, out port); + if (position == -1) return false; - // LP(path) - position = LP(position, stampBinary, out path); - if (position == -1) return false; - return true; - } - else if (Protocol == StampProtocol.AnonymizedDNSCryptRelay) - { - // Anonymized DNSCrypt Relay doesn't have properties to skip + // LP(path) + position = LP(position, stampBinary, out path); + if (position == -1) return false; + return true; + } + else if (Protocol == StampProtocol.AnonymizedDNSCryptRelay) + { + // Anonymized DNSCrypt Relay doesn't have properties to skip - // LP(addr) - position = LPHostIpPort(position, stampBinary, DefaultPort.AnonymizedDNSCryptRelay, out ip, out port); - if (position == -1) return false; - return true; - } - else if (Protocol == StampProtocol.ObliviousDohRelay) - { - position += 8; // Skip Properties + // LP(addr) + position = LPHostIpPort(position, stampBinary, DefaultPort.AnonymizedDNSCryptRelay, out ip, out port); + if (position == -1) return false; + return true; + } + else if (Protocol == StampProtocol.ObliviousDohRelay) + { + position += 8; // Skip Properties - // LP(addr) - position = LPHostIpPort(position, stampBinary, DefaultPort.ObliviousDohRelay, out ip, out port); - if (position == -1) return false; + // LP(addr) + position = LPHostIpPort(position, stampBinary, DefaultPort.ObliviousDohRelay, out ip, out port); + if (position == -1) return false; - // VLP(hash1, hash2, ...hashn) - position = VLPHash(position, stampBinary, out hashi); - if (position == -1) return false; + // VLP(hash1, hash2, ...hashn) + position = VLPHash(position, stampBinary, out hashi); + if (position == -1) return false; - // LP(hostname [:port]) - position = LPHostIpPort(position, stampBinary, port, out host, out port); - if (position == -1) return false; + // LP(hostname [:port]) + position = LPHostIpPort(position, stampBinary, port, out host, out port); + if (position == -1) return false; - // LP(path) - position = LP(position, stampBinary, out path); - if (position == -1) return false; + // LP(path) + position = LP(position, stampBinary, out path); + if (position == -1) return false; - // VLP(bootstrap_ip1, bootstrap_ip2, ...bootstrap_ipn) - Optional - position = VLPBootstrap(position, stampBinary, out bootstraps); - if (position == -1) return false; - return true; + // VLP(bootstrap_ip1, bootstrap_ip2, ...bootstrap_ipn) - Optional + position = VLPBootstrap(position, stampBinary, out bootstraps); + if (position == -1) return false; + return true; + } + } + catch (Exception ex) + { + Debug.WriteLine("DNSCryptStampReader DecryptTheRest: " + ex.Message); } return true; } - private int LPHostIpPort(int position, byte[] stampBinary, int defaultPort, out string host, out int port) + private static int LPHostIpPort(int position, byte[] stampBinary, int defaultPort, out string host, out int port) { try { @@ -372,7 +400,7 @@ private int LPHostIpPort(int position, byte[] stampBinary, int defaultPort, out } } - private int LP(int position, byte[] stampBinary, out string outResult) + private static int LP(int position, byte[] stampBinary, out string outResult) { try { @@ -401,7 +429,7 @@ private int LP(int position, byte[] stampBinary, out string outResult) } } - private int LPPublicKey(int position, byte[] stampBinary, out string outResult) + private static int LPPublicKey(int position, byte[] stampBinary, out string outResult) { try { @@ -430,7 +458,7 @@ private int LPPublicKey(int position, byte[] stampBinary, out string outResult) } } - private int VLPHash(int position, byte[] stampBinary, out List hashi) + private static int VLPHash(int position, byte[] stampBinary, out List hashi) { try { @@ -477,7 +505,7 @@ private int VLPHash(int position, byte[] stampBinary, out List hashi) } } - private int VLPBootstrap(int position, byte[] stampBinary, out List bootstraps) + private static int VLPBootstrap(int position, byte[] stampBinary, out List bootstraps) { try { diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsDeDup.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DnsDeDup.cs similarity index 88% rename from MsmhToolsClass/MsmhToolsClass/DnsTool/DnsDeDup.cs rename to MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DnsDeDup.cs index ef5ea78..d0cc8a2 100644 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/DnsDeDup.cs +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DnsDeDup.cs @@ -1,7 +1,7 @@ -using MsmhToolsClass.DnsTool; -using System.Diagnostics; +using System.Diagnostics; +using System.Net; -namespace MsmhToolsClass.MsmhToolsClass.DnsTool; +namespace MsmhToolsClass.MsmhAgnosticServer; public class DnsDeDup { @@ -31,7 +31,7 @@ await Task.Run(async () => if (CTS.IsCancellationRequested) break; Status = $"Working on {n + 1} of {DnsList.Count}"; - + string dns1 = DnsList[n].Trim(); bool isSame = false; for (int n2 = 0; n2 < DedupDnsList.Count; n2++) @@ -52,7 +52,7 @@ await Task.Run(async () => if (!isSame) DedupDnsList.Add(dns1); } }); - + return DedupDnsList; } @@ -60,18 +60,18 @@ private async Task IsTheSame(string dns1, string dns2) { return await Task.Run(() => { - string ip1 = string.Empty; + IPAddress ip1 = IPAddress.None; DnsReader dr1 = new(dns1, null); NetworkTool.GetUrlDetails(dns1, dr1.Port, out string scheme1, out string host1, out _, out _, out int port1, out string path1, out _); if (dr1.IsDnsCryptStamp) { host1 = dr1.Host; - ip1 = dr1.IP ?? string.Empty; + ip1 = dr1.IP; port1 = dr1.Port; path1 = dr1.Path; } - string ip2 = string.Empty; + IPAddress ip2 = IPAddress.None; DnsReader dr2 = new(dns2, null); NetworkTool.GetUrlDetails(dns2, dr2.Port, out string scheme2, out string host2, out _, out _, out int port2, out string path2, out _); if (dr2.IsDnsCryptStamp) @@ -80,7 +80,7 @@ private async Task IsTheSame(string dns1, string dns2) if (dr1.Protocol == dr2.Protocol) scheme2 = scheme1; host2 = dr2.Host; - ip2 = dr2.IP ?? string.Empty; + ip2 = dr2.IP; port2 = dr2.Port; path2 = dr2.Path; } @@ -102,13 +102,13 @@ private async Task IsTheSame(string dns1, string dns2) if (dr1.IsDnsCryptStamp && !dr2.IsDnsCryptStamp) { - if (string.IsNullOrEmpty(host1)) host1 = dr1.IP ?? string.Empty; + if (string.IsNullOrEmpty(host1)) host1 = dr1.IP.ToString(); result = scheme1.Equals(scheme2) && host1.Equals(host2) && port1.Equals(port2) && path1.Equals(path2); } if (!dr1.IsDnsCryptStamp && dr2.IsDnsCryptStamp) { - if (string.IsNullOrEmpty(host2)) host2 = dr2.IP ?? string.Empty; + if (string.IsNullOrEmpty(host2)) host2 = dr2.IP.ToString(); result = scheme1.Equals(scheme2) && host1.Equals(host2) && port1.Equals(port2) && path1.Equals(path2); } diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DnsReader.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DnsReader.cs new file mode 100644 index 0000000..5b586f9 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/DnsReader.cs @@ -0,0 +1,178 @@ +using System.Diagnostics; +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class DnsReader +{ + public string Dns { get; private set; } = string.Empty; + public string Scheme { get; private set; } = string.Empty; + public string Host { get; private set; } = string.Empty; + public bool IsHostIP { get; private set; } = false; + public IPAddress IP { get; private set; } = IPAddress.None; + public int Port { get; private set; } + public string Path { get; private set; } = string.Empty; + public string CompanyName { get; private set; } = string.Empty; + private string CompanyNameDataFileContent { get; set; } = string.Empty; + public DnsEnums.DnsProtocol Protocol { get; private set; } = DnsEnums.DnsProtocol.Unknown; + public string ProtocolName { get; private set; } = string.Empty; + public bool IsDnsCryptStamp { get; private set; } = false; + public DNSCryptStampReader StampReader { get; private set; } = new(string.Empty); + + public DnsReader() { } + + /// + /// Read any DNS + /// + /// DNS Address + /// File content to get company name. each line e.g. 8.8.8.8|Google Inc. + public DnsReader(string dns, string? companyNameDataFileContent = null) + { + Dns = dns; + + if (!string.IsNullOrEmpty(companyNameDataFileContent)) + CompanyNameDataFileContent = companyNameDataFileContent; + + Protocol = DnsEnums.DnsProtocol.Unknown; + ProtocolName = DnsEnums.DnsProtocolName.Unknown; + + if (string.IsNullOrEmpty(Dns)) return; + + try + { + if (dns.ToLower().StartsWith("sdns://")) + { + IsDnsCryptStamp = true; + + // Decode Stamp + DNSCryptStampReader stamp = new(dns); + if (stamp != null && stamp.IsDecryptionSuccess) + { + Scheme = "stamp://"; + Host = stamp.Host; + if (!string.IsNullOrEmpty(Host)) + IsHostIP = IPAddress.TryParse(Host, out _); + IP = stamp.IP; + Port = stamp.Port; + Path = stamp.Path; + Protocol = ParseProtocol(stamp.Protocol); + + if (Protocol == DnsEnums.DnsProtocol.DoH || Protocol == DnsEnums.DnsProtocol.ObliviousDohTarget) Scheme = "https://"; + else if (Protocol == DnsEnums.DnsProtocol.DoQ) Scheme = "quic://"; + else if (Protocol == DnsEnums.DnsProtocol.DoT) Scheme = "tls://"; + else if (Protocol == DnsEnums.DnsProtocol.TCP) Scheme = "tcp://"; + else if (Protocol == DnsEnums.DnsProtocol.UDP) Scheme = "udp://"; + + ProtocolName = stamp.ProtocolName; + StampReader = stamp; + + // Get Company Name (SDNS) + string stampHost = stamp.Host; + if (string.IsNullOrEmpty(stampHost)) stampHost = stamp.IP.ToString(); + if (string.IsNullOrEmpty(stampHost)) stampHost = stamp.ProviderName; + if (!string.IsNullOrEmpty(CompanyNameDataFileContent)) + CompanyName = GetCompanyName.HostToCompanyOffline(stampHost, CompanyNameDataFileContent); + } + } + else + { + if (dns.ToLower().StartsWith("https://") || dns.ToLower().StartsWith("http://")) + { + // DoH + SetIpPortHostPath(dns, 443); + + Protocol = DnsEnums.DnsProtocol.DoH; + ProtocolName = DnsEnums.DnsProtocolName.DoH; + } + else if (dns.ToLower().StartsWith("tls://")) + { + // TLS + SetIpPortHostPath(dns, 853); + + Protocol = DnsEnums.DnsProtocol.DoT; + ProtocolName = DnsEnums.DnsProtocolName.DoT; + } + else if (dns.ToLower().StartsWith("quic://")) + { + // DoQ + SetIpPortHostPath(dns, 853); + + Protocol = DnsEnums.DnsProtocol.DoQ; + ProtocolName = DnsEnums.DnsProtocolName.DoQ; + } + else if (dns.ToLower().StartsWith("udp://")) + { + // Plain DNS UDP + SetIpPortHostPath(dns, 53); + + Protocol = DnsEnums.DnsProtocol.UDP; + ProtocolName = DnsEnums.DnsProtocolName.UDP; + } + else if (dns.ToLower().StartsWith("tcp://")) + { + // Plain DNS TCP + SetIpPortHostPath(dns, 53); + + Protocol = DnsEnums.DnsProtocol.TCP; + ProtocolName = DnsEnums.DnsProtocolName.TCP; + } + else + { + // Plain DNS UDP + SetIpPortHostPath(dns, 53); + + Protocol = DnsEnums.DnsProtocol.UDP; + ProtocolName = DnsEnums.DnsProtocolName.UDP; + } + } + } + catch (Exception ex) + { + Debug.WriteLine("DnsReader: " + ex.Message); + } + } + + private void SetIpPortHostPath(string dns, int defaultPort) + { + try + { + NetworkTool.GetUrlDetails(dns, defaultPort, out string scheme, out string host, out _, out _, out int port, out string path, out bool isIPv6); + Scheme = scheme; + Host = host; + IsHostIP = IPAddress.TryParse(Host, out _); + Port = port; + Path = path; + + if (!string.IsNullOrEmpty(CompanyNameDataFileContent)) + { + string? ipOrHost = Host; + if (string.IsNullOrEmpty(ipOrHost)) ipOrHost = IP.ToString(); + if (string.IsNullOrEmpty(ipOrHost)) ipOrHost = host; + CompanyName = GetCompanyName.HostToCompanyOffline(ipOrHost, CompanyNameDataFileContent); + } + } + catch (Exception ex) + { + Debug.WriteLine("DnsReader SetIpPortHostPath: " + ex.Message); + } + } + + private static DnsEnums.DnsProtocol ParseProtocol(DNSCryptStampReader.StampProtocol stampProtocol) + { + var protocol = stampProtocol switch + { + DNSCryptStampReader.StampProtocol.PlainDNS => DnsEnums.DnsProtocol.UDP, + DNSCryptStampReader.StampProtocol.DnsCrypt => DnsEnums.DnsProtocol.DnsCrypt, + DNSCryptStampReader.StampProtocol.DoH => DnsEnums.DnsProtocol.DoH, + DNSCryptStampReader.StampProtocol.DoT => DnsEnums.DnsProtocol.DoT, + DNSCryptStampReader.StampProtocol.DoQ => DnsEnums.DnsProtocol.DoQ, + DNSCryptStampReader.StampProtocol.ObliviousDohTarget => DnsEnums.DnsProtocol.ObliviousDohTarget, + DNSCryptStampReader.StampProtocol.AnonymizedDNSCryptRelay => DnsEnums.DnsProtocol.AnonymizedDNSCryptRelay, + DNSCryptStampReader.StampProtocol.ObliviousDohRelay => DnsEnums.DnsProtocol.ObliviousDohRelay, + DNSCryptStampReader.StampProtocol.Unknown => DnsEnums.DnsProtocol.Unknown, + _ => DnsEnums.DnsProtocol.Unknown, + }; + return protocol; + } + +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/DnsTool/GetCompanyName.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/GetCompanyName.cs similarity index 90% rename from MsmhToolsClass/MsmhToolsClass/DnsTool/GetCompanyName.cs rename to MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/GetCompanyName.cs index 8d5e5ca..95070c5 100644 --- a/MsmhToolsClass/MsmhToolsClass/DnsTool/GetCompanyName.cs +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/GetCompanyName.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace MsmhToolsClass.DnsTool; +namespace MsmhToolsClass.MsmhAgnosticServer; public static class GetCompanyName { @@ -49,8 +49,8 @@ public static string StampToCompanyOffline(string stampUrl, string fileContent) if (!string.IsNullOrEmpty(stamp.Host)) company = HostToCompanyOffline(stamp.Host, fileContent); - else if (!string.IsNullOrEmpty(stamp.IP)) - company = HostToCompanyOffline(stamp.IP, fileContent); + else if (!string.IsNullOrEmpty(stamp.IP.ToString())) + company = HostToCompanyOffline(stamp.IP.ToString(), fileContent); else company = HostToCompanyOffline(stampUrl, fileContent); } @@ -61,4 +61,5 @@ public static string StampToCompanyOffline(string stampUrl, string fileContent) return company; } -} \ No newline at end of file +} + diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/GetIP.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/GetIP.cs new file mode 100644 index 0000000..ddb58c4 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTool/GetIP.cs @@ -0,0 +1,245 @@ +using System.Diagnostics; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public static class GetIP +{ + /// + /// Get First IP in Answer Section + /// + /// Host + /// Look for IPv6 + /// Returns IPAddress.None/IPAddress.IPv6None If Fail + public static IPAddress GetIpFromSystem(string host, bool getIPv6 = false, bool alsoUsePing = true) + { + List ips = GetIpsFromSystem(host, getIPv6); + if (ips.Count != 0) return ips[0]; + else + { + if (alsoUsePing) + { + try + { + using Ping ping = new(); + PingReply reply = ping.Send(host, 3000); + IPAddress ip = reply.Address; + bool isIpv6 = NetworkTool.IsIPv6(ip); + + if (getIPv6 && isIpv6 && !IPAddress.IsLoopback(ip)) return ip; + if (!getIPv6 && !isIpv6 && !IPAddress.IsLoopback(ip)) return ip; + } + catch (Exception) { } + } + } + + return getIPv6 ? IPAddress.IPv6None : IPAddress.None; + } + + /// + /// Get a List of IPs + /// + /// Host + /// Look for IPv6 + /// Returns Empty List If Fail + public static List GetIpsFromSystem(string host, bool getIPv6 = false) + { + List ips = new(); + + try + { + //IPAddress[] ipAddresses = System.Net.Dns.GetHostAddresses(host); + IPHostEntry ipHostEntry = Dns.GetHostEntry(host); + IPAddress[] ipAddresses = ipHostEntry.AddressList; + + if (ipAddresses == null || ipAddresses.Length == 0) return ips; + + if (!getIPv6) + { + for (int n = 0; n < ipAddresses.Length; n++) + { + AddressFamily addressFamily = ipAddresses[n].AddressFamily; + IPAddress ip = ipAddresses[n]; + if (addressFamily != AddressFamily.InterNetworkV6) + if (!string.IsNullOrEmpty(ip.ToString()) && !NetworkTool.IsLocalIP(ip.ToString()) && !IPAddress.IsLoopback(ip)) + ips.Add(ip); + } + } + else + { + for (int n = 0; n < ipAddresses.Length; n++) + { + AddressFamily addressFamily = ipAddresses[n].AddressFamily; + IPAddress ip = ipAddresses[n]; + if (addressFamily == AddressFamily.InterNetworkV6) + if (!string.IsNullOrEmpty(ip.ToString()) && !IPAddress.IsLoopback(ip)) + ips.Add(ip); + } + } + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + + return ips; + } + + /// + /// Get IPs in Answer Section (Using Wire Format) + /// + /// Host + /// A List Of DNS Servers + /// Timeout (Sec) + /// Use Proxy to Get IP + /// Returns An Empty List If Fail + public static async Task> GetIPsFromDnsAddressAsync(string host, List dnss, bool allowInsecure, int timeoutSec, bool getIPv6, IPAddress bootstrapIP, int bootstrapPort, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) + { + List ips = new(); + DnsEnums.RRType rrType = getIPv6 ? DnsEnums.RRType.AAAA : DnsEnums.RRType.A; + DnsMessage dmQ = DnsMessage.CreateQuery(DnsEnums.DnsProtocol.UDP, host, rrType, DnsEnums.CLASS.IN); + bool isWriteSuccess = DnsMessage.TryWrite(dmQ, out byte[] dmQBuffer); + if (isWriteSuccess) + { + byte[] dmABuffer = await DnsClient.QueryAsync(dmQBuffer, DnsEnums.DnsProtocol.UDP, dnss, allowInsecure, bootstrapIP, bootstrapPort, timeoutSec * 1000, proxyScheme, proxyUser, proxyPass); + DnsMessage dmA = DnsMessage.Read(dmABuffer, DnsEnums.DnsProtocol.UDP); + + if (dmA.IsSuccess && dmA.Header.AnswersCount > 0) + { + foreach (IResourceRecord irr in dmA.Answers.AnswerRecords) + { + if (getIPv6) + { + if (irr is not AaaaRecord aaaaRecord) continue; + bool isLoopbackIP = IPAddress.IsLoopback(aaaaRecord.IP); + if (!isLoopbackIP) ips.Add(aaaaRecord.IP); + } + else + { + if (irr is not ARecord aRecord) continue; + string ipStr = aRecord.IP.ToString(); + bool isLocalIP = NetworkTool.IsLocalIP(ipStr); + bool isLoopbackIP = IPAddress.IsLoopback(aRecord.IP); + if (!isLocalIP && !isLoopbackIP) ips.Add(aRecord.IP); + } + } + } + } + return ips; + } + + /// + /// Get IPs in Answer Section (Using Wire Format) + /// + /// Host + /// A List Of DNS Servers + /// Returns An Empty List If Fail + public static async Task> GetIPsFromDnsAddressAsync(string host, List dnss, bool getIPv6, AgnosticSettings s) + { + return await GetIPsFromDnsAddressAsync(host, dnss, s.AllowInsecure, s.DnsTimeoutSec, getIPv6, s.BootstrapIpAddress, s.BootstrapPort, s.UpstreamProxyScheme, s.UpstreamProxyUser, s.UpstreamProxyPass); + } + + /// + /// Get IPs in Answer Section (Using Wire Format) + /// + /// Host + /// A DNS Server + /// Timeout (Sec) + /// Use Proxy to Get IP + /// Returns An Empty List If Fail + public static async Task> GetIPsFromDnsAddressAsync(string host, string dns, bool allowInsecure, int timeoutSec, bool getIPv6, IPAddress bootstrapIP, int bootstrapPort, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) + { + return await GetIPsFromDnsAddressAsync(host, new List() { dns }, allowInsecure, timeoutSec, getIPv6, bootstrapIP, bootstrapPort, proxyScheme, proxyUser, proxyPass); + } + + /// + /// Get IPs in Answer Section (Using Wire Format) + /// + /// Host + /// A DNS Server + /// Returns An Empty List If Fail + public static async Task> GetIPsFromDnsAddressAsync(string host, string dns, bool getIPv6, AgnosticSettings s) + { + return await GetIPsFromDnsAddressAsync(host, new List() { dns }, s.AllowInsecure, s.DnsTimeoutSec, getIPv6, s.BootstrapIpAddress, s.BootstrapPort, s.UpstreamProxyScheme, s.UpstreamProxyUser, s.UpstreamProxyPass); + } + + /// + /// Get First IP in Answer Section (Using Wire Format) + /// + /// Host + /// A List Of DNS Servers + /// Timeout (Sec) + /// Use Proxy to Get IP + /// Returns IPAddress.None/IPAddress.IPv6None If Fail + public static async Task GetIpFromDnsAddressAsync(string host, List dnss, bool allowInsecure, int timeoutSec, bool getIPv6, IPAddress bootstrapIP, int bootstrapPort, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) + { + DnsEnums.RRType rrType = getIPv6 ? DnsEnums.RRType.AAAA : DnsEnums.RRType.A; + DnsMessage dmQ = DnsMessage.CreateQuery(DnsEnums.DnsProtocol.UDP, host, rrType, DnsEnums.CLASS.IN); + bool isWriteSuccess = DnsMessage.TryWrite(dmQ, out byte[] dmQBuffer); + if (isWriteSuccess) + { + byte[] dmABuffer = await DnsClient.QueryAsync(dmQBuffer, DnsEnums.DnsProtocol.UDP, dnss, allowInsecure, bootstrapIP, bootstrapPort, timeoutSec * 1000, proxyScheme, proxyUser, proxyPass); + DnsMessage dmA = DnsMessage.Read(dmABuffer, DnsEnums.DnsProtocol.UDP); + + if (dmA.IsSuccess && dmA.Header.AnswersCount > 0) + { + foreach (IResourceRecord irr in dmA.Answers.AnswerRecords) + { + if (getIPv6) + { + if (irr is not AaaaRecord aaaaRecord) continue; + bool isLoopbackIP = IPAddress.IsLoopback(aaaaRecord.IP); + if (!isLoopbackIP) return aaaaRecord.IP; + } + else + { + if (irr is not ARecord aRecord) continue; + string ipStr = aRecord.IP.ToString(); + bool isLocalIP = NetworkTool.IsLocalIP(ipStr); + bool isLoopbackIP = IPAddress.IsLoopback(aRecord.IP); + if (!isLocalIP && !isLoopbackIP) return aRecord.IP; + } + } + } + } + return getIPv6 ? IPAddress.IPv6None : IPAddress.None; + } + + /// + /// Get First IP in Answer Section (Using Wire Format) + /// + /// Host + /// A List Of DNS Servers + /// Returns IPAddress.None/IPAddress.IPv6None If Fail + public static async Task GetIpFromDnsAddressAsync(string host, List dnss, bool getIPv6, AgnosticSettings s) + { + return await GetIpFromDnsAddressAsync(host, dnss, s.AllowInsecure, s.DnsTimeoutSec, getIPv6, s.BootstrapIpAddress, s.BootstrapPort, s.UpstreamProxyScheme, s.UpstreamProxyUser, s.UpstreamProxyPass); + } + + /// + /// Get First IP in Answer Section (Using Wire Format) + /// + /// Host + /// A DNS Server + /// Timeout (Sec) + /// Use Proxy to Get IP + /// Returns IPAddress.None/IPAddress.IPv6None If Fail + public static async Task GetIpFromDnsAddressAsync(string host, string dns, bool allowInsecure, int timeoutSec, bool getIPv6, IPAddress bootstrapIP, int bootstrapPort, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) + { + return await GetIpFromDnsAddressAsync(host, new List() { dns }, allowInsecure, timeoutSec, getIPv6, bootstrapIP, bootstrapPort, proxyScheme, proxyUser, proxyPass); + } + + /// + /// Get First IP in Answer Section (Using Wire Format) + /// + /// Host + /// A DNS Server + /// Returns IPAddress.None/IPAddress.IPv6None If Fail + public static async Task GetIpFromDnsAddressAsync(string host, string dns, bool getIPv6, AgnosticSettings s) + { + return await GetIpFromDnsAddressAsync(host, new List() { dns }, s.AllowInsecure, s.DnsTimeoutSec, getIPv6, s.BootstrapIpAddress, s.BootstrapPort, s.UpstreamProxyScheme, s.UpstreamProxyUser, s.UpstreamProxyPass); + } + +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTunnel.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTunnel.cs new file mode 100644 index 0000000..778bfb5 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/DnsServer/DnsTunnel.cs @@ -0,0 +1,184 @@ +using System.Diagnostics; +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class DnsTunnel +{ + public static async Task Process(AgnosticResult aResult, AgnosticProgram.DnsRules dnsRulesProgram, DnsCache dnsCaches, AgnosticSettings settings, EventHandler? onRequestReceived) + { + DnsEnums.DnsProtocol dnsProtocol = aResult.Protocol switch + { + RequestProtocol.UDP => DnsEnums.DnsProtocol.UDP, + RequestProtocol.TCP => DnsEnums.DnsProtocol.TCP, + RequestProtocol.DoH => DnsEnums.DnsProtocol.DoH, + _ => DnsEnums.DnsProtocol.Unknown + }; + + // Event + string msgReqEvent = $"[{aResult.Local_EndPoint.Address}] [{dnsProtocol}] "; + + // Create Request + DnsMessage dmQ = DnsMessage.Read(aResult.FirstBuffer, dnsProtocol); + if (dmQ.IsSuccess && dmQ.Header.QuestionsCount > 0 && dmQ.Questions.QuestionRecords.Count > 0) + { + DnsRequest dnsRequest = new(aResult.Socket, aResult.Ssl_Stream, aResult.Ssl_Kind, aResult.Local_EndPoint, aResult.Remote_EndPoint, aResult.FirstBuffer, dnsProtocol); + string addressQ = dmQ.Questions.QuestionRecords[0].QNAME; + DnsEnums.RRType typeQ = dmQ.Questions.QuestionRecords[0].QTYPE; + + msgReqEvent += $"Q: {addressQ}, A: "; + + bool isCached = dnsCaches.TryGet(dmQ, out DnsMessage dmR); + bool usedCache = false; + if (isCached) + { + if (dmR.IsSuccess) + { + bool isTryWriteSuccess = DnsMessage.TryWrite(dmR, out byte[] responseCached); + //Debug.WriteLine("========== IsTryWriteSuccess: " + isTryWriteSuccess); + if (isTryWriteSuccess) + { + DnsMessage validate = DnsMessage.Read(responseCached, dnsRequest.Protocol); + if (validate.IsSuccess) + { + await dnsRequest.SendToAsync(responseCached).ConfigureAwait(false); + usedCache = true; + } + } + } + + if (!usedCache) + { + // TTL Expired - Remove From Cache + dnsCaches.TryRemove(dmQ); + } + } + //Debug.WriteLine("========== Used Cache: " + usedCache); + + if (!usedCache) + { + // Apply DnsRules Program + AgnosticProgram.DnsRules.DnsRulesResult drr = new(); + if (dnsRulesProgram.RulesMode != AgnosticProgram.DnsRules.Mode.Disable) + { + drr = await dnsRulesProgram.GetAsync(aResult.Local_EndPoint.Address.ToString(), addressQ, settings).ConfigureAwait(false); + } + + bool usedFakeOrCustom = false; + if (drr.IsMatch) + { + // Black List + if (drr.IsBlackList) + { + await dnsRequest.SendFailedResponseAsync().ConfigureAwait(false); + usedFakeOrCustom = true; + + msgReqEvent += "Request Denied - Black List"; + onRequestReceived?.Invoke(msgReqEvent, EventArgs.Empty); + return; + } + + // If Custom Dns Couldn't Get An IP + if (string.IsNullOrEmpty(drr.Dns)) + { + await dnsRequest.SendFailedResponseAsync().ConfigureAwait(false); + usedFakeOrCustom = true; + + msgReqEvent += "Request Denied - Your Dns Rule Couldn't Get An IP!"; + onRequestReceived?.Invoke(msgReqEvent, EventArgs.Empty); + return; + } + + // Fake DNS / Dns Domain / Custom Dns Or Smart DNS + bool isDnsIp = NetworkTool.IsIp(drr.Dns, out IPAddress? dnsIp); + if (isDnsIp && dnsIp != null) + { + bool isDnsIpv6 = NetworkTool.IsIPv6(dnsIp); + if (isDnsIpv6) + { + // IPv6 + if (typeQ == DnsEnums.RRType.AAAA) + { + dmR = DnsMessage.CreateResponse(dmQ, 1, 0, 0); + dmR.Answers.AnswerRecords.Add(new AaaaRecord(addressQ, 60, dnsIp)); + + bool isTryWriteSuccess = DnsMessage.TryWrite(dmR, out byte[] aBuffer); + if (isTryWriteSuccess) + { + await dnsRequest.SendToAsync(aBuffer).ConfigureAwait(false); + usedFakeOrCustom = true; + bool cacheSuccess = dnsCaches.TryAdd(dmQ, dmR); + Debug.WriteLine("ADDED TO CACHE 1: " + cacheSuccess); + } + } + } + else + { + // IPv4 + if (typeQ == DnsEnums.RRType.A) + { + dmR = DnsMessage.CreateResponse(dmQ, 1, 0, 0); + dmR.Answers.AnswerRecords.Add(new ARecord(addressQ, 60, dnsIp)); + + bool isTryWriteSuccess = DnsMessage.TryWrite(dmR, out byte[] aBuffer); + if (isTryWriteSuccess) + { + await dnsRequest.SendToAsync(aBuffer).ConfigureAwait(false); + usedFakeOrCustom = true; + bool cacheSuccess = dnsCaches.TryAdd(dmQ, dmR); + Debug.WriteLine("ADDED TO CACHE 2: " + cacheSuccess); + } + } + } + } + } + + if (!usedFakeOrCustom && settings.DNSs.Count > 0) + { + byte[] response = await DnsClient.QueryAsync(dnsRequest.Buffer, dnsRequest.Protocol, settings).ConfigureAwait(false); + dmR = DnsMessage.Read(response, dnsRequest.Protocol); + if (dmR.IsSuccess) + { + await dnsRequest.SendToAsync(response).ConfigureAwait(false); + bool cacheSuccess = dnsCaches.TryAdd(dmQ, dmR); + Debug.WriteLine("ADDED TO CACHE 3: " + cacheSuccess); + } + else + { + await dnsRequest.SendFailedResponseAsync().ConfigureAwait(false); + } + } + } + + // Event + List answers = new(); + if (dmR.IsSuccess) + { + foreach (IResourceRecord answer in dmR.Answers.AnswerRecords) + { + if (answer is ARecord aRecord) answers.Add(aRecord.IP.ToString()); + else if (answer is AaaaRecord aaaaRecord) answers.Add(aaaaRecord.IP.ToString()); + //else if (answer is CNameRecord cNameRecord) answers.Add(cNameRecord.CName); + else + { + string a = answer.TYPE.ToString(); + if (!answers.IsContain(a)) + { + answers.Add(answer.CLASS.ToString()); + answers.Add(answer.TimeToLive.ToString()); + answers.Add(a); + } + } + } + } + + if (answers.Count != 0) + { + msgReqEvent += answers.ToString(", "); + onRequestReceived?.Invoke(msgReqEvent, EventArgs.Empty); + } + + //Debug.WriteLine(dmR.ToString()); + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/MsmhAgnosticServer.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/MsmhAgnosticServer.cs new file mode 100644 index 0000000..4b8fc37 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/MsmhAgnosticServer.cs @@ -0,0 +1,674 @@ +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +// MSMH Agnostic Server - CopyRight GPLv3 MSasanMH (msasanmh@gmail.com) 2023 - 2024 + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public partial class MsmhAgnosticServer +{ + //======================================= Fragment Support: Static + public static AgnosticProgram.Fragment StaticFragmentProgram { get; set; } = new(); + public void EnableStaticFragment(AgnosticProgram.Fragment fragmentProgram) + { + StaticFragmentProgram = fragmentProgram; + } + + //--- Constant + public AgnosticProgram.Fragment FragmentProgram = new(); + public void EnableFragment(AgnosticProgram.Fragment fragmentProgram) + { + FragmentProgram = fragmentProgram; + } + + //======================================= DnsRules Support + public AgnosticProgram.DnsRules DnsRulesProgram = new(); + public void EnableDnsRules(AgnosticProgram.DnsRules dnsRules) + { + DnsRulesProgram = dnsRules; + } + + //======================================= ProxyRules Support + public AgnosticProgram.ProxyRules ProxyRulesProgram = new(); + public void EnableProxyRules(AgnosticProgram.ProxyRules proxyRules) + { + ProxyRulesProgram = proxyRules; + } + + // ====================================== Const + internal static readonly int MaxDataSize = 65536; + internal static readonly string DnsMessageContentType = "application/dns-message"; + internal static readonly int DNS_HEADER_LENGTH = 12; + internal static readonly SslProtocols SSL_Protocols = SslProtocols.None | SslProtocols.Tls12 | SslProtocols.Tls13; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "")] + internal static bool Callback(object sender, X509Certificate? cert, X509Chain? chain, SslPolicyErrors sslPolicyErrors) => true; + + //======================================= Start Server + internal AgnosticSettings Settings_ = new(); + public AgnosticSettingsSSL SettingsSSL_ { get; internal set; } = new(false); + + private Socket? UdpSocket_; + private readonly DnsCache DnsCaches = new(); + private readonly ProxyRequestsCache ProxyRequestsCaches = new(); + private TcpListener? TcpListener_; + internal TunnelManager TunnelManager_ = new(); + + private CancellationTokenSource? CancelTokenSource_; + private CancellationToken CancelToken_; + private CancellationTokenSource CTS_PR = new(); + + private System.Timers.Timer KillOnOverloadTimer { get; set; } = new(5000); + private float CpuUsage { get; set; } = -1; + + private bool Cancel { get; set; } = false; + + private Thread? MainThread; + + public event EventHandler? OnRequestReceived; + public event EventHandler? OnDebugInfoReceived; + + public Stats Stats { get; private set; } = new(); + public bool IsRunning { get; private set; } = false; + + private readonly ConcurrentQueue MaxRequestsQueue = new(); + private readonly ConcurrentDictionary DelinquentRequests = new(); + private readonly ConcurrentDictionary TestRequests = new(); + internal static readonly int MaxRequestsDelay = 50; + internal static readonly int MaxRequestsDivide = 20; // 20 * 50 = 1000 ms + + public MsmhAgnosticServer() { } + + public async Task EnableSSL(AgnosticSettingsSSL settingsSSL) + { + SettingsSSL_ = settingsSSL; + await SettingsSSL_.Build().ConfigureAwait(false); + } + + public void Start(AgnosticSettings settings) + { + if (IsRunning) return; + IsRunning = true; + + Settings_ = settings; + Settings_.Initialize(); + + // Set Default DNSs + if (Settings_.DNSs.Count == 0) Settings_.DNSs = AgnosticSettings.DefaultDNSs(); + + Stats = new Stats(); + + Welcome(); + + TunnelManager_ = new(); + + CancelTokenSource_ = new(); + CancelToken_ = CancelTokenSource_.Token; + + Cancel = false; + + MaxRequestsTimer(); + + KillOnOverloadTimer.Elapsed += KillOnOverloadTimer_Elapsed; + KillOnOverloadTimer.Start(); + + ThreadStart threadStart = new(AcceptConnections); + MainThread = new(threadStart); + if (OperatingSystem.IsWindows()) MainThread.SetApartmentState(ApartmentState.STA); + MainThread.Start(); + } + + private void Welcome() + { + // Event + string msgEvent = $"Server Starting On Port:{Settings_.ListenerPort}"; + OnRequestReceived?.Invoke(msgEvent, EventArgs.Empty); + OnDebugInfoReceived?.Invoke(msgEvent, EventArgs.Empty); + } + + private async void MaxRequestsTimer() + { + await Task.Run(async () => + { + while (true) + { + if (Cancel) break; + await Task.Delay(MaxRequestsDelay); + + try + { + bool peek = MaxRequestsQueue.TryPeek(out DateTime dt); + if (peek) + { + double diff = (DateTime.UtcNow - dt).TotalMilliseconds; + if (diff > 1000 / MaxRequestsDivide) // Dequeue If Older Than 50 ms (1000 / 20) + MaxRequestsQueue.TryDequeue(out _); + } + } + catch (Exception) { } + } + }); + } + + private async void KillOnOverloadTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) + { + if (OperatingSystem.IsWindows() && typeof(PerformanceCounter) != null) + CpuUsage = await ProcessManager.GetCpuUsage(Environment.ProcessId, 1000); + + if (CpuUsage >= Settings_.KillOnCpuUsage && Settings_.KillOnCpuUsage > 0) + { + KillAll(); + } + + if (CpuUsage >= 95f) + { + try { Environment.Exit(0); } catch (Exception) { } + ProcessManager.KillProcessByPID(Environment.ProcessId); + } + } + + /// + /// Kill all active requests + /// + public void KillAll() + { + try + { + CTS_PR.Cancel(); + ProxyRequestsCaches.Clear(); + if (TunnelManager_ != null) + { + var dic = TunnelManager_.GetTunnels(); + Debug.WriteLine(dic.Count); + foreach (var item in dic) + { + Debug.WriteLine(item.Key); + TunnelManager_.Remove(item.Value.Value); + } + } + } + catch (Exception ex) + { + Debug.WriteLine("MsmhAgnosticServer KillAll: " + ex.Message); + } + } + + public void Stop() + { + if (IsRunning && CancelTokenSource_ != null) + { + try + { + IsRunning = false; + CancelTokenSource_.Cancel(true); + Cancel = true; + UdpSocket_?.Shutdown(SocketShutdown.Both); + UdpSocket_?.Dispose(); + TcpListener_?.Stop(); + TcpListener_ = null; + + KillAll(); + + MaxRequestsQueue.Clear(); + DelinquentRequests.Clear(); + TestRequests.Clear(); + + KillOnOverloadTimer.Stop(); + Goodbye(); + } + catch (Exception ex) + { + Debug.WriteLine("Stop MsmhAgnosticServer: " + ex.GetInnerExceptions()); + } + } + } + + private void Goodbye() + { + // Event + string msgEvent = "Proxy Server Stopped."; + OnRequestReceived?.Invoke(msgEvent, EventArgs.Empty); + OnDebugInfoReceived?.Invoke(msgEvent, EventArgs.Empty); + } + + public int ListeningPort => Settings_.ListenerPort; + public bool IsFragmentActive => FragmentProgram.FragmentMode != AgnosticProgram.Fragment.Mode.Disable || StaticFragmentProgram.FragmentMode != AgnosticProgram.Fragment.Mode.Disable; + public bool IsFakeSniActive => SettingsSSL_.EnableSSL && SettingsSSL_.ChangeSni; + public int ActiveProxyTunnels => TunnelManager_.Count; + public int MaxRequests => Settings_.MaxRequests; + public int CachedDnsRequests => DnsCaches.CachedRequests; + + public void FlushDnsCache() + { + DnsCaches.Flush(); + } + + private async void AcceptConnections() + { + if (Cancel) return; + + try + { + if (Settings_.ListenerIP == null) + { + string msg = "Neither IPv4 Nor IPv6 Is Supported By Your OS!"; + OnRequestReceived?.Invoke(msg, EventArgs.Empty); + Stop(); + return; + } + + // EndPoint + IPEndPoint ipEndPoint = Settings_.ServerEndPoint; + + // UDP + UdpSocket_ = new(ipEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + if (ipEndPoint.Address.Equals(IPAddress.IPv6Any)) + { + UdpSocket_.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0); + UdpSocket_.DualMode = true; + } + UdpSocket_.Bind(ipEndPoint); + + // TCP + TcpListener_ = new(ipEndPoint); + SocketOptionName socketOptionName = SocketOptionName.ReuseAddress | SocketOptionName.ReuseUnicastPort; + TcpListener_.Server.SetSocketOption(SocketOptionLevel.Socket, socketOptionName, true); + if (ipEndPoint.Address.Equals(IPAddress.IPv6Any)) + { + TcpListener_.Server.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0); + TcpListener_.Server.DualMode = true; + } + TcpListener_.Start(); + + await Task.Delay(200); + + if (UdpSocket_ != null && TcpListener_ != null) + { + IsRunning = TcpListener_.Server.IsBound && UdpSocket_.IsBound; + if (!IsRunning) + { + UdpSocket_.Dispose(); + TcpListener_.Stop(); + Stop(); + return; + } + + // Run UDP & TCP Listener In Parallel + udp(UdpSocket_); + tcp(TcpListener_); + + // UDP + async void udp(Socket udpSocket) + { + while (!Cancel) + { + try + { + byte[] buffer = new byte[MaxDataSize]; + SocketReceiveMessageFromResult udpMessage = await udpSocket.ReceiveMessageFromAsync(buffer, SocketFlags.None, ipEndPoint).ConfigureAwait(false); + if (CpuUsage < Settings_.KillOnCpuUsage || Settings_.KillOnCpuUsage <= 0) + { + if (udpMessage.ReceivedBytes > 0) + { + buffer = buffer[..udpMessage.ReceivedBytes]; + //Debug.WriteLine(" UDP ===> " + BitConverter.ToString(buffer)); + AgnosticRequest agnosticRequest = new() + { + Udp_Socket = udpSocket, + Local_EndPoint = udpSocket.LocalEndPoint as IPEndPoint, + Remote_EndPoint = udpMessage.RemoteEndPoint as IPEndPoint, + Peeked_Buffer = buffer, + Protocol = AgnosticRequest.ListenerProtocol.UDP + }; + + ProcessConnectionSync(agnosticRequest); + } + } + if (CancelToken_.IsCancellationRequested || Cancel) break; + } + catch (Exception ex) + { + // Event Error + if (!CancelToken_.IsCancellationRequested || !Cancel) + { + string msgEventErr = $"ERROR: Accept Connections UDP: {ex.GetInnerExceptions()}"; + //OnRequestReceived?.Invoke(msgEventErr, EventArgs.Empty); + Debug.WriteLine(msgEventErr); + } + } + } + + Stop(); + } + + // TCP + async void tcp(TcpListener tcpListener) + { + while (!Cancel) + { + try + { + TcpClient tcpClient = await tcpListener.AcceptTcpClientAsync().ConfigureAwait(false); + if (CpuUsage < Settings_.KillOnCpuUsage || Settings_.KillOnCpuUsage <= 0) + { + byte[] buffer = new byte[MaxDataSize]; + int received = await tcpClient.Client.ReceiveAsync(buffer, SocketFlags.Peek).ConfigureAwait(false); + if (received > 0) + { + tcpClient.NoDelay = true; + buffer = buffer[..received]; + //Debug.WriteLine(" TCP ===> " + BitConverter.ToString(buffer)); + AgnosticRequest agnosticRequest = new() + { + Tcp_Client = tcpClient, + Local_EndPoint = tcpClient.Client.LocalEndPoint as IPEndPoint, + Remote_EndPoint = tcpClient.Client.RemoteEndPoint as IPEndPoint, + Peeked_Buffer = buffer, + Protocol = AgnosticRequest.ListenerProtocol.TCP + }; + + ProcessConnectionSync(agnosticRequest); + } + } + if (CancelToken_.IsCancellationRequested || Cancel) break; + } + catch (Exception ex) + { + // Event Error + if (!CancelToken_.IsCancellationRequested || !Cancel) + { + string msgEventErr = $"ERROR: Accept Connections TCP: {ex.GetInnerExceptions()}"; + //OnRequestReceived?.Invoke(msgEventErr, EventArgs.Empty); + Debug.WriteLine(msgEventErr); + } + } + } + + Stop(); + } + } + else + { + IsRunning = false; + UdpSocket_?.Dispose(); + TcpListener_?.Stop(); + } + } + catch (Exception ex) + { + // Event Error + if (!CancelToken_.IsCancellationRequested || !Cancel) + { + string msgEventErr = $"ERROR: Accept Connections: {ex.GetInnerExceptions()}"; + Debug.WriteLine(msgEventErr); + OnRequestReceived?.Invoke(msgEventErr, EventArgs.Empty); + TcpListener_?.Stop(); + } + } + } + + private void ProcessConnectionSync(AgnosticRequest agnosticRequest) + { + Task.Run(() => ClientConnected(agnosticRequest)); + } + + private async void ClientConnected(AgnosticRequest aRequest) + { + if (aRequest.Local_EndPoint == null || aRequest.Remote_EndPoint == null) + { + aRequest.Disconnect(); + return; + } + + // Count Max Requests + try + { + MaxRequestsQueue.Enqueue(DateTime.UtcNow); + if (Settings_.MaxRequests >= MaxRequestsDivide) + { + if (MaxRequestsQueue.Count >= Settings_.MaxRequests / MaxRequestsDivide) // Check for 50 ms (1000 / 20) + { + // Event + string blockEvent = $"Recevied {MaxRequestsQueue.Count * MaxRequestsDivide} Requests Per Second - Request Denied Due To Max Requests of {Settings_.MaxRequests}."; + Debug.WriteLine("====================> " + blockEvent); + OnRequestReceived?.Invoke(blockEvent, EventArgs.Empty); + aRequest.Disconnect(); + return; + } + } + } + catch (Exception) { } + + // Generate unique int + int connectionId; + try + { + connectionId = Guid.NewGuid().GetHashCode() + BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0); + } + catch (Exception) + { + connectionId = Guid.NewGuid().GetHashCode(); + } + + AgnosticResult aResult = await aRequest.GetResultAsync(SettingsSSL_).ConfigureAwait(false); + + if (aResult.Socket == null || aResult.FirstBuffer.Length == 0 || aResult.Protocol == RequestProtocol.Unknown) + { + aRequest.Disconnect(); + return; + } + + //Debug.WriteLine("Request ListenerProtocol ===> " + aResult.Protocol); + //Debug.WriteLine("Request Buffer ===> " + BitConverter.ToString(aResult.FirstBuffer)); + + if (aResult.Protocol == RequestProtocol.UDP || + aResult.Protocol == RequestProtocol.TCP || + aResult.Protocol == RequestProtocol.DoH) + { + // ===== Process DNS + await DnsTunnel.Process(aResult, DnsRulesProgram, DnsCaches, Settings_, OnRequestReceived); + aRequest.Disconnect(); + } + else + { + // ===== Process Proxy + if (Settings_.Working_Mode == AgnosticSettings.WorkingMode.DnsAndProxy) + { + // Create Client + ProxyClient proxyClient = new(aResult.Socket); + + // Create Request + CTS_PR = new(); + ProxyRequest? req = null; + if (aResult.Protocol == RequestProtocol.HTTP_S) + req = await ProxyRequest.RequestHTTP_S(aResult.FirstBuffer, CTS_PR.Token).ConfigureAwait(false); + else if (aResult.Protocol == RequestProtocol.SOCKS4_4A) + req = await ProxyRequest.RequestSocks4_4A(proxyClient, aResult.FirstBuffer, CTS_PR.Token).ConfigureAwait(false); + else if (aResult.Protocol == RequestProtocol.SOCKS5) + req = await ProxyRequest.RequestSocks5(proxyClient, aResult.FirstBuffer, CTS_PR.Token).ConfigureAwait(false); + else if (aResult.Protocol == RequestProtocol.SniProxy) + req = await ProxyRequest.RequestSniProxy(aResult.SNI, Settings_.ListenerPort, CTS_PR.Token).ConfigureAwait(false); + + // Apply Programs + req = await ApplyPrograms(aResult.Local_EndPoint.Address, req).ConfigureAwait(false); + + if (req == null) + { + aRequest.Disconnect(); + return; + } + + // Create Tunnel + ProxyTunnel proxyTunnel = new(connectionId, proxyClient, req, Settings_, SettingsSSL_); + proxyTunnel.Open(ProxyRulesProgram); + + proxyTunnel.OnTunnelDisconnected += ProxyTunnel_OnTunnelDisconnected; + proxyTunnel.OnDataReceived += ProxyTunnel_OnDataReceived; + + TunnelManager_.Add(proxyTunnel); + } + } + } + + private void ProxyTunnel_OnTunnelDisconnected(object? sender, EventArgs e) + { + try + { + if (sender is not ProxyTunnel pt) return; + + if (pt.KillOnTimeout.IsRunning) + { + pt.KillOnTimeout.Reset(); + pt.KillOnTimeout.Stop(); + } + + pt.OnTunnelDisconnected -= ProxyTunnel_OnTunnelDisconnected; + pt.OnDataReceived -= ProxyTunnel_OnDataReceived; + pt.ClientSSL?.Disconnect(); + + TunnelManager_.Remove(pt); + Debug.WriteLine($"{pt.Req.Address} Disconnected"); + } + catch (Exception ex) + { + Debug.WriteLine("ProxyTunnel_OnTunnelDisconnected: " + ex.Message); + } + } + + private void ProxyTunnel_OnDataReceived(object? sender, EventArgs e) + { + try + { + if (sender is not ProxyTunnel t) return; + + t.Client.OnDataReceived += async (s, e) => + { + // Client Received == Remote Sent + if (!t.KillOnTimeout.IsRunning) t.KillOnTimeout.Start(); + t.KillOnTimeout.Restart(); + if (e.Buffer.Length > 0) + { + if (t.Req.ApplyFragment) + Send(e.Buffer, t); + else + await t.RemoteClient.SendAsync(e.Buffer).ConfigureAwait(false); + + lock (Stats) + { + Stats.AddBytes(e.Buffer.Length, ByteType.Sent); + } + } + + t.KillOnTimeout.Restart(); + await t.Client.StartReceiveAsync().ConfigureAwait(false); + t.KillOnTimeout.Restart(); + }; + + t.Client.OnDataSent += (s, e) => + { + // Client Sent == Remote Received + if (!t.KillOnTimeout.IsRunning) t.KillOnTimeout.Start(); + t.KillOnTimeout.Restart(); + lock (Stats) + { + Stats.AddBytes(e.Buffer.Length, ByteType.Received); + } + }; + + t.RemoteClient.OnDataReceived += async (s, e) => + { + t.KillOnTimeout.Restart(); + + if (e.Buffer.Length > 0) + await t.Client.SendAsync(e.Buffer).ConfigureAwait(false); + + t.KillOnTimeout.Restart(); + await t.RemoteClient.StartReceiveAsync().ConfigureAwait(false); + t.KillOnTimeout.Restart(); + }; + + // Handle SSL + if (t.ClientSSL != null) + { + t.ClientSSL.OnClientDataReceived += (s, e) => + { + t.KillOnTimeout.Restart(); + + if (e.Buffer.Length > 0) + { + // Can't be implement here. Ex: "The WriteAsync method cannot be called when another write operation is pending" + //await t.ClientSSL.RemoteStream.WriteAsync(e.Buffer).ConfigureAwait(false); + + lock (Stats) + { + Stats.AddBytes(e.Buffer.Length, ByteType.Sent); + } + } + t.ClientSSL.OnClientDataReceived -= null; + }; + + t.ClientSSL.OnRemoteDataReceived += (s, e) => + { + t.KillOnTimeout.Restart(); + + if (e.Buffer.Length > 0) + { + // Can't be implement here. Ex: "The WriteAsync method cannot be called when another write operation is pending" + //await t.ClientSSL.ClientStream.WriteAsync(e.Buffer).ConfigureAwait(false); + + lock (Stats) + { + Stats.AddBytes(e.Buffer.Length, ByteType.Received); + } + } + t.ClientSSL.OnRemoteDataReceived -= null; + }; + } + } + catch (Exception ex) + { + Debug.WriteLine("ProxyTunnel_OnDataReceived: " + ex.Message); + } + } + + private void Send(byte[] data, ProxyTunnel t) + { + try + { + if (t.RemoteClient.Socket_ != null && t.RemoteClient.Socket_.Connected) + { + if (FragmentProgram.FragmentMode == AgnosticProgram.Fragment.Mode.Disable) + { + // Static + AgnosticProgram.Fragment bp = StaticFragmentProgram; + bp.DestHostname = t.Req.Address; + bp.DestPort = t.Req.Port; + if (bp.FragmentMode == AgnosticProgram.Fragment.Mode.Program) + { + AgnosticProgram.Fragment.ProgramMode programMode = new(data, t.RemoteClient.Socket_); + programMode.Send(bp); + } + else + t.RemoteClient.Socket_.Send(data); + } + else + { + // Const + AgnosticProgram.Fragment bp = FragmentProgram; + bp.DestHostname = t.Req.Address; + bp.DestPort = t.Req.Port; + AgnosticProgram.Fragment.ProgramMode programMode = new(data, t.RemoteClient.Socket_); + programMode.Send(bp); + } + } + } + catch (Exception ex) + { + Debug.WriteLine("Send: " + ex.Message); + } + } + +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/DnsRules.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/DnsRules.cs new file mode 100644 index 0000000..37b254b --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/DnsRules.cs @@ -0,0 +1,172 @@ +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public partial class AgnosticProgram +{ + public partial class DnsRules + { + public enum Mode + { + File, + Text, + Disable + } + + public Mode RulesMode { get; private set; } = Mode.Disable; + public string PathOrText { get; private set; } = string.Empty; + public string TextContent { get; private set; } = string.Empty; + + private List Rules_List { get; set; } = new(); + private List> Variables { get; set; } = new(); // x = domain.com; + private string Default_DnsProxyScheme { get; set; } = string.Empty; // dnsproxy:; + private string Default_DnsProxyUser { get; set; } = string.Empty; // &user: + private string Default_DnsProxyPass { get; set; } = string.Empty; // &pass: + private List MainRules_List { get; set; } = new(); + + private class DnsMainRules + { + public string Client { get; set; } = string.Empty; + public string Domain { get; set; } = string.Empty; + public bool IsBlock { get; set; } = false; + public string FakeDns { get; set; } = string.Empty; + public List Dnss { get; set; } = new(); + public string DnsDomain { get; set; } = string.Empty; + public string DnsProxyScheme { get; set; } = string.Empty; + public string DnsProxyUser { get; set; } = string.Empty; + public string DnsProxyPass { get; set; } = string.Empty; + } + + public DnsRules() { } + + public void Set(Mode mode, string filePathOrText) + { + Rules_List.Clear(); + Variables.Clear(); + Default_DnsProxyScheme = string.Empty; + Default_DnsProxyUser = string.Empty; + Default_DnsProxyPass = string.Empty; + MainRules_List.Clear(); + + RulesMode = mode; + PathOrText = filePathOrText; + + if (RulesMode == Mode.Disable) return; + + if (RulesMode == Mode.File) + { + try + { + TextContent = File.ReadAllText(Path.GetFullPath(filePathOrText)); + } + catch (Exception) { } + } + else if (RulesMode == Mode.Text) TextContent = filePathOrText; + + if (string.IsNullOrEmpty(TextContent) || string.IsNullOrWhiteSpace(TextContent)) return; + + TextContent += Environment.NewLine; + Rules_List = TextContent.SplitToLines(); + + for (int n = 0; n < Rules_List.Count; n++) + { + string line = Rules_List[n].Trim(); + if (line.StartsWith("//")) continue; // Support Comment // + if (!line.EndsWith(';')) continue; // Must Have ; At The End + if (string.IsNullOrEmpty(line) || string.IsNullOrWhiteSpace(line)) continue; // Line Cannot Be Empty + + // Get Variables + if (line.Contains('=') && !line.Contains(',') && !line.Contains('&')) + { + line = line.TrimEnd(';'); + string[] split = line.Split('='); + if (split.Length == 2) + { + string item1 = split[0].Trim(); + string item2 = split[1].Trim(); + if (!string.IsNullOrEmpty(item1) && !string.IsNullOrEmpty(item2)) + Variables.Add(new Tuple(item1, item2)); + } + } + + // Get Defaults + else if (line.StartsWith(Rules.KEYS.DnsProxy, StringComparison.InvariantCultureIgnoreCase)) + { + Default_DnsProxyScheme = Rules.GetValue(line, Rules.KEYS.DnsProxy, Rules.SUB_KEYS.FirstKey, out _, out _, Variables); + Default_DnsProxyUser = Rules.GetValue(line, Rules.KEYS.DnsProxy, Rules.SUB_KEYS.User, out _, out _, Variables); + Default_DnsProxyPass = Rules.GetValue(line, Rules.KEYS.DnsProxy, Rules.SUB_KEYS.Pass, out _, out _, Variables); + } + + // Get DnsMainRules (Client|Domain|DnsRules) + else if (line.Contains('|')) + { + string[] split = line.Split('|'); + if (split.Length == 2) SetDomainRules(Rules.KEYS.AllClients, split[0].Trim(), split[1].Trim()); + if (split.Length == 3) SetDomainRules(split[0].Trim(), split[1].Trim(), split[2].Trim()); + } + } + } + + private void SetDomainRules(string client, string domain, string rules) // rules Ends With ; + { + try + { + DnsMainRules dmr = new() + { + Client = client, // Client + Domain = domain // Domain + }; + + // Block + if (rules.Equals("block;", StringComparison.InvariantCultureIgnoreCase)) dmr.IsBlock = true; + if (rules.Equals("-;")) dmr.IsBlock = true; + + if (!dmr.IsBlock) + { + // Fake DNS + string fakeDnsIpStr = Rules.GetValue(rules, Rules.KEYS.FirstKey, null, out _, out _, Variables); + bool isIp = NetworkTool.IsIp(fakeDnsIpStr, out _); + if (isIp) dmr.FakeDns = fakeDnsIpStr; + } + + // Dnss + string dnss = Rules.GetValue(rules, Rules.KEYS.Dns, null, out bool isList, out List list, Variables); + if (!isList) // One Dns + { + if (!string.IsNullOrEmpty(dnss)) + dmr.Dnss.Add(dnss); + } + else // Multiple Dnss + { + for (int i = 0; i < list.Count; i++) + { + string dns = list[i]; + if (!string.IsNullOrEmpty(dns)) + dmr.Dnss.Add(dns); + } + } + + // DnsDomain + dmr.DnsDomain = Rules.GetValue(rules, Rules.KEYS.DnsDomain, null, out _, out _, Variables); + + // DnsProxy e.g. socks5://127.0.0.1:6666&user:UserName&pass:PassWord + dmr.DnsProxyScheme = Rules.GetValue(rules, Rules.KEYS.DnsProxy, Rules.SUB_KEYS.FirstKey, out _, out _, Variables); + dmr.DnsProxyUser = Rules.GetValue(rules, Rules.KEYS.DnsProxy, Rules.SUB_KEYS.User, out _, out _, Variables); + dmr.DnsProxyPass = Rules.GetValue(rules, Rules.KEYS.DnsProxy, Rules.SUB_KEYS.Pass, out _, out _, Variables); + if (string.IsNullOrEmpty(dmr.DnsProxyScheme)) + { + dmr.DnsProxyScheme = Default_DnsProxyScheme; + dmr.DnsProxyUser = Default_DnsProxyUser; + dmr.DnsProxyPass = Default_DnsProxyPass; + } + + MainRules_List.Add(dmr); + } + catch (Exception ex) + { + Debug.WriteLine("Dns Rules_SetDomainRules: " + ex.Message); + } + } + + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/DnsRules_Get.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/DnsRules_Get.cs new file mode 100644 index 0000000..3f0e3c4 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/DnsRules_Get.cs @@ -0,0 +1,129 @@ +using System.Diagnostics; +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public partial class AgnosticProgram +{ + public partial class DnsRules + { + public class DnsRulesResult + { + public bool IsMatch { get; set; } = false; + public bool IsBlackList { get; set; } = false; + public string DnsCustomDomain { get; set; } = string.Empty; + public string Dns { get; set; } = string.Empty; + public List Dnss { get; set; } = new(); + } + + public async Task GetAsync(string client, string host, AgnosticSettings settings) + { + DnsRulesResult drr = new(); + if (string.IsNullOrEmpty(host)) return drr; + + try + { + for (int n = 0; n < MainRules_List.Count; n++) + { + DnsMainRules dmr = MainRules_List[n]; + + // Check If Match + bool isClientMatch = !string.IsNullOrEmpty(dmr.Client) && (dmr.Client.Equals(Rules.KEYS.AllClients) || client.Equals(dmr.Client) || client.EndsWith(dmr.Client)); + bool isDomainMatch = Rules.IsDomainMatch(host, dmr.Domain, out bool isWildcard, out string hostNoWww, out string ruleHostNoWww); + bool isMatch = isClientMatch && isDomainMatch; + if (!isMatch) continue; + + // Set Match + drr.IsMatch = isMatch; + + // Is Black List + drr.IsBlackList = dmr.IsBlock; + if (drr.IsBlackList) break; + + // DNS + if (!string.IsNullOrEmpty(dmr.FakeDns)) + { + // Fake DNS + drr.Dns = dmr.FakeDns; + } + else + { + // Get Dns Servers And Upstream Proxy + List dnss = dmr.Dnss.Any() ? dmr.Dnss : settings.DNSs; + drr.Dnss = dnss; + string? dnsProxyScheme = null, dnsProxyUser = null, dnsProxyPass = null; + if (!string.IsNullOrEmpty(dmr.DnsProxyScheme)) + { + dnsProxyScheme = dmr.DnsProxyScheme; + dnsProxyUser = dmr.DnsProxyUser; + dnsProxyPass = dmr.DnsProxyPass; + } + else + { + dnsProxyScheme = settings.UpstreamProxyScheme; + dnsProxyUser = settings.UpstreamProxyUser; + dnsProxyPass = settings.UpstreamProxyPass; + } + + // Get IP By Custom DNS + if (dnss.Any() && !NetworkTool.IsIp(host, out _)) + { + // Get Custom DNS Domain + drr.DnsCustomDomain = host; + if (!string.IsNullOrEmpty(dmr.DnsDomain)) + { + if (!dmr.DnsDomain.StartsWith("*.")) + { + drr.DnsCustomDomain = dmr.DnsDomain; + } + else + { + // Support: xxxx.example.com -> xxxx.domain.com + if (isWildcard) // ruleHostNoWww.StartsWith("*.") + { + if (hostNoWww.EndsWith(ruleHostNoWww[1..])) // Just In Case + { + drr.DnsCustomDomain = hostNoWww.Replace(ruleHostNoWww[1..], dmr.DnsDomain[1..]); + } + } + } + } + + IPAddress ipv4Addr = IPAddress.None; + if (settings.IsIPv4SupportedByISP) + { + ipv4Addr = await GetIP.GetIpFromDnsAddressAsync(drr.DnsCustomDomain, dnss, settings.AllowInsecure, settings.DnsTimeoutSec, false, settings.BootstrapIpAddress, settings.BootstrapPort, dnsProxyScheme, dnsProxyUser, dnsProxyPass); + if (ipv4Addr.Equals(IPAddress.None) && !settings.IsIPv6SupportedByISP) // Retry If IPv6 Is Not Supported + ipv4Addr = await GetIP.GetIpFromDnsAddressAsync(drr.DnsCustomDomain, dnss, settings.AllowInsecure, settings.DnsTimeoutSec, false, settings.BootstrapIpAddress, settings.BootstrapPort, dnsProxyScheme, dnsProxyUser, dnsProxyPass); + } + + if (ipv4Addr.Equals(IPAddress.None)) + { + IPAddress ipv6Addr = await GetIP.GetIpFromDnsAddressAsync(drr.DnsCustomDomain, dnss, settings.AllowInsecure, settings.DnsTimeoutSec, true, settings.BootstrapIpAddress, settings.BootstrapPort, dnsProxyScheme, dnsProxyUser, dnsProxyPass); + if (!ipv6Addr.Equals(IPAddress.IPv6None)) + drr.Dns = ipv6Addr.ToString(); + } + else + { + if (string.IsNullOrEmpty(settings.CloudflareCleanIP)) + drr.Dns = ipv4Addr.ToString(); + else + drr.Dns = CommonTools.IsCfIP(ipv4Addr) ? settings.CloudflareCleanIP : ipv4Addr.ToString(); + } + } + } + + // Break If Match + break; + } + } + catch (Exception ex) + { + Debug.WriteLine("DnsRules_GetAsync: " + ex.Message); + } + + return drr; + } + + } +} diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/Fragment.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/Fragment.cs new file mode 100644 index 0000000..f8d6a58 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/Fragment.cs @@ -0,0 +1,575 @@ +using System.Diagnostics; +using System.Net.Sockets; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public partial class AgnosticProgram +{ + public class Fragment + { + public Mode FragmentMode { get; private set; } = Mode.Disable; + public ChunkMode DPIChunkMode { get; private set; } + public int BeforeSniChunks { get; private set; } = 0; + public int SniChunks { get; private set; } = 0; + public int AntiPatternOffset { get; private set; } = 2; + public int FragmentDelay { get; private set; } = 0; + public string? DestHostname { get; set; } + public int DestPort { get; set; } + + public event EventHandler? OnChunkDetailsReceived; + + public Fragment() { } + + public void Set(Mode mode, int beforeSniChunks, ChunkMode chunkMode, int sniChunks, int antiPatternOffset, int fragmentDelay) + { + FragmentMode = mode; + BeforeSniChunks = beforeSniChunks; + DPIChunkMode = chunkMode; + SniChunks = sniChunks; + AntiPatternOffset = antiPatternOffset; + FragmentDelay = fragmentDelay; + } + + public enum Mode + { + Program, + Disable + } + + public enum ChunkMode + { + SNI, + SniExtension, + AllExtensions + } + + public class ProgramMode + { + private byte[] Data { get; set; } + private Socket Socket { get; set; } + + public ProgramMode(byte[] data, Socket socket) + { + Data = data; + Socket = socket; + } + + public void Send(Fragment bp) + { + try + { + int offset = bp.AntiPatternOffset; + Random random = new(); + + // Anti Pattern Fragment Chunks + int beforeSniChunks = random.Next(bp.BeforeSniChunks - offset, bp.BeforeSniChunks + offset); + if (beforeSniChunks <= 0) beforeSniChunks = 1; + if (beforeSniChunks > Data.Length) beforeSniChunks = Data.Length; + + int sniChunks = random.Next(bp.SniChunks - offset, bp.SniChunks + offset); + if (sniChunks <= 0) sniChunks = 1; + if (sniChunks > Data.Length) sniChunks = Data.Length; + + //Test(Data, Socket, beforeSniChunks, sniChunks, offset, bp); + + if (bp.DPIChunkMode == ChunkMode.AllExtensions) + SendDataInFragmentAllExtensions(Data, Socket, beforeSniChunks, sniChunks, offset, bp); + else if (bp.DPIChunkMode == ChunkMode.SniExtension) + SendDataInFragmentSniExtension(Data, Socket, beforeSniChunks, sniChunks, offset, bp); + else if (bp.DPIChunkMode == ChunkMode.SNI) + SendDataInFragmentSNI(Data, Socket, beforeSniChunks, sniChunks, offset, bp); + } + catch (Exception) { } + } + + private static void Test(byte[] data, Socket socket, int beforeSniChunks, int sniChunks, int offset, Fragment bp) + { + Debug.WriteLine("Send Data in TEST"); + // Create packets + List packets = new(); + packets.Clear(); + + SniModifire sniModifire = new(data); + if (sniModifire.HasSni) + { + packets.Add(sniModifire.ModifiedData); + SendPackets(sniModifire.ModifiedData, socket, bp, packets); + } + else + { + packets.Add(data); + SendPackets(data, socket, bp, packets); + } + + } + + private static void SendDataInFragmentAllExtensions(byte[] data, Socket socket, int beforeSniChunks, int sniChunks, int offset, Fragment bp) + { + //Debug.WriteLine("SendDataInFragmentAllExtensions"); + // Create packets + List packets = new(); + packets.Clear(); + + try + { + SniReader sniReader = new(data); + + if (beforeSniChunks == 1 && sniChunks == 1) + { + packets.Add(data); + } + else + { + if (sniReader.HasTlsExtensions) + { + int prevIndex; + int pos = 0; + SniReader.TlsExtensions allExtensions = sniReader.AllExtensions; + + pos += allExtensions.StartIndex; + prevIndex = pos - allExtensions.StartIndex; + + // Create packet before SNI + int beforeSniLength = allExtensions.StartIndex - prevIndex; + if (beforeSniLength > 0) + { + byte[] beforeSNI = new byte[beforeSniLength]; + Buffer.BlockCopy(data, prevIndex, beforeSNI, 0, beforeSniLength); + + List chunkedbeforeSNI = ChunkDataNormal(beforeSNI, beforeSniChunks, offset); + packets = packets.Concat(chunkedbeforeSNI).ToList(); + //Debug.WriteLine($"{prevIndex} ======> {beforeSniLength}"); + } + + // Create SNI packet + List chunkedSNI = ChunkDataNormal(allExtensions.Data, sniChunks, offset); + packets = packets.Concat(chunkedSNI).ToList(); + + //Debug.WriteLine($"{beforeSniLength} ====== {sni.SniStartIndex}"); + //Debug.WriteLine($"{sni.SniStartIndex} ======> {sni.SniStartIndex + sni.SniLength}"); + Debug.WriteLine("==-----== " + (sniReader.AllExtensions.StartIndex + sniReader.AllExtensions.Length) + " of " + data.Length); + pos = allExtensions.StartIndex + allExtensions.Length; + + // Create packet after SNI + if (pos < data.Length) + { + int afterSniStartIndex = pos; + int afterSniLength = data.Length - pos; + byte[] afterSni = new byte[afterSniLength]; + Buffer.BlockCopy(data, afterSniStartIndex, afterSni, 0, afterSniLength); + packets.Add(afterSni); + + //Debug.WriteLine($"{sni.SniStartIndex + sni.SniLength} ====== {afterSniStartIndex}"); + //Debug.WriteLine($"{afterSniStartIndex} ======> {afterSniStartIndex + afterSniLength}"); + //Debug.WriteLine($"{afterSniStartIndex + afterSniLength} ====== {data.Length}"); + } + } + else + { + packets.Add(data); + } + } + } + catch (Exception) { } + + SendPackets(data, socket, bp, packets); + } + + private static void SendDataInFragmentSniExtension(byte[] data, Socket socket, int beforeSniChunks, int sniChunks, int offset, Fragment bp) + { + //Debug.WriteLine("SendDataInFragmentSniExtension"); + // Create packets + List packets = new(); + packets.Clear(); + + try + { + SniReader sniReader = new(data); + if (sniReader.SniExtensionList.Count > 1) Debug.WriteLine($"=======================> We Have {sniReader.SniExtensionList.Count} SNI Extensions."); + + if (beforeSniChunks == 1 && sniChunks == 1) + { + packets.Add(data); + } + else + { + if (sniReader.HasSniExtension) + { + int prevIndex; + int pos = 0; + for (int n = 0; n < sniReader.SniExtensionList.Count; n++) + { + SniReader.SniExtension sniExtension = sniReader.SniExtensionList[n]; + + pos += sniExtension.StartIndex; + prevIndex = pos - sniExtension.StartIndex; + + // Create packet before SNI + int beforeSniLength = sniExtension.StartIndex - prevIndex; + if (beforeSniLength > 0) + { + byte[] beforeSNI = new byte[beforeSniLength]; + Buffer.BlockCopy(data, prevIndex, beforeSNI, 0, beforeSniLength); + + List chunkedbeforeSNI = ChunkDataNormal(beforeSNI, beforeSniChunks, offset); + packets = packets.Concat(chunkedbeforeSNI).ToList(); + //Debug.WriteLine($"{prevIndex} ======> {beforeSniLength}"); + } + + // Create SNI packet + List chunkedSNI = ChunkDataNormal(sniExtension.Data, sniChunks, offset); + packets = packets.Concat(chunkedSNI).ToList(); + + //Debug.WriteLine($"{beforeSniLength} ====== {sni.SniStartIndex}"); + //Debug.WriteLine($"{sni.SniStartIndex} ======> {sni.SniStartIndex + sni.SniLength}"); + + pos = sniExtension.StartIndex + sniExtension.Length; + + // Last round + if (n == sniReader.SniExtensionList.Count - 1) + { + // Create packet after SNI + if (pos < data.Length) + { + int afterSniStartIndex = pos; + int afterSniLength = data.Length - pos; + byte[] afterSni = new byte[afterSniLength]; + Buffer.BlockCopy(data, afterSniStartIndex, afterSni, 0, afterSniLength); + packets.Add(afterSni); + + //Debug.WriteLine($"{sni.SniStartIndex + sni.SniLength} ====== {afterSniStartIndex}"); + //Debug.WriteLine($"{afterSniStartIndex} ======> {afterSniStartIndex + afterSniLength}"); + //Debug.WriteLine($"{afterSniStartIndex + afterSniLength} ====== {data.Length}"); + } + } + } + } + else + { + packets.Add(data); + } + } + } + catch (Exception) { } + + SendPackets(data, socket, bp, packets); + } + + private static void SendDataInFragmentSNI(byte[] data, Socket socket, int beforeSniChunks, int sniChunks, int offset, Fragment bp) + { + //Debug.WriteLine("SendDataInFragmentSNI"); + // Create packets + List packets = new(); + packets.Clear(); + + try + { + SniReader sniReader = new(data); + if (sniReader.SniList.Count > 1) Debug.WriteLine($"=======================> We Have {sniReader.SniList.Count} SNIs."); + + if (beforeSniChunks == 1 && sniChunks == 1) + { + packets.Add(data); + } + else + { + if (sniReader.HasSni) + { + int prevIndex; + int pos = 0; + for (int n = 0; n < sniReader.SniList.Count; n++) + { + SniReader.SNI sni = sniReader.SniList[n]; + + pos += sni.StartIndex; + prevIndex = pos - sni.StartIndex; + + // Create packet before SNI + int beforeSniLength = sni.StartIndex - prevIndex; + if (beforeSniLength > 0) + { + byte[] beforeSNI = new byte[beforeSniLength]; + Buffer.BlockCopy(data, prevIndex, beforeSNI, 0, beforeSniLength); + + List chunkedbeforeSNI = ChunkDataNormal(beforeSNI, beforeSniChunks, offset); + packets = packets.Concat(chunkedbeforeSNI).ToList(); + //Debug.WriteLine($"{prevIndex} ======> {beforeSniLength}"); + } + + // Create SNI packet + List chunkedSNI = ChunkDataNormal(sni.Data, sniChunks, offset); + packets = packets.Concat(chunkedSNI).ToList(); + + //Debug.WriteLine($"{beforeSniLength} ====== {sni.SniStartIndex}"); + //Debug.WriteLine($"{sni.SniStartIndex} ======> {sni.SniStartIndex + sni.SniLength}"); + + pos = sni.StartIndex + sni.Length; + + // Last round + if (n == sniReader.SniList.Count - 1) + { + // Create packet after SNI + if (pos < data.Length) + { + int afterSniStartIndex = pos; + int afterSniLength = data.Length - pos; + byte[] afterSni = new byte[afterSniLength]; + Buffer.BlockCopy(data, afterSniStartIndex, afterSni, 0, afterSniLength); + packets.Add(afterSni); + + //Debug.WriteLine($"{sni.SniStartIndex + sni.SniLength} ====== {afterSniStartIndex}"); + //Debug.WriteLine($"{afterSniStartIndex} ======> {afterSniStartIndex + afterSniLength}"); + //Debug.WriteLine($"{afterSniStartIndex + afterSniLength} ====== {data.Length}"); + } + } + } + } + else + { + packets.Add(data); + } + } + } + catch (Exception) { } + + SendPackets(data, socket, bp, packets); + } + + private static List ChunkDataNormal(byte[] data, int chunks, int offset) + { + //Debug.WriteLine("ChunkDataNormal"); + // Create chunk packets + Random random = new(); + List chunkPackets = new(); + chunkPackets.Clear(); + + int prevIndex; + int nn = 0; + int sum = 0; + for (int n = 0; n < data.Length; n++) + { + try + { + // Anti Pattern Fragment Size + int fragmentSize = data.Length / chunks; + + int fragmentSizeOut = random.Next(fragmentSize - offset, fragmentSize + offset); + if (fragmentSizeOut <= 0) fragmentSizeOut = 1; + if (fragmentSizeOut > data.Length) fragmentSizeOut = data.Length; + nn += fragmentSizeOut; + + if (nn > data.Length) + { + fragmentSizeOut = data.Length - (nn - fragmentSizeOut); + //Debug.WriteLine(fragmentSizeOut); + } + //Debug.WriteLine(fragmentSizeOut); + + sum += fragmentSizeOut; + byte[] fragmentData = new byte[fragmentSizeOut]; + prevIndex = sum - fragmentSizeOut; + Buffer.BlockCopy(data, prevIndex, fragmentData, 0, fragmentSizeOut); + chunkPackets.Add(fragmentData); + + if (sum >= data.Length) break; + } + catch (Exception ex) + { + chunkPackets.Clear(); + string msgEvent = $"Error, Creating normal packets: {ex.Message}"; + Debug.WriteLine(msgEvent); + return chunkPackets; + } + } + + return chunkPackets; + } + + private static List ChunkDataNormal2(byte[] data, int fragmentSize) + { + Debug.WriteLine("ChunkDataNormal2"); + // Create chunk packets + List chunkPackets = new(); + chunkPackets.Clear(); + + var fragments = data.Chunk(fragmentSize); + for (int n = 0; n < fragments.Count(); n++) + { + try + { + byte[] fragment = fragments.ToArray()[n]; + chunkPackets.Add(fragment); + } + catch (Exception ex) + { + chunkPackets.Clear(); + string msgEvent = $"Error, Creating normal2 packets: {ex.Message}"; + Debug.WriteLine(msgEvent); + return chunkPackets; + } + } + + return chunkPackets; + } + + private static List ChunkDataNormal3(byte[] data, int fragmentSize) + { + Debug.WriteLine("ChunkDataNormal3"); + // Create chunk packets + List chunkPackets = new(); + chunkPackets.Clear(); + + var fragments = ChunkViaMemory(data, fragmentSize); + for (int n = 0; n < fragments.Count(); n++) + { + try + { + byte[] fragment = fragments.ToArray()[n].ToArray(); + chunkPackets.Add(fragment); + } + catch (Exception ex) + { + chunkPackets.Clear(); + string msgEvent = $"Error, Creating normal3 packets: {ex.Message}"; + Debug.WriteLine(msgEvent); + return chunkPackets; + } + } + + return chunkPackets; + } + + private static List ChunkDataRandom(byte[] data, int fragmentChunks) + { + Debug.WriteLine("ChunkDataRandom"); + //// Calculate fragment chunks from size + //int fragmentChunks = data.Length / fragmentSize; + //if (fragmentChunks <= 0) fragmentChunks = 1; + //if (fragmentChunks > data.Length) fragmentChunks = data.Length; + + // Create chunk packets + List packets = new(); + packets.Clear(); + + try + { + fragmentChunks = Math.Min(fragmentChunks, data.Length); + List indices; + if (fragmentChunks < data.Length) + indices = GenerateRandomIndices(1, data.Length - 1, fragmentChunks - 1); + else + indices = Enumerable.Range(0, data.Length - 1).ToList(); + indices.Sort(); + + int prevIndex = 0; + for (int n = 0; n < indices.Count; n++) + { + try + { + int index = indices[n]; + byte[] fragmentData = new byte[index - prevIndex]; + Buffer.BlockCopy(data, prevIndex, fragmentData, 0, fragmentData.Length); + packets.Add(fragmentData); + prevIndex = index; + } + catch (Exception ex) + { + packets.Clear(); + string msgEvent = $"Error, Creating random packets: {ex.Message}"; + Debug.WriteLine(msgEvent); + return packets; + } + } + + try + { + byte[] lastFragmentData = new byte[data.Length - prevIndex]; + Buffer.BlockCopy(data, prevIndex, lastFragmentData, 0, lastFragmentData.Length); + packets.Add(lastFragmentData); + } + catch (Exception ex) + { + packets.Clear(); + string msgEvent = $"Error, Creating last random packet: {ex.Message}"; + Debug.WriteLine(msgEvent); + return packets; + } + } + catch (Exception) { } + + return packets; + } + + private static void SendPackets(byte[] data, Socket socket, Fragment bp, List packets) + { + // Check packets + int allLength = 0; + for (int i = 0; i < packets.Count; i++) + allLength += packets[i].Length; + + if (allLength != data.Length) + { + Debug.WriteLine($"{allLength} == {data.Length}, Chunks: {packets.Count}"); + packets.Clear(); + return; + } + + // Send packets + for (int i = 0; i < packets.Count; i++) + { + try + { + byte[] fragmentData = packets[i]; + if (socket == null) return; + socket.Send(fragmentData); + if (bp.FragmentDelay > 0) + Task.Delay(bp.FragmentDelay).Wait(); + } + catch (Exception ex) + { + string msgEvent = $"Error, Send Packets: {ex.Message}"; + Debug.WriteLine(msgEvent); + return; + } + } + + string chunkDetailsEvent = $"{bp.DestHostname}:{bp.DestPort} Length: {data.Length}"; + if (packets.Count > 1) + chunkDetailsEvent += $", Chunks: {packets.Count}"; + bp.OnChunkDetailsReceived?.Invoke(chunkDetailsEvent, EventArgs.Empty); + } + } + + private static List GenerateRandomIndices(int minValue, int maxValue, int count) + { + Random random = new(); + HashSet indicesSet = new(); + + try + { + while (indicesSet.Count < count) + { + indicesSet.Add(random.Next(minValue, maxValue)); + } + } + catch (Exception) { } + + return new List(indicesSet); + } + + private static IEnumerable> ChunkViaMemory(T[] data, int size) + { + var chunks = data.Length / size; + for (int i = 0; i < chunks; i++) + { + yield return data.AsMemory(i * size, size); + } + var leftOver = data.Length % size; + if (leftOver > 0) + { + yield return data.AsMemory(chunks * size, leftOver); + } + } + + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/ProxyRules.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/ProxyRules.cs new file mode 100644 index 0000000..50100a3 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/ProxyRules.cs @@ -0,0 +1,301 @@ +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public partial class AgnosticProgram +{ + public partial class ProxyRules + { + public enum Mode + { + File, + Text, + Disable + } + + public Mode RulesMode { get; private set; } = Mode.Disable; + public string PathOrText { get; private set; } = string.Empty; + public string TextContent { get; private set; } = string.Empty; + + private List Rules_List { get; set; } = new(); + private List> Variables { get; set; } = new(); // x = domain.com; + private List Default_BlockPort { get; set; } = new(); // blockport:80,53; + private List Default_Dnss { get; set; } = new(); // dns: + private string Default_DnsDomain { get; set; } = string.Empty; // dnsdomain:; + private string Default_DnsProxyScheme { get; set; } = string.Empty; // dnsproxy:; + private string Default_DnsProxyUser { get; set; } = string.Empty; // &user: + private string Default_DnsProxyPass { get; set; } = string.Empty; // &pass: + private string Default_Sni { get; set; } = string.Empty; // sni:; + private string Default_ProxyScheme { get; set; } = string.Empty; // proxy:; + private bool Default_ProxyIfBlock { get; set; } = false; // &ifblock:1 + private string Default_ProxyUser { get; set; } = string.Empty; // &user: + private string Default_ProxyPass { get; set; } = string.Empty; // &pass: + private List MainRules_List { get; set; } = new(); + + private class ProxyMainRules + { + public string Client { get; set; } = string.Empty; + public string Domain { get; set; } = string.Empty; + public bool IsBlock { get; set; } = false; + public List BlockPort { get; set; } = new(); + public bool NoBypass { get; set; } = false; + public string FakeDns { get; set; } = string.Empty; + public List Dnss { get; set; } = new(); + public string DnsDomain { get; set; } = string.Empty; + public string DnsProxyScheme { get; set; } = string.Empty; + public string DnsProxyUser { get; set; } = string.Empty; + public string DnsProxyPass { get; set; } = string.Empty; + public string Sni { get; set; } = string.Empty; + public string ProxyScheme { get; set; } = string.Empty; + public bool ProxyIfBlock { get; set; } = false; + public string ProxyUser { get; set; } = string.Empty; + public string ProxyPass { get; set; } = string.Empty; + } + + public ProxyRules() { } + + public void Set(Mode mode, string filePathOrText) + { + Rules_List.Clear(); + Variables.Clear(); + Default_BlockPort.Clear(); + Default_Dnss.Clear(); + Default_DnsDomain = string.Empty; + Default_DnsProxyScheme = string.Empty; + Default_DnsProxyUser = string.Empty; + Default_DnsProxyPass = string.Empty; + Default_Sni = string.Empty; + Default_ProxyScheme = string.Empty; + Default_ProxyIfBlock = false; + Default_ProxyUser = string.Empty; + Default_ProxyPass = string.Empty; + MainRules_List.Clear(); + + RulesMode = mode; + PathOrText = filePathOrText; + + if (RulesMode == Mode.Disable) return; + + if (RulesMode == Mode.File) + { + try + { + TextContent = File.ReadAllText(Path.GetFullPath(filePathOrText)); + } + catch (Exception) { } + } + else if (RulesMode == Mode.Text) TextContent = filePathOrText; + + if (string.IsNullOrEmpty(TextContent) || string.IsNullOrWhiteSpace(TextContent)) return; + + TextContent += Environment.NewLine; + Rules_List = TextContent.SplitToLines(); + + for (int n = 0; n < Rules_List.Count; n++) + { + string line = Rules_List[n].Trim(); + if (line.StartsWith("//")) continue; // Support Comment // + if (!line.EndsWith(';')) continue; // Must Have ; At The End + if (string.IsNullOrEmpty(line) || string.IsNullOrWhiteSpace(line)) continue; // Line Cannot Be Empty + + // Get Variables + if (line.Contains('=') && !line.Contains(',') && !line.Contains('&')) + { + line = line.TrimEnd(';'); + string[] split = line.Split('='); + if (split.Length == 2) + { + string item1 = split[0].Trim(); + string item2 = split[1].Trim(); + if (!string.IsNullOrEmpty(item1) && !string.IsNullOrEmpty(item2)) + Variables.Add(new Tuple(item1, item2)); + } + } + + // Get Defaults + else if (line.StartsWith(Rules.KEYS.BlockPort, StringComparison.InvariantCultureIgnoreCase)) + { + string ports = Rules.GetValue(line, Rules.KEYS.BlockPort, null, out bool isList, out List list, Variables); + if (!isList) // One Port + { + bool success = int.TryParse(ports, out int port); + if (success) Default_BlockPort.Add(port); + } + else // Multiple Ports + { + for (int i = 0; i < list.Count; i++) + { + string portStr = list[i]; + bool success = int.TryParse(portStr, out int port); + if (success) Default_BlockPort.Add(port); + } + } + continue; + } + else if (line.StartsWith(Rules.KEYS.Dns, StringComparison.InvariantCultureIgnoreCase)) + { + string dnss = Rules.GetValue(line, Rules.KEYS.Dns, null, out bool isList, out List list, Variables); + if (!isList) // One Dns + { + if (!string.IsNullOrEmpty(dnss)) + Default_Dnss.Add(dnss); + } + else // Multiple Dnss + { + for (int i = 0; i < list.Count; i++) + { + string dns = list[i]; + if (!string.IsNullOrEmpty(dns)) + Default_Dnss.Add(dns); + } + } + } + else if (line.StartsWith(Rules.KEYS.DnsDomain, StringComparison.InvariantCultureIgnoreCase)) + { + Default_DnsDomain = Rules.GetValue(line, Rules.KEYS.DnsDomain, null, out _, out _, Variables); + } + else if (line.StartsWith(Rules.KEYS.DnsProxy, StringComparison.InvariantCultureIgnoreCase)) + { + Default_DnsProxyScheme = Rules.GetValue(line, Rules.KEYS.DnsProxy, Rules.SUB_KEYS.FirstKey, out _, out _, Variables); + Default_DnsProxyUser = Rules.GetValue(line, Rules.KEYS.DnsProxy, Rules.SUB_KEYS.User, out _, out _, Variables); + Default_DnsProxyPass = Rules.GetValue(line, Rules.KEYS.DnsProxy, Rules.SUB_KEYS.Pass, out _, out _, Variables); + } + else if (line.StartsWith(Rules.KEYS.Sni, StringComparison.InvariantCultureIgnoreCase)) + { + Default_Sni = Rules.GetValue(line, Rules.KEYS.Sni, null, out _, out _, Variables); + } + else if (line.StartsWith(Rules.KEYS.Proxy, StringComparison.InvariantCultureIgnoreCase)) + { + Default_ProxyScheme = Rules.GetValue(line, Rules.KEYS.Proxy, Rules.SUB_KEYS.FirstKey, out _, out _, Variables); + string ifBlock = Rules.GetValue(line, Rules.KEYS.Proxy, Rules.SUB_KEYS.IfBlock, out _, out _, Variables).ToLower().Trim(); + if (!string.IsNullOrEmpty(ifBlock)) + Default_ProxyIfBlock = ifBlock.Equals("1") || ifBlock.Equals("true"); + Default_ProxyUser = Rules.GetValue(line, Rules.KEYS.Proxy, Rules.SUB_KEYS.User, out _, out _, Variables); + Default_ProxyPass = Rules.GetValue(line, Rules.KEYS.Proxy, Rules.SUB_KEYS.Pass, out _, out _, Variables); + } + + // Get ProxyMainRules (Client|Domain|ProxyRules) + else if (line.Contains('|')) + { + string[] split = line.Split('|'); + if (split.Length == 2) SetDomainRules(Rules.KEYS.AllClients, split[0].Trim(), split[1].Trim()); + if (split.Length == 3) SetDomainRules(split[0].Trim(), split[1].Trim(), split[2].Trim()); + } + } + } + + private void SetDomainRules(string client, string domain, string rules) // rules Ends With ; + { + try + { + ProxyMainRules pmr = new() + { + Client = client, // Client + Domain = domain // Domain + }; + + // Block + if (rules.Equals("block;", StringComparison.InvariantCultureIgnoreCase)) pmr.IsBlock = true; + if (rules.Equals("-;")) pmr.IsBlock = true; + + if (!pmr.IsBlock) + { + // No Bypass + if (rules.Contains("nobypass;", StringComparison.InvariantCultureIgnoreCase)) pmr.NoBypass = true; + if (rules.Contains("--;")) pmr.NoBypass = true; + + // Fake DNS + string fakeDnsIpStr = Rules.GetValue(rules, Rules.KEYS.FirstKey, null, out _, out _, Variables); + bool isIp = NetworkTool.IsIp(fakeDnsIpStr, out _); + if (isIp) pmr.FakeDns = fakeDnsIpStr; + + // BlockPort + string ports = Rules.GetValue(rules, Rules.KEYS.BlockPort, null, out bool isList, out List list, Variables); + if (!isList) // One Port + { + bool success = int.TryParse(ports, out int port); + if (success) pmr.BlockPort.Add(port); + } + else // Multiple Ports + { + for (int i = 0; i < list.Count; i++) + { + string portStr = list[i]; + bool success = int.TryParse(portStr, out int port); + if (success) pmr.BlockPort.Add(port); + } + } + if (Default_BlockPort.Any()) + { + try + { + pmr.BlockPort = pmr.BlockPort.Concat(Default_BlockPort).ToList(); + pmr.BlockPort = pmr.BlockPort.Distinct().ToList(); + } + catch (Exception) { } + } + + // Dnss + string dnss = Rules.GetValue(rules, Rules.KEYS.Dns, null, out isList, out list, Variables); + if (!isList) // One Dns + { + if (!string.IsNullOrEmpty(dnss)) + pmr.Dnss.Add(dnss); + } + else // Multiple Dnss + { + for (int i = 0; i < list.Count; i++) + { + string dns = list[i]; + if (!string.IsNullOrEmpty(dns)) + pmr.Dnss.Add(dns); + } + } + + if (!pmr.Dnss.Any() && Default_Dnss.Any()) pmr.Dnss = Default_Dnss; + + // DnsDomain + pmr.DnsDomain = Rules.GetValue(rules, Rules.KEYS.DnsDomain, null, out _, out _, Variables); + if (string.IsNullOrEmpty(pmr.DnsDomain)) pmr.DnsDomain = Default_DnsDomain; + + // DnsProxy e.g. socks5://127.0.0.1:6666&user:UserName&pass:PassWord + pmr.DnsProxyScheme = Rules.GetValue(rules, Rules.KEYS.DnsProxy, Rules.SUB_KEYS.FirstKey, out _, out _, Variables); + pmr.DnsProxyUser = Rules.GetValue(rules, Rules.KEYS.DnsProxy, Rules.SUB_KEYS.User, out _, out _, Variables); + pmr.DnsProxyPass = Rules.GetValue(rules, Rules.KEYS.DnsProxy, Rules.SUB_KEYS.Pass, out _, out _, Variables); + if (string.IsNullOrEmpty(pmr.DnsProxyScheme)) + { + pmr.DnsProxyScheme = Default_DnsProxyScheme; + pmr.DnsProxyUser = Default_DnsProxyUser; + pmr.DnsProxyPass = Default_DnsProxyPass; + } + + // SNI + pmr.Sni = Rules.GetValue(rules, Rules.KEYS.Sni, null, out _, out _, Variables); + if (string.IsNullOrEmpty(pmr.Sni)) pmr.Sni = Default_Sni; + + // Proxy e.g. socks5://127.0.0.1:6666&ifblock:1&user:UserName&pass:PassWord + pmr.ProxyScheme = Rules.GetValue(rules, Rules.KEYS.Proxy, Rules.SUB_KEYS.FirstKey, out _, out _, Variables); + string ifBlock = Rules.GetValue(rules, Rules.KEYS.Proxy, Rules.SUB_KEYS.IfBlock, out _, out _, Variables).ToLower().Trim(); + if (!string.IsNullOrEmpty(ifBlock)) + pmr.ProxyIfBlock = ifBlock.Equals("1") || ifBlock.Equals("true"); + pmr.ProxyUser = Rules.GetValue(rules, Rules.KEYS.Proxy, Rules.SUB_KEYS.User, out _, out _, Variables); + pmr.ProxyPass = Rules.GetValue(rules, Rules.KEYS.Proxy, Rules.SUB_KEYS.Pass, out _, out _, Variables); + if (string.IsNullOrEmpty(pmr.ProxyScheme)) + { + pmr.ProxyScheme = Default_ProxyScheme; + pmr.ProxyIfBlock = Default_ProxyIfBlock; + pmr.ProxyUser = Default_ProxyUser; + pmr.ProxyPass = Default_ProxyPass; + } + } + + MainRules_List.Add(pmr); + } + catch (Exception ex) + { + Debug.WriteLine("Proxy Rules_SetDomainRules: " + ex.Message); + } + } + + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/ProxyRules_ConnectToUpStream.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/ProxyRules_ConnectToUpStream.cs new file mode 100644 index 0000000..f833f6d --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/ProxyRules_ConnectToUpStream.cs @@ -0,0 +1,26 @@ +using MsmhToolsClass.ProxifiedClients; +using System.Net.Sockets; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public partial class AgnosticProgram +{ + public partial class ProxyRules + { + public async Task ConnectToUpStream(ProxyRequest req) + { + ProxyRulesResult prr = req.RulesResult; + string destHostname = req.Address; + int destHostPort = req.Port; + + if (!prr.ApplyUpStreamProxy) return null; + if (string.IsNullOrEmpty(prr.ProxyScheme)) return null; + + ProxifiedTcpClient proxifiedTcpClient = new(prr.ProxyScheme, prr.ProxyUser, prr.ProxyPass); + var upstream = await proxifiedTcpClient.TryGetConnectedProxifiedTcpClient(destHostname, destHostPort); + if (upstream.isSuccess && upstream.proxifiedTcpClient != null) return upstream.proxifiedTcpClient; + + return null; + } + } +} diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/ProxyRules_Get.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/ProxyRules_Get.cs new file mode 100644 index 0000000..9297a61 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/ProxyRules_Get.cs @@ -0,0 +1,186 @@ +using System.Diagnostics; +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public partial class AgnosticProgram +{ + public partial class ProxyRules + { + public class ProxyRulesResult + { + public bool IsMatch { get; set; } = false; + public bool IsBlackList { get; set; } = false; + public bool IsPortBlock { get; set; } = false; + public bool ApplyDpiBypass { get; set; } = true; + public string DnsCustomDomain { get; set; } = string.Empty; + public string Dns { get; set; } = string.Empty; + public List Dnss { get; set; } = new(); + public string Sni { get; set; } = string.Empty; + public bool ApplyUpStreamProxy { get; set; } = false; + public string ProxyScheme { get; set; } = string.Empty; + public bool ApplyUpStreamProxyToBlockedIPs { get; set; } = false; + public string ProxyUser { get; set; } = string.Empty; + public string ProxyPass { get; set; } = string.Empty; + } + + public async Task GetAsync(string client, string host, int port, AgnosticSettings settings) + { + ProxyRulesResult prr = new(); + if (string.IsNullOrEmpty(host)) return prr; + + try + { + prr.Dns = host; + + for (int n = 0; n < MainRules_List.Count; n++) + { + ProxyMainRules pmr = MainRules_List[n]; + + // Check If Match + bool isClientMatch = !string.IsNullOrEmpty(pmr.Client) && (pmr.Client.Equals(Rules.KEYS.AllClients) || client.Equals(pmr.Client) || client.EndsWith(pmr.Client)); + bool isDomainMatch = Rules.IsDomainMatch(host, pmr.Domain, out bool isWildcard, out string hostNoWww, out string ruleHostNoWww); + bool isMatch = isClientMatch && isDomainMatch; + if (!isMatch) continue; + + // Set Match + prr.IsMatch = isMatch; + + // Is Black List + prr.IsBlackList = pmr.IsBlock; + if (prr.IsBlackList) break; + + // Is Port Block + List blockedPorts = pmr.BlockPort.ToList(); + for (int i = 0; i < blockedPorts.Count; i++) + { + int blockedPort = blockedPorts[i]; + if (port == blockedPort) + { + prr.IsPortBlock = true; + break; + } + } + if (prr.IsPortBlock) break; + + // Apply DPI Bypass (Fragment & Change SNI) + prr.ApplyDpiBypass = !pmr.NoBypass; + + // DNS + if (!string.IsNullOrEmpty(pmr.FakeDns)) + { + // Fake DNS + prr.Dns = pmr.FakeDns; + } + else + { + // Get Dns Servers And Upstream Proxy + List dnss = pmr.Dnss.Any() ? pmr.Dnss : settings.DNSs; + prr.Dnss = dnss; + string? dnsProxyScheme = null, dnsProxyUser = null, dnsProxyPass = null; + if (!string.IsNullOrEmpty(pmr.DnsProxyScheme)) + { + dnsProxyScheme = pmr.DnsProxyScheme; + dnsProxyUser = pmr.DnsProxyUser; + dnsProxyPass = pmr.DnsProxyPass; + } + else + { + dnsProxyScheme = settings.UpstreamProxyScheme; + dnsProxyUser = settings.UpstreamProxyUser; + dnsProxyPass = settings.UpstreamProxyPass; + } + + // Get IP By Custom DNS + if (dnss.Any() && !NetworkTool.IsIp(host, out _)) + { + // Get Custom DNS Domain + prr.DnsCustomDomain = host; + if (!string.IsNullOrEmpty(pmr.DnsDomain)) + { + if (!pmr.DnsDomain.StartsWith("*.")) + { + prr.DnsCustomDomain = pmr.DnsDomain; + } + else + { + // Support: xxxx.example.com -> xxxx.domain.com + if (isWildcard) // ruleHostNoWww.StartsWith("*.") + { + if (hostNoWww.EndsWith(ruleHostNoWww[1..])) // Just In Case + { + prr.DnsCustomDomain = hostNoWww.Replace(ruleHostNoWww[1..], pmr.DnsDomain[1..]); + } + } + } + } + + IPAddress ipv4Addr = IPAddress.None; + if (settings.IsIPv4SupportedByISP) + { + ipv4Addr = await GetIP.GetIpFromDnsAddressAsync(prr.DnsCustomDomain, dnss, settings.AllowInsecure, settings.DnsTimeoutSec, false, settings.BootstrapIpAddress, settings.BootstrapPort, dnsProxyScheme, dnsProxyUser, dnsProxyPass); + if (ipv4Addr.Equals(IPAddress.None) && !settings.IsIPv6SupportedByISP) // Retry If IPv6 Is Not Supported + ipv4Addr = await GetIP.GetIpFromDnsAddressAsync(prr.DnsCustomDomain, dnss, settings.AllowInsecure, settings.DnsTimeoutSec, false, settings.BootstrapIpAddress, settings.BootstrapPort, dnsProxyScheme, dnsProxyUser, dnsProxyPass); + } + + if (ipv4Addr.Equals(IPAddress.None)) + { + IPAddress ipv6Addr = await GetIP.GetIpFromDnsAddressAsync(prr.DnsCustomDomain, dnss, settings.AllowInsecure, settings.DnsTimeoutSec, true, settings.BootstrapIpAddress, settings.BootstrapPort, dnsProxyScheme, dnsProxyUser, dnsProxyPass); + if (!ipv6Addr.Equals(IPAddress.IPv6None)) + prr.Dns = ipv6Addr.ToString(); + } + else + { + if (string.IsNullOrEmpty(settings.CloudflareCleanIP)) + prr.Dns = ipv4Addr.ToString(); + else + prr.Dns = CommonTools.IsCfIP(ipv4Addr) ? settings.CloudflareCleanIP : ipv4Addr.ToString(); + } + } + } + + // SNI + prr.Sni = pmr.Sni; + if (!string.IsNullOrEmpty(prr.Sni) && prr.Sni.StartsWith("*.")) + { + // Support: xxxx.example.com -> xxxx.domain.com + if (isWildcard) // ruleHostNoWww.StartsWith("*.") + { + if (hostNoWww.EndsWith(ruleHostNoWww[1..])) // Just In Case + { + prr.Sni = hostNoWww.Replace(ruleHostNoWww[1..], prr.Sni[1..]); + } + } + } + if (string.IsNullOrEmpty(prr.Sni)) prr.Sni = host; // Set SNI To Original Host If Not Defined + + // Upstream Proxy + if (!string.IsNullOrEmpty(pmr.ProxyScheme)) + { + pmr.ProxyScheme = pmr.ProxyScheme.ToLower().Trim(); + if (pmr.ProxyScheme.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + pmr.ProxyScheme.StartsWith("https://", StringComparison.OrdinalIgnoreCase) || + pmr.ProxyScheme.StartsWith("socks5://", StringComparison.OrdinalIgnoreCase)) + { + prr.ApplyUpStreamProxy = true; + prr.ProxyScheme = pmr.ProxyScheme; + prr.ApplyUpStreamProxyToBlockedIPs = pmr.ProxyIfBlock; + prr.ProxyUser = pmr.ProxyUser; + prr.ProxyPass = pmr.ProxyPass; + } + } + + // Break If Match + break; + } + } + catch (Exception ex) + { + Debug.WriteLine("ProxyRules_GetAsync: " + ex.Message); + } + + return prr; + } + + } +} diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/Rules.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/Rules.cs new file mode 100644 index 0000000..5c015b5 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/Programs/Rules.cs @@ -0,0 +1,174 @@ +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public partial class AgnosticProgram +{ + public static class Rules + { + public readonly struct KEYS + { + public static readonly string FirstKey = "FirstKey"; + public static readonly string AllClients = "AllClients"; + public static readonly string BlockPort = "blockport:"; + public static readonly string Dns = "dns:"; + public static readonly string DnsDomain = "dnsdomain:"; + public static readonly string DnsProxy = "dnsproxy:"; + public static readonly string Sni = "sni:"; + public static readonly string Proxy = "proxy:"; + } + + public readonly struct SUB_KEYS + { + public static readonly string FirstKey = "FirstKey"; + public static readonly string IfBlock = "&ifblock:"; + public static readonly string User = "&user:"; + public static readonly string Pass = "&pass:"; + } + + public static string GetValue(string line, string key, string? subKey, out bool isList, out List list, List> variables) + { + string result = line.Trim(); + isList = false; + list = new(); + + try + { + if (result.Contains(key, StringComparison.InvariantCultureIgnoreCase) || key == KEYS.FirstKey) + { + try + { + if (key == KEYS.FirstKey) + { + result = result.Remove(result.IndexOf(';')); + result = result.Trim(); + } + else + { + result = result.Remove(0, result.IndexOf(key, StringComparison.InvariantCultureIgnoreCase) + key.Length); + result = result.Remove(result.IndexOf(';')); + result = result.Trim(); + } + } + catch (Exception) { } + + if (!string.IsNullOrEmpty(subKey)) + { + if (subKey.Equals(SUB_KEYS.FirstKey, StringComparison.InvariantCultureIgnoreCase)) + { + if (result.Contains('&')) + { + try + { + result = result.Remove(result.IndexOf('&')); + } + catch (Exception) { } + } + } + else + { + if (result.Contains(subKey, StringComparison.InvariantCultureIgnoreCase)) + { + try + { + result = result.Remove(0, result.IndexOf(subKey, StringComparison.InvariantCultureIgnoreCase) + subKey.Length); + } + catch (Exception) { } + + if (result.Contains('&') && result.Contains(':')) + { + try + { + result = result.Remove(result.IndexOf('&')); + } + catch (Exception) { } + } + } + } + } + + if (!result.Contains(',')) + { + // Not A List + return ApplyVariables(result, variables); + } + else + { + // It's A List + isList = true; + string[] split = result.Split(','); + for (int n = 0; n < split.Length; n++) + { + string value = split[n].Trim(); + list.Add(ApplyVariables(value, variables)); + } + if (list.Any()) return list[0]; + } + } + } + catch (Exception) { } + + return string.Empty; + } + + private static string ApplyVariables(string vari, List> variables) + { + string result = vari; + try + { + variables = variables.ToList(); + for (int n = 0; n < variables.Count; n++) + { + Tuple tuple = variables[n]; + if (vari.Equals(tuple.Item1)) + { + result = tuple.Item2; break; + } + } + } + catch (Exception ex) + { + Debug.WriteLine("Rules_ApplyVariables: " + ex.Message); + } + return result; + } + + public static bool IsDomainMatch(string host, string ruleHost, out bool isWildcard, out string hostNoWWW, out string ruleHostNoWWW) + { + isWildcard = false; + hostNoWWW = host.ToLower().Trim(); + ruleHostNoWWW = ruleHost.ToLower().Trim(); + + try + { + if (hostNoWWW.StartsWith("www.")) + hostNoWWW = hostNoWWW.TrimStart("www."); + if (hostNoWWW.EndsWith('/')) hostNoWWW = hostNoWWW[0..^1]; + + if (ruleHostNoWWW.StartsWith("www.")) + ruleHostNoWWW = ruleHostNoWWW.TrimStart("www."); + if (ruleHostNoWWW.EndsWith('/')) ruleHostNoWWW = ruleHostNoWWW[0..^1]; + + if (!string.IsNullOrEmpty(ruleHostNoWWW)) + { + if (ruleHostNoWWW.Equals("*")) return true; // No Wildcard + + if (!ruleHostNoWWW.StartsWith("*.")) + { + // No Wildcard + if (ruleHostNoWWW.Equals(hostNoWWW)) return true; + } + else + { + // Wildcard + isWildcard = true; + if (!hostNoWWW.Equals(ruleHostNoWWW[2..]) && hostNoWWW.EndsWith(ruleHostNoWWW[1..])) return true; + } + } + } + catch (Exception) { } + + return false; + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ApplyPrograms.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ApplyPrograms.cs new file mode 100644 index 0000000..117ddc3 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ApplyPrograms.cs @@ -0,0 +1,368 @@ +using System.Diagnostics; +using System.Net; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public partial class MsmhAgnosticServer +{ + public async Task ApplyPrograms(IPAddress clientIP, ProxyRequest? req) + { + try + { + if (Cancel) return null; + if (req == null) return null; + if (string.IsNullOrEmpty(req.Address)) return null; + if (req.Address.Equals("0.0.0.0")) return null; + if (req.Address.StartsWith("10.")) return null; + req.ClientIP = clientIP; + + // Event + string msgReqEvent = $"[{req.ClientIP}] [{req.ProxyName}] "; + + if (req.ProxyName == Proxy.Name.HTTP || req.ProxyName == Proxy.Name.HTTPS) + msgReqEvent += $"[{req.HttpMethod}] "; + + if (req.ProxyName == Proxy.Name.Socks4 || req.ProxyName == Proxy.Name.Socks4A || req.ProxyName == Proxy.Name.Socks5) + msgReqEvent += $"[{req.Command}] "; + + // Count Max Requests + string checkRequest = $"{clientIP}_{req.AddressOrig}"; + bool isKeyExist = DelinquentRequests.TryGetValue(checkRequest, out DateTime lastTime); + if (isKeyExist) + { + DateTime now = DateTime.UtcNow; + TimeSpan ts = now - lastTime; + if (ts <= TimeSpan.FromMilliseconds(0.4)) + { + DelinquentRequests.TryUpdate(checkRequest, now, lastTime); + + // Event + msgReqEvent += $"Delinquent Request Detected - Request Denied ({req.AddressOrig}:{req.Port})"; + OnRequestReceived?.Invoke(msgReqEvent, EventArgs.Empty); + return null; + } + else DelinquentRequests.TryRemove(checkRequest, out _); + } + else DelinquentRequests.TryAdd(checkRequest, DateTime.UtcNow); + + // Cache Requests + var cachedReq = ProxyRequestsCaches.Get(checkRequest, req); + if (cachedReq != null) + { + OnRequestReceived?.Invoke(cachedReq.Value.eventMsg, EventArgs.Empty); + return cachedReq.Value.pReq; + } + + // Test Requests + bool isTestRequestExist = TestRequests.TryGetValue(req.AddressOrig, out (DateTime dt, bool applyFakeSNI, bool applyFragment) testReq); + if (isTestRequestExist) + { + DateTime now = DateTime.UtcNow; + TimeSpan ts = now - testReq.dt; + if (ts >= TimeSpan.FromMinutes(2)) + { + TestRequests.TryRemove(req.AddressOrig, out _); + isTestRequestExist = false; + } + } + + // Apply Programs + req.TimeoutSec = Settings_.ProxyTimeoutSec; + + // Block Port 80 + if (Settings_.BlockPort80 && req.Port == 80) + { + // Event + msgReqEvent += $"Block Port 80: {req.Address}:{req.Port}, Request Denied."; + OnRequestReceived?.Invoke(msgReqEvent, EventArgs.Empty); + return null; + } + + //// ProxyRules Program + AgnosticProgram.ProxyRules.ProxyRulesResult rr = new(); + if (ProxyRulesProgram.RulesMode != AgnosticProgram.ProxyRules.Mode.Disable) + { + rr = await ProxyRulesProgram.GetAsync(req.ClientIP.ToString(), req.Address, req.Port, Settings_); + } + + if (rr.IsMatch) + { + // Black List + if (rr.IsBlackList) + { + // Event + msgReqEvent += $"Black List: {req.Address}:{req.Port}, Request Denied."; + OnRequestReceived?.Invoke(msgReqEvent, EventArgs.Empty); + return null; + } + + // Block Port + if (rr.IsPortBlock) + { + // Event + msgReqEvent += $"Block Port {req.Port}: {req.Address}:{req.Port}, Request Denied."; + OnRequestReceived?.Invoke(msgReqEvent, EventArgs.Empty); + return null; + } + + // Apply DPI Bypass If Is Match + req.ApplyFragment = rr.ApplyDpiBypass && IsFragmentActive; + req.ApplyChangeSNI = rr.ApplyDpiBypass && IsFakeSniActive; + + // Fake DNS Or Custom DNS + bool isDnsIp = NetworkTool.IsIp(rr.Dns, out _); + if (isDnsIp) req.Address = rr.Dns; + } + else + { + // Apply DPI Bypass If Is Not Match + req.ApplyFragment = IsFragmentActive; + req.ApplyChangeSNI = IsFakeSniActive; + } + + // UDP Does Not Support Fragmentation + if (req.ProxyName == Proxy.Name.Socks5 && req.Command == Socks.Commands.UDP) + req.ApplyFragment = false; + + // Override For Test + if (req.ProxyName == Proxy.Name.Test && isTestRequestExist) + { + req.ApplyFragment = testReq.applyFragment; + req.ApplyChangeSNI = testReq.applyFakeSNI; + } + + //// FakeSNI Program + if (req.ApplyChangeSNI) + { + if (rr.IsMatch) + if (!string.IsNullOrEmpty(rr.Sni) && !rr.Sni.Equals(req.AddressOrig)) + req.AddressSNI = rr.Sni; + + if (req.AddressSNI.Equals(req.AddressOrig)) + { + string defaultSni = SettingsSSL_.DefaultSni; + if (!string.IsNullOrWhiteSpace(defaultSni)) + { + req.AddressSNI = defaultSni; + } + } + + if (string.IsNullOrWhiteSpace(req.AddressSNI) || req.AddressSNI.Equals(req.AddressOrig)) + req.ApplyChangeSNI = false; + } + + // Check If Address Is An IP + bool isIp = NetworkTool.IsIp(req.Address, out _); + + //// Apply DNS To Proxy Request + if (req.AddressOrig.Equals(req.Address) && !isIp) + { + string dnsServer = Settings_.ServerUdpDnsAddress; + + IPAddress ipv4Addr = IPAddress.None; + if (Settings_.IsIPv4SupportedByISP) + { + ipv4Addr = await GetIP.GetIpFromDnsAddressAsync(req.Address, dnsServer, false, Settings_); + if (ipv4Addr.Equals(IPAddress.None) && !Settings_.IsIPv6SupportedByISP) // Retry If IPv6 Is Not Supported + ipv4Addr = await GetIP.GetIpFromDnsAddressAsync(req.Address, dnsServer, false, Settings_); + } + + if (ipv4Addr.Equals(IPAddress.None)) + { + IPAddress ipv6Addr = await GetIP.GetIpFromDnsAddressAsync(req.Address, dnsServer, true, Settings_); + if (!ipv6Addr.Equals(IPAddress.IPv6None)) + req.Address = ipv6Addr.ToString(); + } + else + { + if (string.IsNullOrEmpty(Settings_.CloudflareCleanIP)) + req.Address = ipv4Addr.ToString(); + else + req.Address = CommonTools.IsCfIP(ipv4Addr) ? Settings_.CloudflareCleanIP : ipv4Addr.ToString(); + } + } + + // Event + if (req.AddressOrig.Equals(req.Address)) + msgReqEvent += $"{req.AddressOrig}:{req.Port}"; + else + { + if (!string.IsNullOrEmpty(rr.DnsCustomDomain) && !req.AddressOrig.Equals(rr.DnsCustomDomain)) + msgReqEvent += $"{req.AddressOrig}:{req.Port} => {rr.DnsCustomDomain} => {req.Address}"; + else + msgReqEvent += $"{req.AddressOrig}:{req.Port} => {req.Address}"; + } + + HttpStatusCode httpStatus = HttpStatusCode.RequestTimeout; + if (req.ApplyChangeSNI) + { + msgReqEvent += $" => {req.AddressSNI}"; + if (req.ProxyName != Proxy.Name.Test) + { + HttpStatusCode hsc = await NetworkTool.GetHttpStatusCode($"https://{req.AddressOrig}:{req.Port}", null, 5000, false, Settings_.ServerHttpProxyAddress).ConfigureAwait(false); + if (hsc == HttpStatusCode.Forbidden || hsc == HttpStatusCode.MisdirectedRequest || hsc == HttpStatusCode.InternalServerError || hsc == HttpStatusCode.RequestTimeout) + { + HttpStatusCode hsc2 = await NetworkTool.GetHttpStatusCode($"https://{req.AddressOrig}:{req.Port}", null, 5000, false).ConfigureAwait(false); + if (hsc2 != HttpStatusCode.RequestTimeout) + { + req.ApplyChangeSNI = false; + req.ApplyFragment = false; + msgReqEvent += $" ({hsc2}) => OFF"; + httpStatus = hsc2; + } + else if (hsc != HttpStatusCode.RequestTimeout) + { + req.ApplyChangeSNI = false; + msgReqEvent += $" ({hsc})"; + TestRequests.AddOrUpdate(req.AddressOrig, (DateTime.UtcNow, req.ApplyChangeSNI, req.ApplyFragment)); + httpStatus = hsc; + } + } + } + } + + if (!req.ApplyChangeSNI && req.ApplyFragment) + { + msgReqEvent += " => Fragmented"; + if (req.ProxyName != Proxy.Name.Test) + { + HttpStatusCode hsc = await NetworkTool.GetHttpStatusCode($"https://{req.AddressOrig}:{req.Port}", null, 3000, false, Settings_.ServerHttpProxyAddress).ConfigureAwait(false); + if (hsc != HttpStatusCode.OK && hsc != HttpStatusCode.NotFound && hsc != HttpStatusCode.RequestTimeout) + { + req.ApplyFragment = false; + msgReqEvent += $" ({hsc}) => OFF"; + TestRequests.AddOrUpdate(req.AddressOrig, (DateTime.UtcNow, req.ApplyChangeSNI, req.ApplyFragment)); + httpStatus = hsc; + } + } + } + + // Check If IP Is Blocked + isIp = NetworkTool.IsIp(req.Address, out IPAddress? ip); + bool isIpv6 = false; + if (isIp && ip != null) + { + isIpv6 = NetworkTool.IsIPv6(ip); + if ((!isIpv6 && Settings_.IsIPv4SupportedByISP) || (isIpv6 && Settings_.IsIPv6SupportedByISP)) + { + if (req.ProxyName != Proxy.Name.Test) + { + if (httpStatus == HttpStatusCode.RequestTimeout || httpStatus == HttpStatusCode.Forbidden) + { + httpStatus = await NetworkTool.GetHttpStatusCode($"https://{req.AddressOrig}:{req.Port}", null, 5000, false, Settings_.ServerHttpProxyAddress).ConfigureAwait(false); + req.IsDestBlocked = httpStatus == HttpStatusCode.RequestTimeout || httpStatus == HttpStatusCode.Forbidden; + + //if (req.ProxyName == Proxy.Name.Socks5 && req.Command == Socks.Commands.UDP) + // req.IsDestBlocked = !await NetworkTool.CanUdpConnect(req.Address, req.Port, 5000); + //else + //{ + // bool canPing = await NetworkTool.CanPing(req.Address, 5000); + // bool canTcpConnect = await NetworkTool.CanTcpConnect(req.Address, req.Port, 5000); + // Debug.WriteLine("===--> " + req.AddressOrig + " -> " + httpStatus + " -> " + canPing + " -> " + canTcpConnect); + // req.IsDestBlocked = !canPing || !canTcpConnect; + //} + + if (req.IsDestBlocked) msgReqEvent += $" ({httpStatus})"; + } + } + } + else + { + req.IsDestBlocked = true; // IP Protocol Is Not Supported + string ipP = isIpv6 ? "IPv6" : "IPv4"; + msgReqEvent += $" (Your Network Does Not Support {ipP})"; + } + } + + // Apply Upstream? + if ((rr.IsMatch && rr.ApplyUpStreamProxy && !rr.ApplyUpStreamProxyToBlockedIPs) || + (rr.IsMatch && rr.ApplyUpStreamProxy && rr.ApplyUpStreamProxyToBlockedIPs && req.IsDestBlocked)) + { + if (!IsUpstreamEqualToServerAddress(rr.ProxyScheme)) + { + req.ApplyUpStreamProxy = true; + req.ApplyChangeSNI = false; + req.ApplyFragment = false; + msgReqEvent += $" (Using Upstream: {rr.ProxyScheme})"; + } + } + + if (!req.ApplyUpStreamProxy && !string.IsNullOrWhiteSpace(Settings_.UpstreamProxyScheme)) + { + if ((!Settings_.ApplyUpstreamOnlyToBlockedIps) || + (Settings_.ApplyUpstreamOnlyToBlockedIps && req.IsDestBlocked)) + { + if (!IsUpstreamEqualToServerAddress(Settings_.UpstreamProxyScheme)) + { + req.ApplyUpStreamProxy = true; + req.ApplyChangeSNI = false; + req.ApplyFragment = false; + msgReqEvent += $" (Using Upstream: {Settings_.UpstreamProxyScheme.ToLower()})"; + } + } + } + + // Block Request Without DNS IP + bool blockReq = !req.AddressIsIp && req.AddressOrig.Equals(req.Address) && !req.ApplyUpStreamProxy; + if (blockReq) msgReqEvent += " Request Denied (Has No DNS IP)"; + + //Debug.WriteLine(msgReqEvent); + if (req.ProxyName != Proxy.Name.Test) + OnRequestReceived?.Invoke(msgReqEvent, EventArgs.Empty); + + if (blockReq) return null; + + // Change AddressType Based On DNS + if (req.ProxyName == Proxy.Name.HTTP || + req.ProxyName == Proxy.Name.HTTPS || + (req.ProxyName == Proxy.Name.Socks4A && req.AddressType == Socks.AddressType.Domain) || + (req.ProxyName == Proxy.Name.Socks5 && req.AddressType == Socks.AddressType.Domain)) + { + if (isIp && ip != null) + { + if (isIpv6) req.AddressType = Socks.AddressType.Ipv6; + else + req.AddressType = Socks.AddressType.Ipv4; + } + } + + // Add To Cache + ProxyRequestsCaches.Add(checkRequest, req, msgReqEvent); + + return req; + } + catch (Exception ex) + { + Debug.WriteLine("AgnosticServer ApplyPrograms: " + ex.Message); + return null; + } + } + + private bool IsUpstreamEqualToServerAddress(string? proxyScheme) + { + bool result = false; + + try + { + if (!string.IsNullOrEmpty(proxyScheme)) + { + NetworkTool.GetUrlDetails(proxyScheme, 443, out _, out string host, out _, out _, out int port, out _, out _); + if (Settings_.ListenerPort == port) + { + bool isIP = NetworkTool.IsIp(host, out IPAddress? ip); + if (isIP && ip != null) + { + if (IPAddress.IsLoopback(ip)) result = true; + } + else + { + if (host.ToLower().Equals("localhost")) result = true; + } + } + } + } + catch (Exception) { } + + return result; + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/EventArgs.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/EventArgs.cs similarity index 91% rename from MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/EventArgs.cs rename to MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/EventArgs.cs index 378bf0b..84cd892 100644 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/EventArgs.cs +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/EventArgs.cs @@ -1,4 +1,4 @@ -namespace MsmhToolsClass.MsmhProxyServer; +namespace MsmhToolsClass.MsmhAgnosticServer; public class DataEventArgs : EventArgs { diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ProxyClient.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyClient.cs similarity index 77% rename from MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ProxyClient.cs rename to MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyClient.cs index abb7e9f..6c2c743 100644 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ProxyClient.cs +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyClient.cs @@ -1,11 +1,10 @@ using System.Diagnostics; using System.Net.Sockets; -namespace MsmhToolsClass.MsmhProxyServer; +namespace MsmhToolsClass.MsmhAgnosticServer; public class ProxyClient { - private byte[] Buffer_ { get; set; } private bool Disposed_ { get; set; } = false; public Socket Socket_ { get; set; } @@ -16,9 +15,7 @@ public ProxyClient(Socket socket) { // Start Data Exchange. Socket_ = socket; - int packetSize = MsmhProxyServer.MaxDataSize; - Buffer_ = new byte[packetSize]; - Socket_.ReceiveBufferSize = packetSize; + Socket_.ReceiveBufferSize = MsmhAgnosticServer.MaxDataSize; } public async Task StartReceiveAsync() @@ -27,8 +24,9 @@ public async Task StartReceiveAsync() { if (Disposed_ || Socket_ is null) return; + byte[] buffer = new byte[MsmhAgnosticServer.MaxDataSize]; int received = 0; - try { received = await Socket_.ReceiveAsync(Buffer_, SocketFlags.None); } catch (Exception) { /* HSTS / Timeout / Done */ } + try { received = await Socket_.ReceiveAsync(buffer, SocketFlags.None).ConfigureAwait(false); } catch (Exception) { /* HSTS / Timeout / Done */ } if (received <= 0) { @@ -36,8 +34,7 @@ public async Task StartReceiveAsync() return; } - byte[] buffer = new byte[received]; - Buffer.BlockCopy(Buffer_, 0, buffer, 0, received); + buffer = buffer[..received]; DataEventArgs data = new(this, buffer); OnDataReceived?.Invoke(this, data); @@ -49,11 +46,11 @@ public async Task StartReceiveAsync() } } - public async Task ReceiveAsync(byte[] data) + public async Task ReceiveAsync(byte[] data, SocketFlags socketFlags = SocketFlags.None) { try { - int received = await Socket_.ReceiveAsync(data, SocketFlags.None); + int received = await Socket_.ReceiveAsync(data, socketFlags).ConfigureAwait(false); if (received <= 0) { @@ -77,7 +74,7 @@ public async Task SendAsync(byte[] buffer) { if (Socket_ != null && Socket_.Connected) { - int sent = await Socket_.SendAsync(buffer, SocketFlags.None); + int sent = await Socket_.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false); if (sent <= 0) { @@ -116,10 +113,7 @@ public void Disconnect(TcpClient? tcpClient = null) } } } - catch(Exception) - { - // do nothing - } + catch(Exception) { } } } \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ProxyClientSSL.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyClientSSL.cs similarity index 73% rename from MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ProxyClientSSL.cs rename to MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyClientSSL.cs index 094fbe1..25440ba 100644 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ProxyClientSSL.cs +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyClientSSL.cs @@ -2,11 +2,11 @@ using System.Net.NetworkInformation; using System.Net.Security; using System.Net.Sockets; -using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography; +using System.Net; -namespace MsmhToolsClass.MsmhProxyServer; +namespace MsmhToolsClass.MsmhAgnosticServer; public class ProxyClientSSL { @@ -15,8 +15,8 @@ public class ProxyClientSSL public ProxyRequest Request { get; set; } private TcpClient ClientTcpClient { get; set; } private TcpClient RemoteTcpClient { get; set; } - public Stream ClientStream { get; set; } - public Stream RemoteStream { get; set; } + public Stream? ClientStream { get; set; } + public Stream? RemoteStream { get; set; } public event EventHandler? OnClientDataReceived; public event EventHandler? OnClientDataSent; @@ -25,7 +25,7 @@ public class ProxyClientSSL public event EventHandler? OnRemoteDataSent; private readonly ProxyTunnel? ProxyTunnel_ = null; - private readonly SettingsSSL SettingsSSL_ = new(false); + private readonly AgnosticSettingsSSL SettingsSSL_ = new(false); private readonly Stopwatch KillOnTimeout = new(); internal ProxyClientSSL(ProxyTunnel proxyTunnel) @@ -35,8 +35,15 @@ internal ProxyClientSSL(ProxyTunnel proxyTunnel) ClientTcpClient = new(); ClientTcpClient.Client = ProxyTunnel_.Client.Socket_; RemoteTcpClient = new(); RemoteTcpClient.Client = ProxyTunnel_.RemoteClient.Socket_; - ClientStream = ClientTcpClient.GetStream(); - RemoteStream = RemoteTcpClient.GetStream(); + try + { + ClientStream = ClientTcpClient.GetStream(); + RemoteStream = RemoteTcpClient.GetStream(); + } + catch (Exception) + { + Disconnect(); + } Request = ProxyTunnel_.Req; SettingsSSL_ = ProxyTunnel_.SettingsSSL_; @@ -51,18 +58,25 @@ public async Task Execute() // Start Data Exchange. await Task.Run(async () => { - bool isDecryptSuccess = await DecryptHttpsTrafficAsync(ClientStream, RemoteStream, Request); + + bool isDecryptSuccess = false; + + if (ClientStream != null && RemoteStream != null) + { + isDecryptSuccess = await DecryptHttpsTrafficAsync(ClientStream, RemoteStream, Request).ConfigureAwait(false); + } + if (!isDecryptSuccess) { Disconnect(); return; } - + while (IsActive()) { Task c = ReadClient(); Task r = ReadRemote(); - await Task.WhenAll(c, r); + await Task.WhenAll(c, r).ConfigureAwait(false); } Disconnect(); @@ -76,17 +90,17 @@ await Task.Run(async () => while (IsActive()) { if (Disposed_) break; + if (ClientStream == null) break; if (!ClientStream.CanRead) break; - byte[] clientBufferInit = new byte[65536]; byte[] clientBuffer = Array.Empty(); try { - int clientRead = await ClientStream.ReadAsync(clientBufferInit, CancellationToken.None); + int clientRead = await ClientStream.ReadAsync(clientBufferInit, CancellationToken.None).ConfigureAwait(false); if (clientRead == 0) break; clientBuffer = new byte[clientRead]; Buffer.BlockCopy(clientBufferInit, 0, clientBuffer, 0, clientRead); - + // Client Received SSLDataEventArgs data = new(this, clientBuffer); OnClientDataReceived?.Invoke(this, data); @@ -97,22 +111,20 @@ await Task.Run(async () => break; } + if (RemoteStream == null) break; if (!RemoteStream.CanWrite) break; try { if (clientBuffer.Length == 0) break; - await RemoteStream.WriteAsync(clientBuffer, CancellationToken.None); + await RemoteStream.WriteAsync(clientBuffer, CancellationToken.None).ConfigureAwait(false); // Remote Sent SSLDataEventArgs data = new(this, clientBuffer); OnRemoteDataSent?.Invoke(this, data); } - catch (Exception) - { - // do nothing - } + catch (Exception) { } } }); } @@ -124,13 +136,14 @@ await Task.Run(async () => while (IsActive()) { if (Disposed_) break; + if (RemoteStream == null) break; if (!RemoteStream.CanRead) break; byte[] remoteBufferInit = new byte[65536]; byte[] remoteBuffer = Array.Empty(); try { - int remoteRead = await RemoteStream.ReadAsync(remoteBufferInit, CancellationToken.None); + int remoteRead = await RemoteStream.ReadAsync(remoteBufferInit, CancellationToken.None).ConfigureAwait(false); if (remoteRead == 0) break; remoteBuffer = new byte[remoteRead]; Buffer.BlockCopy(remoteBufferInit, 0, remoteBuffer, 0, remoteRead); @@ -145,22 +158,20 @@ await Task.Run(async () => break; } + if (ClientStream == null) break; if (!ClientStream.CanWrite) break; try { if (remoteBuffer.Length == 0) break; - await ClientStream.WriteAsync(remoteBuffer, CancellationToken.None); + await ClientStream.WriteAsync(remoteBuffer, CancellationToken.None).ConfigureAwait(false); // Client Sent SSLDataEventArgs data = new(this, remoteBuffer); OnClientDataSent?.Invoke(this, data); } - catch (Exception) - { - // do nothing - } + catch (Exception) { } } }); @@ -179,18 +190,18 @@ private bool IsClientActive() if (ClientTcpClient.Client != null) { TcpState clientState = GetTcpRemoteState(ClientTcpClient); - - if (clientState == TcpState.Established - || clientState == TcpState.Listen - || clientState == TcpState.SynReceived - || clientState == TcpState.SynSent - || clientState == TcpState.TimeWait) + + if (clientState == TcpState.Established || + clientState == TcpState.Listen || + clientState == TcpState.SynReceived || + clientState == TcpState.SynSent || + clientState == TcpState.TimeWait) { clientSocketActive = true; } } } - + return clientActive && clientSocketActive; } @@ -212,17 +223,16 @@ public bool IsActive() active = false; KillOnTimeout.Stop(); } - else - active = IsClientActive() && IsRemoteActive(); + else active = IsClientActive() && IsRemoteActive(); return active; } - private TcpState GetTcpRemoteState(TcpClient tcpClient) + private static TcpState GetTcpRemoteState(TcpClient tcpClient) { try { if (tcpClient.Client == null) return TcpState.Unknown; - + IPGlobalProperties ipgp = IPGlobalProperties.GetIPGlobalProperties(); if (ipgp != null) { @@ -236,8 +246,15 @@ private TcpState GetTcpRemoteState(TcpClient tcpClient) { if (tcpClient.Client != null) { - if (tci.RemoteEndPoint.Equals(tcpClient.Client.RemoteEndPoint)) - return tci.State; + if (tcpClient.Client.RemoteEndPoint is IPEndPoint tcpClientEndPoint) + { + if (tcpClientEndPoint.Address.Equals(tci.RemoteEndPoint.Address) || tcpClientEndPoint.Address.ToString().EndsWith(tci.RemoteEndPoint.Address.ToString())) + if (tci.RemoteEndPoint.Port.Equals(tcpClientEndPoint.Port)) + { + return tci.State; + } + + } } } } @@ -260,37 +277,37 @@ private async Task DecryptHttpsTrafficAsync(Stream clientStream, Stream re if (!clientStream.CanRead || !remoteStream.CanRead) return false; //ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; - static bool callback(object sender, X509Certificate? cert, X509Chain? chain, SslPolicyErrors sslPolicyErrors) => true; - SslProtocols protocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12; - string pass = Guid.NewGuid().ToString(); List domains = new() { - req.AddressOrig, req.Address, req.AddressSNI + req.AddressOrig, req.Address }; + if (!req.AddressSNI.Equals(req.AddressOrig)) domains.Add(req.AddressSNI); + string certSubject = CommonTools.GetWildCardDomainName(req.AddressOrig); X509Certificate2 certificate = CertificateTool.GenerateCertificateByIssuer(SettingsSSL_.RootCA, domains, certSubject, out RSA privateKey); if (!certificate.HasPrivateKey) certificate = certificate.CopyWithPrivateKey(privateKey); + string pass = Guid.NewGuid().ToString(); certificate = new(certificate.Export(X509ContentType.Pfx, pass), pass); - Debug.WriteLine("Serial Number: " + certificate.SerialNumber); - //===== Server Authentication - SslStream sslStreamClient = new(clientStream, false, callback, null); - SslServerAuthenticationOptions optionsServer = new(); - optionsServer.ServerCertificate = certificate; - optionsServer.ClientCertificateRequired = false; - optionsServer.EnabledSslProtocols = protocols; - optionsServer.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; - - await sslStreamClient.AuthenticateAsServerAsync(optionsServer, CancellationToken.None); + SslServerAuthenticationOptions optionsServer = new() + { + ServerCertificate = certificate, + ClientCertificateRequired = false, + EnabledSslProtocols = MsmhAgnosticServer.SSL_Protocols, + CertificateRevocationCheckMode = X509RevocationMode.NoCheck, + RemoteCertificateValidationCallback = MsmhAgnosticServer.Callback + }; + SslStream sslStreamClient = new(clientStream, false, MsmhAgnosticServer.Callback, null); + await sslStreamClient.AuthenticateAsServerAsync(optionsServer, CancellationToken.None).ConfigureAwait(false); + // Update Client Stream ClientStream = sslStreamClient; //===== Client Authentication - SslStream sslStreamRemote = new(remoteStream, false, callback, null); SslClientAuthenticationOptions optionsClient = new(); // Apply DontBypass Program @@ -308,11 +325,13 @@ private async Task DecryptHttpsTrafficAsync(Stream clientStream, Stream re optionsClient.TargetHost = req.AddressOrig; } } - - optionsClient.EnabledSslProtocols = protocols; + + optionsClient.EnabledSslProtocols = MsmhAgnosticServer.SSL_Protocols; optionsClient.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; - - await sslStreamRemote.AuthenticateAsClientAsync(optionsClient, CancellationToken.None); + optionsClient.RemoteCertificateValidationCallback = MsmhAgnosticServer.Callback; + + SslStream sslStreamRemote = new(remoteStream, false, MsmhAgnosticServer.Callback, null); + await sslStreamRemote.AuthenticateAsClientAsync(optionsClient, CancellationToken.None).ConfigureAwait(false); // Update Remote Stream RemoteStream = sslStreamRemote; @@ -323,7 +342,7 @@ private async Task DecryptHttpsTrafficAsync(Stream clientStream, Stream re } catch (Exception ex) { - Debug.WriteLine("======= DecryptHttpsTrafficAsync:\n" + ex.GetInnerExceptions()); + Debug.WriteLine("======= DecryptHttpsTrafficAsync:\n" + ex.Message); return false; } } @@ -344,14 +363,11 @@ public void Disconnect() RemoteTcpClient?.Dispose(); Disposed_ = true; - if (ProxyTunnel_ != null) ProxyTunnel_.Disconnect = true; + if (ProxyTunnel_ != null) ProxyTunnel_.ManualDisconnect = true; return; } } - catch(Exception) - { - // do nothing - } + catch(Exception) { } } } \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Enums.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyEnums.cs similarity index 92% rename from MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Enums.cs rename to MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyEnums.cs index 0856b21..025b204 100644 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Enums.cs +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyEnums.cs @@ -1,15 +1,16 @@ -namespace MsmhToolsClass.MsmhProxyServer; +namespace MsmhToolsClass.MsmhAgnosticServer; public class Proxy { public enum Name { - Local = 0, + Test = 0, HTTP = 1, HTTPS = 2, Socks4 = 3, Socks4A = 4, - Socks5 = 5 + Socks5 = 5, + SniProxy = 6 } } diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ProxyRequest.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyRequest.cs similarity index 64% rename from MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ProxyRequest.cs rename to MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyRequest.cs index 468cdd9..87a60c9 100644 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ProxyRequest.cs +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyRequest.cs @@ -1,125 +1,47 @@ -using MsmhToolsClass.DnsTool; -using MsmhToolsClass.ProxyServerPrograms; -using System.Collections.Specialized; using System.Diagnostics; using System.Net; using System.Text; -namespace MsmhToolsClass.MsmhProxyServer; +namespace MsmhToolsClass.MsmhAgnosticServer; public class ProxyRequest { // More than this means there is no internet or destination is blocked so it's better to cancel the request private static readonly int TimeoutRequestCreationMS = 200; - public static ProxyRequest? RequestLocal(ProxyClient client) + public static async Task RequestHTTP_S(byte[] firstBuffer, CancellationToken ct) { - // Just to get Local Ip and Port - if (client.Socket_.RemoteEndPoint is not IPEndPoint ipEndPoint) return null; - IPAddress ipAddress = ipEndPoint.Address; - int port = ipEndPoint.Port; - if (ipAddress == null) return null; - - return new ProxyRequest(Proxy.Name.Local, Socks.Version.Zero, Socks.Commands.Connect, Socks.AddressType.Ipv4, ipAddress.ToString(), port, string.Empty, string.Empty); - } - - public static async Task RequestHttpRemote(byte[] firstBuffer, CancellationToken ct) - { - Task task = Task.Run(async () => + Task task = Task.Run(() => { try { // Set Security Protocols - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; - - RestResponse? restResponse = null; - HttpMethod httpMethod = HttpMethod.Post; - Proxy.Name proxyName = Proxy.Name.Local; - string address = string.Empty; - int port = -1; - string path = string.Empty; - bool keepalive = false; - string useragent = string.Empty; - long contentLength = 0; - string contentType = string.Empty; - NameValueCollection httpHeaders = new(StringComparer.InvariantCultureIgnoreCase); - - // Convert to String List - string str = Encoding.UTF8.GetString(firstBuffer); - string[] headers = str.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries); - - // Process Each Line - for (int n = 0; n < headers.Length; n++) - { - if (n == 0) - { - // First Line - string[] requestLine = headers[n].Trim().Trim('\0').Split(' '); - if (requestLine.Length < 3) return null; // Request line does not contain at least three parts (method, raw URL, protocol/version). - - // Set Proxy Name - httpMethod = CommonTools.GetHttpMethod.Parse(requestLine[0]); - proxyName = httpMethod == HttpMethod.Post ? Proxy.Name.HTTPS : Proxy.Name.HTTP; + //ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; - // Set Default Port - int defaultPort = proxyName == Proxy.Name.HTTPS ? 443 : 80; - - // Get Address & Port - string hostPort = requestLine[1]; - NetworkTool.GetUrlDetails(hostPort, defaultPort, out _, out address, out _, out _, out port, out path, out _); - - if (address.StartsWith('[')) address = address.TrimStart('['); - if (address.EndsWith(']')) address = address.TrimEnd(']'); - // string protocolVersion = requestLine[2]; - } - else - { - // Subsequent Line - string[] headerLine = headers[n].Split(':'); - if (headerLine.Length == 2) - { - string key = headerLine[0].Trim(); - string val = headerLine[1].Trim(); - if (string.IsNullOrEmpty(key)) continue; - string keyEval = key.ToLower(); - - if (keyEval.Equals("keep-alive")) - keepalive = Convert.ToBoolean(val); - else if (keyEval.Equals("user-agent")) - useragent = val; - else if (keyEval.Equals("content-length")) - contentLength = Convert.ToInt64(val); - else if (keyEval.Equals("content-type")) - contentType = val; - else - httpHeaders = CommonTools.AddToDict(key, val, httpHeaders); - } - } - } + HttpRequestResult hrResult = HttpRequest.Read(firstBuffer); + if (!hrResult.IsSuccess) return null; + if (hrResult.URI == null) return null; + HttpRequest httpRequest = new() + { + IsForHttpProxy = true, + URI = hrResult.URI, + Method = hrResult.Method, + Headers = hrResult.Headers, + UserAgent = hrResult.UserAgent, + TimeoutMS = 30000 + }; + + Proxy.Name proxyName = httpRequest.URI.Scheme.Equals("http") ? Proxy.Name.HTTP : Proxy.Name.HTTPS; + if (httpRequest.UserAgent.Equals("Other")) proxyName = Proxy.Name.Test; + // I Set User and Pass to none (I don't support Auth) string user = string.Empty, pass = string.Empty; - if (proxyName == Proxy.Name.Local || string.IsNullOrEmpty(address) || port == -1) return null; - - if (proxyName == Proxy.Name.HTTP) - { - string fullUrl = $"http://{address}:{port}{path}"; - RestRequest restRequest = new(fullUrl, httpMethod, httpHeaders, contentType); - restResponse = await restRequest.SendAsync(); - - // If Can't Handle HTTP Redirect To HTTPS - if (restResponse == null) - { - proxyName = Proxy.Name.HTTPS; - httpMethod = HttpMethod.Post; - } - } - // Create Request - ProxyRequest proxyRequest = new(proxyName, Socks.Version.Zero, Socks.Commands.Unknown, Socks.AddressType.Domain, address, port, user, pass); - proxyRequest.HttpMethod = httpMethod; - proxyRequest.RestResponse = restResponse; + ProxyRequest proxyRequest = new(proxyName, Socks.Version.Zero, Socks.Commands.Unknown, Socks.AddressType.Domain, httpRequest.URI.Host, httpRequest.URI.Port, user, pass); + proxyRequest.HttpMethod = httpRequest.Method; + proxyRequest.HttpRequest = httpRequest; return proxyRequest; } catch (Exception ex) @@ -131,7 +53,7 @@ public class ProxyRequest try { - return await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutRequestCreationMS), ct); + return await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutRequestCreationMS), ct).ConfigureAwait(false); } catch (Exception) { @@ -139,7 +61,7 @@ public class ProxyRequest } } - public static async Task RequestSocks4Remote(ProxyClient client, byte[] firstBuffer, CancellationToken ct) + public static async Task RequestSocks4_4A(ProxyClient client, byte[] firstBuffer, CancellationToken ct) { Task task = Task.Run(async () => { @@ -149,13 +71,13 @@ async Task SendSocks4Reply(ProxyClient socksClient, Socks.Version version, Socks { byte[] response = new byte[] { - (byte)version, - (byte)status, - portBuffer[0], portBuffer[1], - addressBuffer[0], addressBuffer[1], addressBuffer[2], addressBuffer[3], + (byte)version, + (byte)status, + portBuffer[0], portBuffer[1], + addressBuffer[0], addressBuffer[1], addressBuffer[2], addressBuffer[3], }; - await socksClient.SendAsync(response); + await socksClient.SendAsync(response).ConfigureAwait(false); } bool IsSocks4aProtocol(IReadOnlyList ip) @@ -235,7 +157,7 @@ bool IsSocks4aProtocol(IReadOnlyList ip) } // Send Response - await SendSocks4Reply(client, Socks.Version.Zero, Socks.Status.GrantedSocks4, portBuffer, addressBuffer); + await SendSocks4Reply(client, Socks.Version.Zero, Socks.Status.GrantedSocks4, portBuffer, addressBuffer).ConfigureAwait(false); // Create Socks Request return new ProxyRequest(proxyName, version, command, Socks.AddressType.Ipv4, address, port, userId, string.Empty); @@ -249,7 +171,7 @@ bool IsSocks4aProtocol(IReadOnlyList ip) try { - return await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutRequestCreationMS), ct); + return await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutRequestCreationMS), ct).ConfigureAwait(false); } catch (Exception) { @@ -257,7 +179,7 @@ bool IsSocks4aProtocol(IReadOnlyList ip) } } - public static async Task RequestSocks5Remote(ProxyClient client, byte[] firstBuffer, CancellationToken ct) + public static async Task RequestSocks5(ProxyClient client, byte[] firstBuffer, CancellationToken ct) { Task task = Task.Run(async () => { @@ -283,11 +205,11 @@ bool IsSocks4aProtocol(IReadOnlyList ip) // Send Response byte[] response = new[] { (byte)Socks.Version.Socks5, (byte)Socks.HandshakeMethods.NoAuth }; - await client.SendAsync(response); + await client.SendAsync(response).ConfigureAwait(false); // Connection Request - byte[] buffer = new byte[MsmhProxyServer.MaxDataSize]; - int recv = await client.ReceiveAsync(buffer); + byte[] buffer = new byte[MsmhAgnosticServer.MaxDataSize]; + int recv = await client.ReceiveAsync(buffer).ConfigureAwait(false); if (recv == -1) return null; // recv = -1 Will Result in Overflow byte[] buff = new byte[recv]; @@ -367,7 +289,37 @@ bool IsSocks4aProtocol(IReadOnlyList ip) try { - return await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutRequestCreationMS), ct); + return await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutRequestCreationMS), ct).ConfigureAwait(false); + } + catch (Exception) + { + return null; + } + } + + public static async Task RequestSniProxy(string sni, int port, CancellationToken ct) + { + Task task = Task.Run(() => + { + try + { + // Sni Proxy Doesn't Have A User And Pass + string user = string.Empty, pass = string.Empty; + + // Create Request + ProxyRequest proxyRequest = new(Proxy.Name.SniProxy, Socks.Version.Zero, Socks.Commands.Unknown, Socks.AddressType.Domain, sni, port, user, pass); + return proxyRequest; + } + catch (Exception ex) + { + Debug.WriteLine("RequestHttpRemote: " + ex.Message); + return null; + } + }); + + try + { + return await task.WaitAsync(TimeSpan.FromMilliseconds(TimeoutRequestCreationMS), ct).ConfigureAwait(false); } catch (Exception) { @@ -412,7 +364,7 @@ public ProxyRequest(Proxy.Name proxyName, Socks.Version version, Socks.Commands /// /// For HTTP Only /// - public RestResponse? RestResponse { get; set; } + public HttpRequest HttpRequest { get; set; } = new(); /// /// Close request if didn't receive data for n seconds. Default: 0 Sec (Disabled) @@ -432,57 +384,7 @@ public ProxyRequest(Proxy.Name proxyName, Socks.Version version, Socks.Commands public bool IsDestBlocked { get; set; } = false; public bool ApplyUpStreamProxy { get; set; } = false; - public ProxyProgram.Rules.RulesResult RulesResult { get; set; } = new(); - - public IPAddress? Ip - { - get - { - if (AddressType == Socks.AddressType.Domain) - { - try - { - string ipStr = GetIP.GetIpFromSystem(Address, false); - if (string.IsNullOrEmpty(ipStr)) ipStr = GetIP.GetIpFromSystem(Address, true); // Try Ipv6 - return !string.IsNullOrEmpty(ipStr) ? IPAddress.Parse(ipStr) : null; - } - catch (Exception) - { - Status = Socks.Status.HostUnreachable; - return null; - } - } - else if (AddressType == Socks.AddressType.Ipv4) - { - try - { - return IPAddress.Parse(Address); - } - catch (Exception) - { - Status = Socks.Status.CommandNotSupported; - return null; - } - } - else if (AddressType == Socks.AddressType.Ipv6) - { - try - { - string addr = Address; - if (addr.StartsWith('[') && addr.EndsWith(']')) - addr = addr[1..^1]; - return IPAddress.Parse(addr); - } - catch (Exception) - { - Status = Socks.Status.CommandNotSupported; - return null; - } - } - else - return null; - } - } + public AgnosticProgram.ProxyRules.ProxyRulesResult RulesResult { get; set; } = new(); /// /// Only for Socks5 diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyRequestsCache.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyRequestsCache.cs new file mode 100644 index 0000000..085a396 --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyRequestsCache.cs @@ -0,0 +1,74 @@ +using System.Collections.Concurrent; +using System.Diagnostics; + +namespace MsmhToolsClass.MsmhAgnosticServer; + +public class ProxyRequestsCache +{ + private readonly ConcurrentDictionary Caches = new(); + + public (ProxyRequest pReq, string eventMsg)? Get(string key, ProxyRequest req) + { + try + { + bool isCached = Caches.TryGetValue(key, out (DateTime dt, ProxyRequest pr, string eventMsg) cachedReq); + if (isCached) + { + DateTime now = DateTime.UtcNow; + TimeSpan ts = now - cachedReq.dt; + if (ts >= TimeSpan.FromMinutes(10)) + { + Caches.TryRemove(key, out _); + } + else + { + if (req.ProxyName != Proxy.Name.Test) + { + req.TimeoutSec = cachedReq.pr.TimeoutSec; + req.ApplyFragment = cachedReq.pr.ApplyFragment; + req.ApplyChangeSNI = cachedReq.pr.ApplyChangeSNI; + req.AddressSNI = cachedReq.pr.AddressSNI; + req.Address = cachedReq.pr.Address; + req.IsDestBlocked = cachedReq.pr.IsDestBlocked; + req.ApplyUpStreamProxy = cachedReq.pr.ApplyUpStreamProxy; + req.AddressType = cachedReq.pr.AddressType; + + return (req, cachedReq.eventMsg); + } + } + } + } + catch (Exception ex) + { + Debug.WriteLine("ProxyRequestsCache Get: " + ex.Message); + } + + return null; + } + + public void Add(string key, ProxyRequest req, string msgReqEvent) + { + try + { + if (req.ProxyName != Proxy.Name.Test) + Caches.TryAdd(key, (DateTime.UtcNow, req, msgReqEvent)); + } + catch (Exception ex) + { + Debug.WriteLine("ProxyRequestsCache Add: " + ex.Message); + } + } + + public void Clear() + { + try + { + Caches.Clear(); + } + catch (Exception ex) + { + Debug.WriteLine("ProxyRequestsCache Clear: " + ex.Message); + } + } + +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ProxyTunnel.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyTunnel.cs similarity index 61% rename from MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ProxyTunnel.cs rename to MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyTunnel.cs index 158f85e..6211a42 100644 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ProxyTunnel.cs +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/ProxyTunnel.cs @@ -1,11 +1,10 @@ -using System.Buffers; +using MsmhToolsClass.ProxifiedClients; using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Text; -using MsmhToolsClass.ProxyServerPrograms; -namespace MsmhToolsClass.MsmhProxyServer; +namespace MsmhToolsClass.MsmhAgnosticServer; internal class ProxyTunnel { @@ -14,25 +13,25 @@ internal class ProxyTunnel public ProxyClient RemoteClient; public ProxyRequest Req; - private readonly ProxySettings ProxySettings_; + private readonly AgnosticSettings Settings_; private TcpClient? ProxifiedTcpClient_; // Handle SSL - public SettingsSSL SettingsSSL_ { get; private set; } = new(false); + public readonly AgnosticSettingsSSL SettingsSSL_; public ProxyClientSSL? ClientSSL; public event EventHandler? OnTunnelDisconnected; public event EventHandler? OnDataReceived; public readonly Stopwatch KillOnTimeout = new(); - public bool Disconnect { get; set; } = false; + public bool ManualDisconnect { get; set; } = false; - public ProxyTunnel(int connectionId, ProxyClient sc, ProxyRequest req, ProxySettings proxySettings, SettingsSSL settingsSSL) + public ProxyTunnel(int connectionId, ProxyClient sc, ProxyRequest req, AgnosticSettings settings, AgnosticSettingsSSL settingsSSL) { ConnectionId = connectionId; Client = sc; Req = req; - ProxySettings_ = proxySettings; + Settings_ = settings; SettingsSSL_ = settingsSSL; try @@ -41,7 +40,7 @@ public ProxyTunnel(int connectionId, ProxyClient sc, ProxyRequest req, ProxySett Socket remoteSocket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // HTTP & HTTPS Remote - if (Req.ProxyName == Proxy.Name.HTTP || Req.ProxyName == Proxy.Name.HTTPS) + if (Req.ProxyName == Proxy.Name.Test || Req.ProxyName == Proxy.Name.HTTP || Req.ProxyName == Proxy.Name.HTTPS || Req.ProxyName == Proxy.Name.SniProxy) { // TCP Ipv4 if (Req.AddressType == Socks.AddressType.Domain || Req.AddressType == Socks.AddressType.Ipv4) @@ -106,10 +105,10 @@ await Task.Run(async () => while(true) { await Task.Delay(2000); - if (ProxySettings_.RequestTimeoutSec != 0 && - KillOnTimeout.ElapsedMilliseconds > TimeSpan.FromSeconds(ProxySettings_.RequestTimeoutSec).TotalMilliseconds) + if (Settings_.ProxyTimeoutSec > 0 && + KillOnTimeout.ElapsedMilliseconds > TimeSpan.FromSeconds(Settings_.ProxyTimeoutSec).TotalMilliseconds) { - string msg = $"Killed Request On Timeout({Req.TimeoutSec} Sec): {Req.Address}:{Req.Port}"; + string msg = $"Killed Request On Timeout({Req.TimeoutSec} Sec): {Req.AddressOrig}:{Req.Port}"; Debug.WriteLine(msg); OnTunnelDisconnected?.Invoke(this, EventArgs.Empty); @@ -117,8 +116,8 @@ await Task.Run(async () => break; } - // Manual Disconnect - if (Disconnect) + // Manual ManualDisconnect + if (ManualDisconnect) { OnTunnelDisconnected?.Invoke(this, EventArgs.Empty); ProxifiedTcpClient_?.Close(); @@ -128,7 +127,7 @@ await Task.Run(async () => }); } - public async void Open(ProxyProgram.Rules proxyRules, ProxyProgram.UpStreamProxy upStreamProxyProgram) + public async void Open(AgnosticProgram.ProxyRules rulesProgram) { try { @@ -138,19 +137,23 @@ public async void Open(ProxyProgram.Rules proxyRules, ProxyProgram.UpStreamProxy return; } - if (Req.ProxyName == Proxy.Name.HTTP) - await HttpHandler(); + if (!KillOnTimeout.IsRunning) KillOnTimeout.Start(); + if (Req.ProxyName == Proxy.Name.HTTP) + await HttpHandler().ConfigureAwait(false); + if (Req.ProxyName == Proxy.Name.Socks5 && Req.Status != Socks.Status.Granted) { // Send Connection Request Frame - await Client.SendAsync(Req.GetConnectionRequestFrameData()); + await Client.SendAsync(Req.GetConnectionRequestFrameData()).ConfigureAwait(false); OnTunnelDisconnected?.Invoke(this, EventArgs.Empty); return; } // Connect - if (Req.ProxyName == Proxy.Name.HTTPS || + if (Req.ProxyName == Proxy.Name.Test || + Req.ProxyName == Proxy.Name.HTTPS || + Req.ProxyName == Proxy.Name.SniProxy || (Req.ProxyName == Proxy.Name.Socks4 && Req.Command == Socks.Commands.Connect) || (Req.ProxyName == Proxy.Name.Socks4A && Req.Command == Socks.Commands.Connect) || (Req.ProxyName == Proxy.Name.Socks5 && Req.Command == Socks.Commands.Connect)) @@ -160,9 +163,14 @@ public async void Open(ProxyProgram.Rules proxyRules, ProxyProgram.UpStreamProxy if (Req.ApplyUpStreamProxy) { if (!string.IsNullOrEmpty(Req.RulesResult.ProxyScheme)) - ProxifiedTcpClient_ = await proxyRules.ConnectToUpStream(Req); + ProxifiedTcpClient_ = await rulesProgram.ConnectToUpStream(Req).ConfigureAwait(false); else - ProxifiedTcpClient_ = await upStreamProxyProgram.Connect(Req.Address, Req.Port); + { + ProxifiedTcpClient proxifiedTcpClient = new(Settings_.UpstreamProxyScheme, Settings_.UpstreamProxyUser, Settings_.UpstreamProxyPass); + var upstream = await proxifiedTcpClient.TryGetConnectedProxifiedTcpClient(Req.Address, Req.Port).ConfigureAwait(false); + if (upstream.isSuccess && upstream.proxifiedTcpClient != null) + ProxifiedTcpClient_ = upstream.proxifiedTcpClient; + } if (ProxifiedTcpClient_ != null) { @@ -174,7 +182,7 @@ public async void Open(ProxyProgram.Rules proxyRules, ProxyProgram.UpStreamProxy if (!applyUpStreamProxy) { - await RemoteClient.Socket_.ConnectAsync(Req.Address, Req.Port); + await RemoteClient.Socket_.ConnectAsync(Req.Address, Req.Port).ConfigureAwait(false); ConnectHandler(); } } @@ -184,20 +192,28 @@ public async void Open(ProxyProgram.Rules proxyRules, ProxyProgram.UpStreamProxy (Req.ProxyName == Proxy.Name.Socks4A && Req.Command == Socks.Commands.Bind) || (Req.ProxyName == Proxy.Name.Socks5 && Req.Command == Socks.Commands.Bind)) { + SocketOptionName socketOptionName = SocketOptionName.ReuseAddress | SocketOptionName.ReuseUnicastPort; + RemoteClient.Socket_.SetSocketOption(SocketOptionLevel.Socket, socketOptionName, true); + if (RemoteClient.Socket_.AddressFamily == AddressFamily.InterNetworkV6) RemoteClient.Socket_.Bind(new IPEndPoint(IPAddress.IPv6Any, 0)); else RemoteClient.Socket_.Bind(new IPEndPoint(IPAddress.Any, 0)); + ConnectHandler(); } // UDP (Only Socks5 Supports UDP) if (Req.ProxyName == Proxy.Name.Socks5 && Req.Command == Socks.Commands.UDP) { + SocketOptionName socketOptionName = SocketOptionName.ReuseAddress | SocketOptionName.ReuseUnicastPort; + RemoteClient.Socket_.SetSocketOption(SocketOptionLevel.Socket, socketOptionName, true); + if (RemoteClient.Socket_.AddressFamily == AddressFamily.InterNetworkV6) RemoteClient.Socket_.Bind(new IPEndPoint(IPAddress.IPv6Any, 0)); else RemoteClient.Socket_.Bind(new IPEndPoint(IPAddress.Any, 0)); + ConnectHandler(); } } @@ -215,12 +231,12 @@ private async void ConnectHandler() try { // Https Response - if (Req.ProxyName == Proxy.Name.HTTPS) + if (Req.ProxyName == Proxy.Name.Test || Req.ProxyName == Proxy.Name.HTTPS) { string resp = "HTTP/1.1 200 Connection Established\r\nConnection: close\r\n\r\n"; byte[] httpsResponse = Encoding.UTF8.GetBytes(resp); - await Client.SendAsync(httpsResponse); + await Client.SendAsync(httpsResponse).ConfigureAwait(false); } // Socks5 Response @@ -228,22 +244,22 @@ private async void ConnectHandler() { // Send Connection Request Frame to Server byte[] request = Req.GetConnectionRequestFrameData(); - await Client.SendAsync(request); + await Client.SendAsync(request).ConfigureAwait(false); } - // Receive Data from Both EndPoints - if (SettingsSSL_.EnableSSL && !Req.AddressIsIp) // Cert Can't Be Valid When There's An IP Without A Domain. Like SOCKS4 + // Receive Data From Both EndPoints + if (SettingsSSL_.EnableSSL && Req.ApplyChangeSNI && !Req.AddressIsIp) // Cert Can't Be Valid When There's An IP Without A Domain. Like SOCKS4 { ClientSSL = new(this); OnDataReceived?.Invoke(this, EventArgs.Empty); - await ClientSSL.Execute(); + await ClientSSL.Execute().ConfigureAwait(false); } else { OnDataReceived?.Invoke(this, EventArgs.Empty); Task ct = Client.StartReceiveAsync(); Task rt = RemoteClient.StartReceiveAsync(); - await Task.WhenAll(ct, rt); // Both Must Receive at the Same Time + await Task.WhenAll(ct, rt).ConfigureAwait(false); // Both Must Receive At The Same Time } } catch (Exception ex) @@ -253,33 +269,52 @@ private async void ConnectHandler() ProxifiedTcpClient_?.Close(); } } + private async Task HttpHandler() { - RestResponse? rr = Req.RestResponse; - if (rr != null) + // Support Upstream Proxy For HTTP Get Method + if (Req.ApplyUpStreamProxy) { - byte[]? buffer = Array.Empty(); - string statusLine = rr.ProtocolVersion + " " + rr.StatusCode + " " + rr.StatusDescription + "\r\n"; - buffer = CommonTools.AppendBytes(buffer, Encoding.UTF8.GetBytes(statusLine)); - - if (!string.IsNullOrEmpty(rr.ContentType)) + if (!string.IsNullOrEmpty(Req.RulesResult.ProxyScheme)) { - string contentTypeLine = "Content-Type: " + rr.ContentType + "\r\n"; - buffer = CommonTools.AppendBytes(buffer, Encoding.UTF8.GetBytes(contentTypeLine)); + Req.HttpRequest.ProxyScheme = Req.RulesResult.ProxyScheme; + Req.HttpRequest.ProxyUser = Req.RulesResult.ProxyUser; + Req.HttpRequest.ProxyPass = Req.RulesResult.ProxyPass; } - - if (rr.ContentLength > 0) + else { - string contentLenLine = "Content-Length: " + rr.ContentLength + "\r\n"; - buffer = CommonTools.AppendBytes(buffer, Encoding.UTF8.GetBytes(contentLenLine)); + Req.HttpRequest.ProxyScheme = Settings_.UpstreamProxyScheme; + Req.HttpRequest.ProxyUser = Settings_.UpstreamProxyUser; + Req.HttpRequest.ProxyPass = Settings_.UpstreamProxyPass; } - - if (rr.Headers != null && rr.Headers.Count > 0) + } + + HttpRequestResponse hrr = await HttpRequest.SendAsync(Req.HttpRequest).ConfigureAwait(false); + + if (hrr.IsSuccess) + { + try { - for (int i = 0; i < rr.Headers.Count; i++) + List bufferList = new(); + string statusLine = hrr.ProtocolVersion + " " + hrr.StatusCode + " " + hrr.StatusDescription + "\r\n"; + bufferList.AddRange(Encoding.UTF8.GetBytes(statusLine)); + + if (!string.IsNullOrEmpty(hrr.ContentType)) { - string? key = rr.Headers.GetKey(i); - string? val = rr.Headers.Get(i); + string contentTypeLine = "Content-Type: " + hrr.ContentType + "\r\n"; + bufferList.AddRange(Encoding.UTF8.GetBytes(contentTypeLine)); + } + + if (hrr.ContentLength > 0) + { + string contentLenLine = "Content-Length: " + hrr.ContentLength + "\r\n"; + bufferList.AddRange(Encoding.UTF8.GetBytes(contentLenLine)); + } + + for (int n = 0; n < hrr.Headers.Count; n++) + { + string? key = hrr.Headers.GetKey(n); + string? val = hrr.Headers.Get(n); if (string.IsNullOrEmpty(key)) continue; if (string.IsNullOrEmpty(val)) continue; @@ -288,21 +323,36 @@ private async Task HttpHandler() if (key.ToLower().Trim().Equals("content-length")) continue; string headerLine = key + ": " + val + "\r\n"; - buffer = CommonTools.AppendBytes(buffer, Encoding.UTF8.GetBytes(headerLine)); + bufferList.AddRange(Encoding.UTF8.GetBytes(headerLine)); } - } - - buffer = CommonTools.AppendBytes(buffer, Encoding.UTF8.GetBytes("\r\n")); - // Merge Headers and Body - buffer = buffer.Concat(rr.DataAsBytes ?? Array.Empty()).ToArray(); + bufferList.AddRange(Encoding.UTF8.GetBytes("\r\n")); + + // Merge Headers and Body + bufferList.AddRange(hrr.Data); - // Send - await Client.SendAsync(buffer); + // Send + bool isSent = await Client.SendAsync(bufferList.ToArray()).ConfigureAwait(false); - // Receive - await Client.StartReceiveAsync(); + if (isSent) + { + // Receive + Task ct = Client.StartReceiveAsync(); + Task rt = RemoteClient.StartReceiveAsync(); + await Task.WhenAll(ct, rt).ConfigureAwait(false); // Both Must Receive at the Same Time + } + } + catch (Exception ex) + { + Debug.WriteLine("ProxyServer HttpHandler: " + ex.Message); + } } } + public void Disconnect() + { + Client?.Disconnect(); + RemoteClient?.Disconnect(); + } + } \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Stats.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/Stats.cs similarity index 97% rename from MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Stats.cs rename to MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/Stats.cs index f087c88..add6ea9 100644 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Stats.cs +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/Stats.cs @@ -1,4 +1,4 @@ -namespace MsmhToolsClass.MsmhProxyServer; +namespace MsmhToolsClass.MsmhAgnosticServer; public class Stats { diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/TunnelManager.cs b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/TunnelManager.cs new file mode 100644 index 0000000..6ed50aa --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/MsmhAgnosticServer/ProxyServer/TunnelManager.cs @@ -0,0 +1,69 @@ +using System.Collections.Concurrent; +using System.Diagnostics; + +#nullable enable +namespace MsmhToolsClass.MsmhAgnosticServer; + +internal class TunnelManager +{ + private readonly ConcurrentDictionary> Tunnels = new(); + + /// + /// Construct the Tunnel Manager. + /// + public TunnelManager() { } + + internal void Add(ProxyTunnel pt) + { + try + { + Tunnels.GetOrAdd(pt.ConnectionId, id => new Lazy(pt)); + } + catch (Exception ex) + { + Debug.WriteLine("TunnelManager Add: " + ex.Message); + } + } + + internal void Remove(ProxyTunnel pt) + { + try + { + int connectionId = pt.ConnectionId; + if (Tunnels.ContainsKey(connectionId)) + { + ProxyTunnel curr = Tunnels[connectionId].Value; + if (curr != null) + { + curr.Disconnect(); + Tunnels.TryRemove(connectionId, out Lazy? _); + } + } + } + catch (Exception ex) + { + Debug.WriteLine("TunnelManager Remove: " + ex.Message); + } + } + + internal Dictionary> GetTunnels() + { + Dictionary> tempDic = new(Tunnels); + return tempDic; + } + + public int Count + { + get + { + try + { + return Tunnels.ToList().Count; + } + catch (Exception) + { + return -1; + } + } + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ApplyPrograms.cs b/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ApplyPrograms.cs deleted file mode 100644 index 579e0f7..0000000 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/ApplyPrograms.cs +++ /dev/null @@ -1,212 +0,0 @@ -using MsmhToolsClass.ProxyServerPrograms; -using System.Diagnostics; -using System.Net; - -namespace MsmhToolsClass.MsmhProxyServer; - -public partial class MsmhProxyServer -{ - public async Task ApplyPrograms(IPAddress clientIP, ProxyRequest? req) - { - if (Cancel) return null; - if (req == null) return null; - if (string.IsNullOrEmpty(req.Address)) return null; - if (req.Address.Equals("0.0.0.0")) return null; - if (req.Address.StartsWith("10.")) return null; - req.ClientIP = clientIP; - - // Count Max Requests - MaxRequestsQueue2.Enqueue(DateTime.UtcNow); - if (ProxySettings_.MaxRequests >= MaxRequestsDivide) - if (MaxRequestsQueue2.Count >= ProxySettings_.MaxRequests / MaxRequestsDivide) // Check for 50 ms (1000 / 20) - { - // Event - string blockEvent = $"Recevied {MaxRequestsQueue2.Count * MaxRequestsDivide} Requests Per Second - Request Denied ({req.AddressOrig}:{req.Port}) Due To Max Requests of {ProxySettings_.MaxRequests}."; - Debug.WriteLine(blockEvent); - OnRequestReceived?.Invoke(blockEvent, EventArgs.Empty); - return null; - } - - // Apply Programs - req.TimeoutSec = ProxySettings_.RequestTimeoutSec; - - // Block Port 80 - if (ProxySettings_.BlockPort80 && req.Port == 80) - { - // Event - string msgEvent = $"Block Port 80: {req.Address}:{req.Port}, Request Denied."; - OnRequestReceived?.Invoke(msgEvent, EventArgs.Empty); - return null; - } - - //// Proxy Rules Program - ProxyProgram.Rules.RulesResult rr = new(); - if (RulesProgram.RulesMode != ProxyProgram.Rules.Mode.Disable) - { - rr = await RulesProgram.GetAsync(req.ClientIP.ToString(), req.Address, req.Port); - } - - if (rr.IsMatch) - { - // Black List - if (rr.IsBlackList) - { - // Event - string msgEvent = $"Black List: {req.Address}:{req.Port}, Request Denied."; - OnRequestReceived?.Invoke(msgEvent, EventArgs.Empty); - return null; - } - - // Block Port - if (rr.IsPortBlock) - { - // Event - string msgEvent = $"Block Port {req.Port}: {req.Address}:{req.Port}, Request Denied."; - OnRequestReceived?.Invoke(msgEvent, EventArgs.Empty); - return null; - } - - // Apply DPI Bypass - req.ApplyFragment = rr.ApplyDpiBypass; - req.ApplyChangeSNI = rr.ApplyDpiBypass; - - // Fake DNS Or DNS - bool isDnsIp = NetworkTool.IsIp(rr.Dns, out _); - if (isDnsIp) req.Address = rr.Dns; - } - - //// FakeSNI Program - if (req.ApplyChangeSNI && SettingsSSL_.EnableSSL && SettingsSSL_.ChangeSni) - { - if (rr.IsMatch) - if (!string.IsNullOrEmpty(rr.Sni) && !rr.Sni.Equals(req.AddressOrig)) - req.AddressSNI = rr.Sni; - - if (req.AddressSNI.Equals(req.AddressOrig)) - { - string defaultSni = SettingsSSL_.DefaultSni; - if (!string.IsNullOrEmpty(defaultSni) && !string.IsNullOrWhiteSpace(defaultSni)) - { - req.AddressSNI = defaultSni; - } - } - } - - // Check If Address Is An IP - bool isIp = NetworkTool.IsIp(req.Address, out _); - - //// DNS Program - if (req.AddressOrig.Equals(req.Address)) - { - if (DNSProgram.DNSMode != ProxyProgram.Dns.Mode.Disable && !isIp) - { - string ipStr = await DNSProgram.Get(req.Address); - if (!string.IsNullOrEmpty(ipStr) && !NetworkTool.IsLocalIP(ipStr)) - req.Address = ipStr; - } - } - - // Event - string msgReqEvent = $"[{req.ClientIP}] [{req.ProxyName}] "; - - if (req.ProxyName == Proxy.Name.HTTP || req.ProxyName == Proxy.Name.HTTPS) - msgReqEvent += $"[{req.HttpMethod}] "; - - if (req.ProxyName == Proxy.Name.Socks4 || req.ProxyName == Proxy.Name.Socks4A || req.ProxyName == Proxy.Name.Socks5) - msgReqEvent += $"[{req.Command}] "; - - if (req.AddressOrig.Equals(req.Address)) - msgReqEvent += $"{req.AddressOrig}:{req.Port}"; - else - { - if (!string.IsNullOrEmpty(rr.DnsCustomDomain) && !req.AddressOrig.Equals(rr.DnsCustomDomain)) - msgReqEvent += $"{req.AddressOrig}:{req.Port} => {rr.DnsCustomDomain} => {req.Address}:{req.Port}"; - else - msgReqEvent += $"{req.AddressOrig}:{req.Port} => {req.Address}:{req.Port}"; - } - - if (!req.AddressOrig.Equals(req.AddressSNI) && req.ApplyChangeSNI && SettingsSSL_.EnableSSL && SettingsSSL_.ChangeSni) - msgReqEvent += $" => {req.AddressSNI}:{req.Port}"; - - // Check if Dest Host or IP is blocked - isIp = NetworkTool.IsIp(req.Address, out IPAddress? ip); - bool isIpv6 = false; - if (isIp && ip != null) - { - isIpv6 = NetworkTool.IsIPv6(ip); - if (req.ProxyName == Proxy.Name.Socks5 && req.Command == Socks.Commands.UDP) - req.IsDestBlocked = !await NetworkTool.CanUdpConnect(req.Address, req.Port, 5000); - else - { - bool canPing = await NetworkTool.CanPing(req.Address, 5000); - bool canTcpConnect = await NetworkTool.CanTcpConnect(req.Address, req.Port, 5000); - req.IsDestBlocked = !canPing || !canTcpConnect; - } - } - else - req.IsDestBlocked = await NetworkTool.IsHostBlocked(req.Address, req.Port, 5000); - - if (req.IsDestBlocked && isIp) - { - bool isIpProtocolReachable = NetworkTool.IsIpProtocolReachable(req.Address); // Returns True for Non Windows - if (isIpProtocolReachable) - msgReqEvent += " (IP is blocked)"; - else - { - string ipP = isIpv6 ? "Ipv6" : "Ipv4"; - msgReqEvent += $" (your network does not support {ipP})"; - } - } - if (req.IsDestBlocked && !isIp) - msgReqEvent += " (Host is blocked)"; - - // Apply Upstream? - if ((rr.IsMatch && rr.ApplyUpStreamProxy && !rr.ApplyUpStreamProxyToBlockedIPs) || - (rr.IsMatch && rr.ApplyUpStreamProxy && rr.ApplyUpStreamProxyToBlockedIPs && req.IsDestBlocked)) - { - req.ApplyUpStreamProxy = true; - msgReqEvent += $" (Bypassing through Upstream Proxy: {rr.ProxyScheme})"; - } - - if (!req.ApplyUpStreamProxy) - { - bool isUpStreamProgramActive = UpStreamProxyProgram.UpStreamMode != ProxyProgram.UpStreamProxy.Mode.Disable; - if ((isUpStreamProgramActive && !UpStreamProxyProgram.OnlyApplyToBlockedIps) || - (isUpStreamProgramActive && UpStreamProxyProgram.OnlyApplyToBlockedIps && req.IsDestBlocked)) - { - req.ApplyUpStreamProxy = true; - msgReqEvent += $" (Bypassing through Upstream Proxy: {UpStreamProxyProgram.UpStreamMode.ToString().ToLower()}://{UpStreamProxyProgram.ProxyHost}:{UpStreamProxyProgram.ProxyPort})"; - } - } - - // Block Blocked Hosts Without DNS IP - bool blockReq = req.IsDestBlocked && !req.AddressIsIp && req.AddressOrig.Equals(req.Address) && !req.ApplyUpStreamProxy; - if (blockReq) - msgReqEvent += " Request Denied (It's Blocked and has no DNS IP)"; - - Debug.WriteLine(msgReqEvent); - OnRequestReceived?.Invoke(msgReqEvent, EventArgs.Empty); - - if (blockReq) return null; - - // Change AddressType Based On DNS - if (req.ProxyName == Proxy.Name.HTTP || - req.ProxyName == Proxy.Name.HTTPS || - (req.ProxyName == Proxy.Name.Socks4A && req.AddressType == Socks.AddressType.Domain) || - (req.ProxyName == Proxy.Name.Socks5 && req.AddressType == Socks.AddressType.Domain)) - { - if (isIp && ip != null) - { - if (isIpv6) req.AddressType = Socks.AddressType.Ipv6; - else - req.AddressType = Socks.AddressType.Ipv4; - } - } - - // UDP Does Not Support Fragmentation - if (req.ProxyName == Proxy.Name.Socks5 && req.Command == Socks.Commands.UDP) - req.ApplyFragment = false; - - return req; - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/CommonTools.cs b/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/CommonTools.cs deleted file mode 100644 index cb9e0f8..0000000 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/CommonTools.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections.Specialized; - -namespace MsmhToolsClass.MsmhProxyServer; - -public static class CommonTools -{ - public static class GetHttpMethod - { - public static HttpMethod Parse(string method) - { - method = method.Trim().ToLower(); - var httpMethod = method switch - { - "get" => HttpMethod.Get, - "head" => HttpMethod.Head, - "put" => HttpMethod.Put, - "post" => HttpMethod.Post, - "connect" => HttpMethod.Post, - "delete" => HttpMethod.Delete, - "patch" => HttpMethod.Patch, - "options" => HttpMethod.Options, - "trace" => HttpMethod.Trace, - _ => HttpMethod.Get, - }; - return httpMethod; - } - } - - internal static byte[] AppendBytes(byte[] orig, byte[] append) - { - if (append == null) return orig; - if (orig == null) return append; - - byte[] ret = new byte[orig.Length + append.Length]; - Buffer.BlockCopy(orig, 0, ret, 0, orig.Length); - Buffer.BlockCopy(append, 0, ret, orig.Length, append.Length); - return ret; - } - - /// - /// Add a key-value pair to a supplied Dictionary. - /// - /// The key. - /// The value. - /// An existing dictionary. - /// The existing dictionary with a new key and value, or, a new dictionary with the new key value pair. - internal static NameValueCollection AddToDict(string key, string? value, NameValueCollection existing) - { - if (string.IsNullOrEmpty(key)) return existing; - if (string.IsNullOrEmpty(value)) value = string.Empty; - - NameValueCollection ret = new(); - - if (existing == null) - { - ret.Add(key, value); - return ret; - } - else - { - string? theKey = existing[key]; - if (!string.IsNullOrEmpty(theKey)) // Key Exist - { - if (string.IsNullOrEmpty(value)) return existing; - string tempVal = theKey; - tempVal += "," + value; - existing.Remove(key); - existing.Add(key, tempVal); - return existing; - } - else - { - existing.Add(key, value); - return existing; - } - } - } - - /// - /// Get root domain from a given hostname - /// - /// - /// - public static string GetWildCardDomainName(string hostname) - { - // Only for subdomains we need wild card - // Example www.google.com or gstatic.google.com - // But NOT for google.com or IP address - - if (NetworkTool.IsIp(hostname, out _)) return hostname; - - NetworkTool.GetHostDetails(hostname, 443, out _, out _, out string baseHost, out _, out _, out _); - - if (baseHost.Equals(hostname)) return hostname; - - return $"*.{baseHost}"; - } - -} diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/GetFirstPacket.cs b/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/GetFirstPacket.cs deleted file mode 100644 index fe1e135..0000000 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/GetFirstPacket.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Diagnostics; - -namespace MsmhToolsClass.MsmhProxyServer; - -public class GetFirstPacket -{ - public byte[] Packet { get; set; } = new byte[MsmhProxyServer.MaxDataSize]; - public Proxy.Name? ProxyName; - - public GetFirstPacket() { } - - public async Task Get(ProxyClient socksClient) - { - int recv = await socksClient.ReceiveAsync(Packet); - if (recv == -1) return; // recv = -1 Will Result in Overflow - - byte[] buffer = new byte[recv]; - Buffer.BlockCopy(Packet, 0, buffer, 0, recv); - //Debug.WriteLine(BitConverter.ToString(buffer).Replace("-", " ")); - - ProxyName = buffer[0] switch - { - (byte)Socks.Version.Socks5 => Proxy.Name.Socks5, - (byte)Socks.Version.Socks4 => Proxy.Name.Socks4, - 0x43 => Proxy.Name.HTTP, - 0x47 => Proxy.Name.HTTP, - _ => Proxy.Name.HTTP - }; - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Http/HttpRequest.cs b/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Http/HttpRequest.cs deleted file mode 100644 index 0060995..0000000 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Http/HttpRequest.cs +++ /dev/null @@ -1,1157 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Globalization; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Text.Json.Serialization; - -namespace MsmhToolsClass.MsmhProxyServer; - -/// -/// Serialization helper. -/// -public interface ISerializationHelper -{ - /// - /// Deserialize from JSON to an object of the specified type. - /// - /// Type. - /// JSON string. - /// Instance. - T DeserializeJson(string json); - - /// - /// Serialize from object to JSON. - /// - /// Object. - /// Pretty print. - /// JSON. - string SerializeJson(object obj, bool pretty = true); -} - -/// -/// A chunk of data, used when reading from a request where the Transfer-Encoding header includes 'chunked'. -/// -public class Chunk -{ - /// - /// Length of the data. - /// - public int Length = 0; - - /// - /// Data. - /// - public byte[]? Data = null; - - /// - /// Any additional metadata that appears on the length line after the length hex value and semicolon. - /// - public string? Metadata = null; - - /// - /// Indicates whether or not this is the final chunk, i.e. the chunk length received was zero. - /// - public bool IsFinalChunk = false; - - internal Chunk() - { - - } -} - -/// -/// HTTP request. -/// -public class HttpRequest -{ - /// - /// UTC timestamp from when the request was received. - /// - [JsonPropertyOrder(-10)] - public DateTime TimestampUtc { get; private set; } = DateTime.Now.ToUniversalTime(); - - /// - /// Thread ID on which the request exists. - /// - [JsonPropertyOrder(-9)] - public int ThreadId { get; private set; } = Environment.CurrentManagedThreadId; - - /// - /// The protocol and version. - /// - [JsonPropertyOrder(-9)] - public string? ProtocolVersion { get; set; } = null; - - /// - /// Source (requestor) IP and port information. - /// - [JsonPropertyOrder(-8)] - public SourceDetails Source { get; set; } = new SourceDetails(); - - /// - /// Destination IP and port information. - /// - [JsonPropertyOrder(-7)] - public DestinationDetails Destination { get; set; } = new DestinationDetails(); - - /// - /// The HTTP method used in the request. - /// - [JsonPropertyOrder(-6)] - public HttpMethod Method { get; set; } = HttpMethod.Get; - - /// - /// The string version of the HTTP method, useful if Method is UNKNOWN. - /// - [JsonPropertyOrder(-5)] - public string? MethodRaw { get; set; } = null; - - /// - /// URL details. - /// - [JsonPropertyOrder(-4)] - public UrlDetails Url { get; set; } = new UrlDetails(); - - /// - /// Query details. - /// - [JsonPropertyOrder(-3)] - public QueryDetails Query { get; set; } = new QueryDetails(); - - /// - /// The headers found in the request. - /// - [JsonPropertyOrder(-2)] - public NameValueCollection Headers - { - get - { - return _Headers; - } - set - { - if (value == null) _Headers = new NameValueCollection(StringComparer.InvariantCultureIgnoreCase); - else _Headers = value; - } - } - - /// - /// Specifies whether or not the client requested HTTP keepalives. - /// - public bool Keepalive { get; set; } = false; - - /// - /// Indicates whether or not chunked transfer encoding was detected. - /// - public bool ChunkedTransfer { get; set; } = false; - - /// - /// Indicates whether or not the payload has been gzip compressed. - /// - public bool Gzip { get; set; } = false; - - /// - /// Indicates whether or not the payload has been deflate compressed. - /// - public bool Deflate { get; set; } = false; - - /// - /// The useragent specified in the request. - /// - public string? Useragent { get; set; } = null; - - /// - /// The content type as specified by the requestor (client). - /// - [JsonPropertyOrder(990)] - public string? ContentType { get; set; } = null; - - /// - /// The number of bytes in the request body. - /// - [JsonPropertyOrder(991)] - public long ContentLength { get; private set; } = 0; - - /// - /// The stream from which to read the request body sent by the requestor (client). - /// - [JsonIgnore] - public Stream? Data; - - /// - /// Retrieve the request body as a byte array. This will fully read the stream. - /// - [JsonIgnore] - public byte[]? DataAsBytes - { - get - { - if (_DataAsBytes != null) return _DataAsBytes; - if (Data != null && ContentLength > 0) - { - _DataAsBytes = ReadStreamFully(Data); - return _DataAsBytes; - } - return null; - } - } - - /// - /// Retrieve the request body as a string. This will fully read the stream. - /// - [JsonIgnore] - public string? DataAsString - { - get - { - if (_DataAsBytes != null) return Encoding.UTF8.GetString(_DataAsBytes); - if (Data != null && ContentLength > 0) - { - _DataAsBytes = ReadStreamFully(Data); - if (_DataAsBytes != null) return Encoding.UTF8.GetString(_DataAsBytes); - } - return null; - } - } - - /// - /// The original HttpListenerContext from which the HttpRequest was constructed. - /// - [JsonIgnore] - public HttpListenerContext? ListenerContext; - - /// - /// Close request if didn't receive data for n seconds. Default: 0 Sec (Disabled) - /// - public int TimeoutSec { get; set; } = 0; - - /// - /// Apply DPI Bypass to this Request if DPI Bypass Program is available. - /// - public bool ApplyDpiBypass { get; set; } = false; - - public bool IsDestBlocked { get; set; } = false; - - public bool ApplyUpStreamProxy { get; set; } = false; - - private readonly int _StreamBufferSize = 65536; - private readonly Uri? _Uri = null; - private byte[]? _DataAsBytes = null; - private readonly ISerializationHelper? _Serializer = null; - private NameValueCollection _Headers = new(StringComparer.InvariantCultureIgnoreCase); - private static readonly int _TimeoutDataReadMs = 2000; - private static readonly int _DataReadSleepMs = 10; - - /// - /// HTTP request. - /// - public HttpRequest() - { - - } - - public HttpRequest(TcpClient client) - { - try - { - IPEndPoint? clientIpEndpoint = client.Client.RemoteEndPoint as IPEndPoint; - IPEndPoint? serverIpEndpoint = client.Client.LocalEndPoint as IPEndPoint; - - string clientEndpoint = clientIpEndpoint != null ? clientIpEndpoint.ToString() : string.Empty; - string serverEndpoint = serverIpEndpoint != null ? serverIpEndpoint.ToString() : string.Empty; - - string clientIp = clientIpEndpoint != null ? clientIpEndpoint.Address.ToString() : string.Empty; - int clientPort = clientIpEndpoint != null ? clientIpEndpoint.Port : 0; - - string serverIp = serverIpEndpoint != null ? serverIpEndpoint.Address.ToString() : string.Empty; - int serverPort = serverIpEndpoint != null ? serverIpEndpoint.Port : 0; - - byte[]? headerBytes = null; - byte[] lastFourBytes = new byte[4]; - lastFourBytes[0] = 0x00; - lastFourBytes[1] = 0x00; - lastFourBytes[2] = 0x00; - lastFourBytes[3] = 0x00; - - // Attach-Stream - NetworkStream stream = client.GetStream(); - - if (!stream.CanRead) return; - - // Read-Headers - using (MemoryStream headerMs = new()) - { - // Read-Header-Bytes - byte[] headerBuffer = new byte[1]; - int read = 0; - int headerBytesRead = 0; - - while ((read = stream.Read(headerBuffer, 0, headerBuffer.Length)) > 0) - { - if (read > 0) - { - // Initialize-Header-Bytes-if-Needed - headerBytesRead += read; - headerBytes ??= new byte[1]; - - // Update-Last-Four - if (read == 1) - { - lastFourBytes[0] = lastFourBytes[1]; - lastFourBytes[1] = lastFourBytes[2]; - lastFourBytes[2] = lastFourBytes[3]; - lastFourBytes[3] = headerBuffer[0]; - } - - // Append-to-Header-Buffer - byte[] tempHeader = new byte[headerBytes.Length + 1]; - Buffer.BlockCopy(headerBytes, 0, tempHeader, 0, headerBytes.Length); - tempHeader[headerBytes.Length] = headerBuffer[0]; - headerBytes = tempHeader; - - // Check-for-End-of-Headers - if ((int)(lastFourBytes[0]) == 13 - && (int)(lastFourBytes[1]) == 10 - && (int)(lastFourBytes[2]) == 13 - && (int)(lastFourBytes[3]) == 10) - { - break; - } - } - } - } - - // Process-Headers - if (headerBytes == null || headerBytes.Length < 1) - { - // No header data read from the stream - return; - } - //Debug.WriteLine("== " + BitConverter.ToString(headerBytes).Replace("-", " ")); - HttpRequest ret = BuildHeaders(headerBytes); - ContentLength = ret.ContentLength; - ContentType = ret.ContentType; - Headers = ret.Headers; - Keepalive = ret.Keepalive; - Method = ret.Method; - ProtocolVersion = ret.ProtocolVersion; - ThreadId = ret.ThreadId; - TimestampUtc = ret.TimestampUtc; - Useragent = ret.Useragent; - - // Read-Data - byte[]? data = null; - if (ContentLength > 0) - { - // Read-from-Stream - data = new byte[ContentLength]; - - using (MemoryStream dataMs = new()) - { - long bytesRemaining = ContentLength; - long bytesRead = 0; - bool timeout = false; - int currentTimeout = 0; - - int read = 0; - byte[] buffer; - long bufferSize = 2048; - if (bufferSize > bytesRemaining) bufferSize = bytesRemaining; - buffer = new byte[bufferSize]; - - while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) - { - if (read > 0) - { - dataMs.Write(buffer, 0, read); - bytesRead += read; - bytesRemaining -= read; - - // reduce buffer size if number of bytes remaining is - // less than the pre-defined buffer size of 2KB - if (bytesRemaining < bufferSize) - { - bufferSize = bytesRemaining; - } - - buffer = new byte[bufferSize]; - - // check if read fully - if (bytesRemaining == 0) break; - if (bytesRead == ContentLength) break; - } - else - { - if (currentTimeout >= _TimeoutDataReadMs) - { - timeout = true; - break; - } - else - { - currentTimeout += _DataReadSleepMs; - Task.Delay(_DataReadSleepMs).Wait(); - } - } - } - - if (timeout) - { - throw new IOException("Timeout reading data from stream."); - } - - Data = dataMs; - } - - // Validate-Data - if (Data == null || Data.Length < 1) - { - throw new IOException("Unable to read data from stream."); - } - - if (Data.Length != ContentLength) - { - throw new IOException("Data read does not match specified content length."); - } - } - - // serverIp is 127.0.0.1 unless we use DNS resolver - Destination = new(serverIp, ret.Destination.Port, ret.Destination.Hostname); - Source = new(clientIp, clientPort); - Url = new(ret.Url.Full, ret.Url.RawWithQuery); - } - catch (Exception) - { - // do nothing - } - } - - private static HttpRequest BuildHeaders(byte[] bytes) - { - // Initial-Values - HttpRequest ret = new(); - ret.TimestampUtc = DateTime.Now.ToUniversalTime(); - ret.ThreadId = Environment.CurrentManagedThreadId; - ret.Headers = new(); - - // Convert-to-String-List - string str = Encoding.UTF8.GetString(bytes); - string[] headers = str.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries); - - // Process-Each-Line - for (int i = 0; i < headers.Length; i++) - { - if (i == 0) - { - // First-Line - string[] requestLine = headers[i].Trim().Trim('\0').Split(' '); - if (requestLine.Length < 3) throw new ArgumentException("Request line does not contain at least three parts (method, raw URL, protocol/version)."); - - ret.Method = CommonTools.GetHttpMethod.Parse(requestLine[0]); - ret.Url.Full = requestLine[1]; - ret.ProtocolVersion = requestLine[2]; - ret.Url.RawWithQuery = ret.Url.Full; - - try - { - string ipPort = ret.Url.Full; - if (!ipPort.ToLower().StartsWith("http://") && !ipPort.ToLower().StartsWith("https://")) - { - Uri tempUri = new($"https://{ret.Url.Full}"); - if (tempUri.Port == 80) - ret.Url.Full = $"http://{ret.Url.Full}"; - else - ret.Url.Full = $"https://{ret.Url.Full}"; - } - Uri uri = new(ret.Url.Full); - ret.Destination.Hostname = uri.Host; - ret.Destination.Port = uri.Port; - } - catch (Exception) - { - // do nothing - } - - if (string.IsNullOrEmpty(ret.Destination.Hostname)) - { - if (!ret.Url.Full.Contains("://") & ret.Url.Full.Contains(':')) - { - string[] hostAndPort = ret.Url.Full.Split(':'); - if (hostAndPort.Length == 2) - { - ret.Destination.Hostname = hostAndPort[0]; - bool isInt = int.TryParse(hostAndPort[1], out int port); - if (isInt) - ret.Destination.Port = port; - else - { - Debug.WriteLine("Unable to parse destination hostname and port."); - } - } - } - } - } - else - { - // Subsequent-Line - string[] headerLine = headers[i].Split(':'); - if (headerLine.Length == 2) - { - string key = headerLine[0].Trim(); - string val = headerLine[1].Trim(); - if (string.IsNullOrEmpty(key)) continue; - string keyEval = key.ToLower(); - - if (keyEval.Equals("keep-alive")) - ret.Keepalive = Convert.ToBoolean(val); - else if (keyEval.Equals("user-agent")) - ret.Useragent = val; - else if (keyEval.Equals("content-length")) - ret.ContentLength = Convert.ToInt64(val); - else if (keyEval.Equals("content-type")) - ret.ContentType = val; - else - ret.Headers = CommonTools.AddToDict(key, val, ret.Headers); - } - - } - } - - return ret; - } - - //======================================================================================== - - /// - /// HTTP request. - /// Instantiate the object using an HttpListenerContext. - /// - /// HttpListenerContext. - /// Serialization helper. - public HttpRequest(HttpListenerContext ctx, ISerializationHelper serializer) - { - if (ctx == null) return; - if (ctx.Request == null) return; - if (serializer == null) return; - - if (ctx.Request.Url == null) return; - if (ctx.Request.RawUrl == null) return; - - _Serializer = serializer; - - ListenerContext = ctx; - Keepalive = ctx.Request.KeepAlive; - ContentLength = ctx.Request.ContentLength64; - Useragent = ctx.Request.UserAgent; - ContentType = ctx.Request.ContentType; - - _Uri = new Uri(ctx.Request.Url.ToString().Trim()); - - ThreadId = Environment.CurrentManagedThreadId; - TimestampUtc = DateTime.Now.ToUniversalTime(); - ProtocolVersion = "HTTP/" + ctx.Request.ProtocolVersion.ToString(); - Source = new SourceDetails(ctx.Request.RemoteEndPoint.Address.ToString(), ctx.Request.RemoteEndPoint.Port); - Destination = new DestinationDetails(ctx.Request.LocalEndPoint.Address.ToString(), ctx.Request.LocalEndPoint.Port, _Uri.Host); - Url = new UrlDetails(ctx.Request.Url.ToString().Trim(), ctx.Request.RawUrl.ToString().Trim()); - if (string.IsNullOrEmpty(Url.Full)) return; - Query = new QueryDetails(Url.Full); - MethodRaw = ctx.Request.HttpMethod; - - try - { - Method = (HttpMethod)Enum.Parse(typeof(HttpMethod), ctx.Request.HttpMethod, true); - } - catch (Exception) - { - Method = HttpMethod.Get; // Default - } - - Headers = ctx.Request.Headers; - - for (int i = 0; i < Headers.Count; i++) - { - string? key = Headers.GetKey(i); - string[]? vals = Headers.GetValues(i); - - if (string.IsNullOrEmpty(key)) continue; - if (vals == null || vals.Length < 1) continue; - - if (key.ToLower().Equals("transfer-encoding")) - { - if (vals.Contains("chunked", StringComparer.InvariantCultureIgnoreCase)) - ChunkedTransfer = true; - if (vals.Contains("gzip", StringComparer.InvariantCultureIgnoreCase)) - Gzip = true; - if (vals.Contains("deflate", StringComparer.InvariantCultureIgnoreCase)) - Deflate = true; - } - else if (key.ToLower().Equals("x-amz-content-sha256")) - { - if (vals.Contains("streaming", StringComparer.InvariantCultureIgnoreCase)) - { - ChunkedTransfer = true; - } - } - } - - Data = ctx.Request.InputStream; - } - - /// - /// For chunked transfer-encoded requests, read the next chunk. - /// It is strongly recommended that you use the ChunkedTransfer parameter before invoking this method. - /// - /// Cancellation token useful for canceling the request. - /// Chunk. - public async Task ReadChunk(CancellationToken token = default) - { - Chunk chunk = new(); - - // Get-Length-and-Metadata - byte[] buffer = new byte[1]; - byte[]? lenBytes = null; - int bytesRead; - - while (true) - { - if (Data == null) break; - bytesRead = await Data.ReadAsync(buffer, token).ConfigureAwait(false); - if (bytesRead > 0) - { - lenBytes = AppendBytes(lenBytes, buffer); - if (lenBytes == null) break; - string lenStr = Encoding.UTF8.GetString(lenBytes); - - if (lenBytes[^1] == 10) - { - lenStr = lenStr.Trim(); - - if (lenStr.Contains(';')) - { - string[] lenParts = lenStr.Split(new char[] { ';' }, 2); - chunk.Length = int.Parse(lenParts[0], NumberStyles.HexNumber); - if (lenParts.Length >= 2) chunk.Metadata = lenParts[1]; - } - else - { - chunk.Length = int.Parse(lenStr, NumberStyles.HexNumber); - } - - break; - } - } - } - - // Get-Data - int bytesRemaining = chunk.Length; - - if (chunk.Length > 0) - { - chunk.IsFinalChunk = false; - using MemoryStream ms = new(); - while (true) - { - if (bytesRemaining > _StreamBufferSize) buffer = new byte[_StreamBufferSize]; - else buffer = new byte[bytesRemaining]; - - if (Data == null) break; - bytesRead = await Data.ReadAsync(buffer, token).ConfigureAwait(false); - - if (bytesRead > 0) - { - await ms.WriteAsync(buffer.AsMemory(0, bytesRead), token); - bytesRemaining -= bytesRead; - } - - if (bytesRemaining == 0) break; - } - - ms.Seek(0, SeekOrigin.Begin); - chunk.Data = ms.ToArray(); - } - else - { - chunk.IsFinalChunk = true; - } - - // Get-Trailing-CRLF - buffer = new byte[1]; - - while (true) - { - if (Data == null) break; - bytesRead = await Data.ReadAsync(buffer, token).ConfigureAwait(false); - if (bytesRead > 0) - { - if (buffer[0] == 10) break; - } - } - - return chunk; - } - - /// - /// Read the data stream fully and convert the data to the object type specified using JSON deserialization. - /// Note: if you use this method, you will not be able to read from the data stream afterward. - /// - /// Type. - /// Object of type specified. - public T? DataAsJsonObject() where T : class - { - if (_Serializer == null) return null; - string? json = DataAsString; - if (string.IsNullOrEmpty(json)) return null; - return _Serializer.DeserializeJson(json); - } - - /// - /// Determine if a header exists. - /// - /// Header key. - /// True if exists. - public bool HeaderExists(string key) - { - try - { - return Headers.AllKeys.Any(k => k.ToLower().Equals(key.ToLower())); - } - catch (Exception) - { - return false; - } - } - - /// - /// Determine if a querystring entry exists. - /// - /// Querystring key. - /// True if exists. - public bool QuerystringExists(string key) - { - try - { - return Query.Elements.AllKeys.Any(k => k.ToLower().Equals(key.ToLower())); - } - catch (Exception) - { - return false; - } - } - - /// - /// Retrieve a header (or querystring) value. - /// - /// Key. - /// Value. - public string? RetrieveHeaderValue(string key) - { - string? headerValue = Headers?.Get(key); - return !string.IsNullOrEmpty(headerValue) ? headerValue : null; - } - - /// - /// Retrieve a querystring value. - /// - /// Key. - /// Value. - public string? RetrieveQueryValue(string key) - { - if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); - - if (Query != null && Query.Elements != null) - { - string? val = Query.Elements.Get(key); - val = !string.IsNullOrEmpty(val) ? WebUtility.UrlDecode(val) : null; - return val; - } - - return null; - } - - // Private-Methods - private byte[]? AppendBytes(byte[]? orig, byte[]? append) - { - byte[]? ret; - if (orig != null && append == null) - { - ret = new byte[orig.Length]; - Buffer.BlockCopy(orig, 0, ret, 0, orig.Length); - return ret; - } - - if (orig == null && append != null) - { - ret = new byte[append.Length]; - Buffer.BlockCopy(append, 0, ret, 0, append.Length); - return ret; - } - - if (orig != null && append != null) - { - ret = new byte[orig.Length + append.Length]; - Buffer.BlockCopy(orig, 0, ret, 0, orig.Length); - Buffer.BlockCopy(append, 0, ret, orig.Length, append.Length); - return ret; - } - - return null; - } - - private byte[]? StreamToBytes(Stream input) - { - if (input == null) return null; - if (!input.CanRead) return null; - - byte[] buffer = new byte[16 * 1024]; - using MemoryStream ms = new(); - int read; - - while ((read = input.Read(buffer, 0, buffer.Length)) > 0) - { - ms.Write(buffer, 0, read); - } - - return ms.ToArray(); - } - - private void ReadStreamFully() - { - if (Data == null) return; - if (!Data.CanRead) return; - - if (_DataAsBytes == null) - { - if (!ChunkedTransfer) - { - _DataAsBytes = StreamToBytes(Data); - } - else - { - while (true) - { - Chunk chunk = ReadChunk().Result; - if (chunk.Data != null && chunk.Data.Length > 0) _DataAsBytes = AppendBytes(_DataAsBytes, chunk.Data); - if (chunk.IsFinalChunk) break; - } - } - } - } - - private byte[]? ReadStreamFully(Stream input) - { - if (input == null) return null; - if (!input.CanRead) return null; - - byte[] buffer = new byte[16 * 1024]; - using MemoryStream ms = new(); - int read; - - while ((read = input.Read(buffer, 0, buffer.Length)) > 0) - { - ms.Write(buffer, 0, read); - } - - byte[] ret = ms.ToArray(); - return ret; - } - - // Embedded-Classes - - /// - /// Source details. - /// - public class SourceDetails - { - /// - /// IP address of the requestor. - /// - public string? IpAddress { get; set; } = null; - - /// - /// TCP port from which the request originated on the requestor. - /// - public int Port { get; set; } = 0; - - /// - /// Source details. - /// - public SourceDetails() - { - - } - - /// - /// Source details. - /// - /// IP address of the requestor. - /// TCP port from which the request originated on the requestor. - public SourceDetails(string ip, int port) - { - if (string.IsNullOrEmpty(ip)) return; - if (port < 0) return; - - IpAddress = ip; - Port = port; - } - } - - /// - /// Destination details. - /// - public class DestinationDetails - { - /// - /// IP address to which the request was made. - /// - public string? IpAddress { get; set; } = null; - - /// - /// TCP port on which the request was received. - /// - public int Port { get; set; } = 0; - - /// - /// Hostname to which the request was directed. - /// - public string? Hostname { get; set; } = null; - - /// - /// Hostname elements. - /// - public string[] HostnameElements - { - get - { - string? hostname = Hostname; - string[] ret; - - if (!string.IsNullOrEmpty(hostname)) - { - if (!IPAddress.TryParse(hostname, out _)) - { - ret = hostname.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries); - return ret; - } - else - { - ret = new string[1]; - ret[0] = hostname; - return ret; - } - } - - ret = Array.Empty(); - return ret; - } - } - - /// - /// Destination details. - /// - public DestinationDetails() - { - - } - - /// - /// Source details. - /// - /// IP address to which the request was made. - /// TCP port on which the request was received. - /// Hostname. - public DestinationDetails(string ip, int port, string? hostname) - { - if (port < 0) return; - - IpAddress = ip; - Port = port; - Hostname = hostname; - } - } - - /// - /// URL details. - /// - public class UrlDetails - { - /// - /// Full URL. - /// - public string? Full { get; set; } = null; - - /// - /// Raw URL with query. - /// - public string? RawWithQuery { get; set; } = null; - - /// - /// Raw URL without query. - /// - public string? RawWithoutQuery - { - get - { - if (!string.IsNullOrEmpty(RawWithQuery)) - { - if (RawWithQuery.Contains('?')) return RawWithQuery[..RawWithQuery.IndexOf("?")]; - else return RawWithQuery; - } - else - { - return null; - } - } - } - - /// - /// Raw URL elements. - /// - public string[] Elements - { - get - { - string? rawUrl = RawWithoutQuery; - - if (!string.IsNullOrEmpty(rawUrl)) - { - while (rawUrl.Contains("//")) rawUrl = rawUrl.Replace("//", "/"); - while (rawUrl.StartsWith("/")) rawUrl = rawUrl[1..]; - while (rawUrl.EndsWith("/")) rawUrl = rawUrl[..^1]; - string[] encoded = rawUrl.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - if (encoded != null && encoded.Length > 0) - { - string[] decoded = new string[encoded.Length]; - for (int i = 0; i < encoded.Length; i++) - { - decoded[i] = WebUtility.UrlDecode(encoded[i]); - } - - return decoded; - } - } - - string[] ret = Array.Empty(); - return ret; - } - } - - /// - /// Parameters found within the URL, if using parameter routes. - /// - public NameValueCollection Parameters - { - get - { - return _Parameters; - } - set - { - if (value == null) _Parameters = new NameValueCollection(StringComparer.InvariantCultureIgnoreCase); - else _Parameters = value; - } - } - - /// - /// URL details. - /// - public UrlDetails() - { - - } - - /// - /// URL details. - /// - /// Full URL. - /// Raw URL. - public UrlDetails(string? fullUrl, string? rawUrl) - { - Full = fullUrl; - RawWithQuery = rawUrl; - } - - private NameValueCollection _Parameters = new(StringComparer.InvariantCultureIgnoreCase); - } - - /// - /// Query details. - /// - public class QueryDetails - { - /// - /// Querystring, excluding the leading '?'. - /// - public string? Querystring - { - get - { - if (!string.IsNullOrEmpty(_FullUrl) && _FullUrl.Contains('?')) - { - return _FullUrl.Substring(_FullUrl.IndexOf("?") + 1, (_FullUrl.Length - _FullUrl.IndexOf("?") - 1)); - } - else - { - return null; - } - } - } - - /// - /// Query elements. - /// - public NameValueCollection Elements - { - get - { - NameValueCollection ret = new(StringComparer.InvariantCultureIgnoreCase); - string? qs = Querystring; - if (!string.IsNullOrEmpty(qs)) - { - string[] queries = qs.Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries); - if (queries.Length > 0) - { - for (int i = 0; i < queries.Length; i++) - { - string[] queryParts = queries[i].Split('='); - if (queryParts != null && queryParts.Length == 2) - { - ret.Add(queryParts[0], queryParts[1]); - } - else if (queryParts != null && queryParts.Length == 1) - { - ret.Add(queryParts[0], null); - } - } - } - } - - return ret; - } - } - - /// - /// Query details. - /// - public QueryDetails() - { - - } - - /// - /// Query details. - /// - /// Full URL. - public QueryDetails(string fullUrl) - { - if (string.IsNullOrEmpty(fullUrl)) return; - - _FullUrl = fullUrl; - } - - private readonly string? _FullUrl = null; - } - -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Http/RestRequest.cs b/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Http/RestRequest.cs deleted file mode 100644 index 949d37a..0000000 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Http/RestRequest.cs +++ /dev/null @@ -1,737 +0,0 @@ -using System; -using System.Net; -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Text.Json.Serialization; -using System.Diagnostics; -using System.Security.Authentication; -using System.Net.Http.Headers; -using System.Collections.Specialized; -// https://github.com/jchristn/RestWrapper/blob/master/src/RestWrapper/RestRequest.cs - -#nullable enable -namespace MsmhToolsClass.MsmhProxyServer; - -/// -/// Authorization header options. -/// -public class AuthorizationHeader -{ - /// - /// The username to use in the authorization header, if any. - /// - public string? User = null; - - /// - /// The password to use in the authorization header, if any. - /// - public string? Password = null; - - /// - /// The bearer token to use in the authorization header, if any. - /// - public string? BearerToken = null; - - /// - /// Enable to encode credentials in the authorization header. - /// - public bool EncodeCredentials = true; - - /// - /// Instantiate the object. - /// - public AuthorizationHeader() - { - - } -} - -/// -/// RESTful HTTP request to be sent to a server. -/// -public class RestRequest -{ - /// - /// Method to invoke when sending log messages. - /// - [JsonIgnore] - public Action? Logger { get; set; } = null; - - /// - /// The URL to which the request should be directed. - /// - public string? Url { get; set; } = null; - - /// - /// The HTTP method to use, also known as a verb (GET, PUT, POST, DELETE, etc). - /// - public HttpMethod Method = HttpMethod.Get; - - private AuthorizationHeader _Authorization = new(); - - /// - /// Authorization header parameters. - /// - public AuthorizationHeader Authorization - { - get - { - return _Authorization; - } - set - { - if (value == null) _Authorization = new AuthorizationHeader(); - else _Authorization = value; - } - } - - /// - /// Ignore certificate errors such as expired certificates, self-signed certificates, or those that cannot be validated. - /// - public bool IgnoreCertificateErrors { get; set; } = false; - - /// - /// The filename of the file containing the certificate. - /// - public string? CertificateFilename { get; set; } = null; - - /// - /// The password to the certificate file. - /// - public string? CertificatePassword { get; set; } = null; - - /// - /// The query elements attached to the URL. - /// - public NameValueCollection Query - { - get - { - NameValueCollection ret = new(StringComparer.InvariantCultureIgnoreCase); - - if (!string.IsNullOrEmpty(Url)) - { - if (Url.Contains('?')) - { - string query = Url[(Url.IndexOf("?") + 1)..]; - - if (!string.IsNullOrEmpty(query)) - { - string[] elements = query.Split('&'); - - if (elements != null && elements.Length > 0) - { - for (int i = 0; i < elements.Length; i++) - { - string[] elementParts = elements[i].Split(new char[] { '=' }, 2, StringSplitOptions.None); - - if (elementParts.Length == 1) - { - ret.Add(elementParts[0], null); - } - else - { - ret.Add(elementParts[0], elementParts[1]); - } - } - } - } - } - } - - return ret; - } - } - - /// - /// The HTTP headers to attach to the request. - /// - public NameValueCollection? Headers - { - get - { - return _Headers; - } - set - { - if (value == null) _Headers = new NameValueCollection(StringComparer.InvariantCultureIgnoreCase); - else _Headers = value; - } - } - - /// - /// The content type of the payload (i.e. Data or DataStream). - /// - public string? ContentType { get; set; } = null; - - /// - /// The content length of the payload (i.e. Data or DataStream). - /// - public long ContentLength { get; private set; } = 0; - - /// - /// The size of the buffer to use while reading from the DataStream and the response stream from the server. - /// - public int BufferSize - { - get - { - return _StreamReadBufferSize; - } - set - { - if (value < 1) throw new ArgumentException("StreamReadBufferSize must be at least one byte in size."); - _StreamReadBufferSize = value; - } - } - - /// - /// The number of milliseconds to wait before assuming the request has timed out. - /// - public int TimeoutMilliseconds - { - get - { - return _TimeoutMilliseconds; - } - set - { - if (value < 1) throw new ArgumentException("Timeout must be greater than 1ms."); - _TimeoutMilliseconds = value; - } - } - - /// - /// The user agent header to set on outbound requests. - /// - public string UserAgent { get; set; } = string.Empty; - - /// - /// Enable or disable support for automatically handling redirects. - /// - public bool AllowAutoRedirect { get; set; } = true; - - private readonly string _Header = string.Empty; - private int _StreamReadBufferSize = 65536; - private int _TimeoutMilliseconds = 30000; - private NameValueCollection _Headers = new(StringComparer.InvariantCultureIgnoreCase); - - /// - /// A simple RESTful HTTP client. - /// - /// URL to access on the server. - public RestRequest(string url) - { - if (string.IsNullOrEmpty(url)) throw new ArgumentNullException(nameof(url)); - - Url = url; - } - - /// - /// A simple RESTful HTTP client. - /// - /// URL to access on the server. - /// HTTP method to use. - public RestRequest(string url, HttpMethod method) - { - if (string.IsNullOrEmpty(url)) throw new ArgumentNullException(nameof(url)); - - Url = url; - Method = method; - } - - /// - /// A simple RESTful HTTP client. - /// - /// URL to access on the server. - /// HTTP method to use. - /// Content type to use. - public RestRequest(string url, HttpMethod method, string contentType) - { - if (string.IsNullOrEmpty(url)) throw new ArgumentNullException(nameof(url)); - - Url = url; - Method = method; - ContentType = contentType; - } - - /// - /// A simple RESTful HTTP client. - /// - /// URL to access on the server. - /// HTTP method to use. - /// HTTP headers to use. - /// Content type to use. - public RestRequest(string url, - HttpMethod method, - NameValueCollection? headers, - string? contentType) - { - //if (string.IsNullOrEmpty(url)) throw new ArgumentNullException(nameof(url)); - - Url = url; - Method = method; - Headers = headers; - ContentType = contentType; - } - - /// - /// Send the HTTP request with no data. - /// - /// RestResponse. - public RestResponse? Send() - { - return SendInternal(0, null); - } - - /// - /// Send the HTTP request using form-encoded data. - /// This method will automatically set the content-type header to 'application/x-www-form-urlencoded' if it is not already set. - /// - /// Dictionary. - /// - public RestResponse? Send(Dictionary form) - { - // refer to https://github.com/dotnet/runtime/issues/22811 - form ??= new Dictionary(); - var items = form.Select(i => WebUtility.UrlEncode(i.Key) + "=" + WebUtility.UrlEncode(i.Value)); - var content = new StringContent(string.Join("&", items), null, "application/x-www-form-urlencoded"); - byte[] bytes = Encoding.UTF8.GetBytes(content.ReadAsStringAsync().Result); - ContentLength = bytes.Length; - if (string.IsNullOrEmpty(ContentType)) ContentType = "application/x-www-form-urlencoded"; - return Send(bytes); - } - - /// - /// Send the HTTP request with the supplied data. - /// - /// A string containing the data you wish to send to the server (does not work with GET requests). - /// RestResponse. - public RestResponse? Send(string data) - { - if (string.IsNullOrEmpty(data)) return Send(); - return Send(Encoding.UTF8.GetBytes(data)); - } - - /// - /// Send the HTTP request with the supplied data. - /// - /// A byte array containing the data you wish to send to the server (does not work with GET requests). - /// RestResponse. - public RestResponse? Send(byte[] data) - { - long contentLength = 0; - MemoryStream stream = new(Array.Empty()); - - if (data != null && data.Length > 0) - { - contentLength = data.Length; - stream = new MemoryStream(data); - stream.Seek(0, SeekOrigin.Begin); - } - - return SendInternal(contentLength, stream); - } - - /// - /// Send the HTTP request with the supplied data. - /// - /// The number of bytes to read from the input stream. - /// Stream containing the data you wish to send to the server (does not work with GET requests). - /// RestResponse. - public RestResponse? Send(long contentLength, Stream? stream) - { - return SendInternal(contentLength, stream); - } - - /// - /// Send the HTTP request with no data. - /// - /// Cancellation token. - /// RestResponse. - public async Task SendAsync(CancellationToken token = default) - { - return await SendInternalAsync(0, null, token); - } - - /// - /// Send the HTTP request using form-encoded data. - /// This method will automatically set the content-type header. - /// - /// Dictionary. - /// Cancellation token. - /// RestResponse. - public async Task SendAsync(Dictionary form, CancellationToken token = default) - { - // refer to https://github.com/dotnet/runtime/issues/22811 - form ??= new Dictionary(); - var items = form.Select(i => WebUtility.UrlEncode(i.Key) + "=" + WebUtility.UrlEncode(i.Value)); - var content = new StringContent(string.Join("&", items), null, "application/x-www-form-urlencoded"); - byte[] bytes = Encoding.UTF8.GetBytes(content.ReadAsStringAsync(token).Result); - ContentLength = bytes.Length; - if (string.IsNullOrEmpty(ContentType)) ContentType = "application/x-www-form-urlencoded"; - return await SendAsync(bytes, token); - } - - /// - /// Send the HTTP request with the supplied data. - /// - /// A string containing the data you wish to send to the server (does not work with GET requests). - /// Cancellation token. - /// RestResponse. - public async Task SendAsync(string data, CancellationToken token = default) - { - if (string.IsNullOrEmpty(data)) return await SendAsync(token); - return await SendAsync(Encoding.UTF8.GetBytes(data), token); - } - - /// - /// Send the HTTP request with the supplied data. - /// - /// A byte array containing the data you wish to send to the server (does not work with GET requests). - /// Cancellation token. - /// RestResponse. - public async Task SendAsync(byte[] data, CancellationToken token = default) - { - long contentLength = 0; - MemoryStream stream = new(Array.Empty()); - - if (data != null && data.Length > 0) - { - contentLength = data.Length; - stream = new MemoryStream(data); - stream.Seek(0, SeekOrigin.Begin); - } - - return await SendInternalAsync(contentLength, stream, token); - } - - /// - /// Send the HTTP request with the supplied data. - /// - /// The number of bytes to read from the input stream. - /// A stream containing the data you wish to send to the server (does not work with GET requests). - /// Cancellation token. - /// RestResponse. - public async Task SendAsync(long contentLength, Stream? stream, CancellationToken token = default) - { - return await SendInternalAsync(contentLength, stream, token); - } - - private bool Validator(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) - { - return true; - } - - private RestResponse? SendInternal(long contentLength, Stream? stream) - { - return SendInternalAsync(contentLength, stream, CancellationToken.None).Result; - } - - private async Task SendInternalAsync(long contentLength, Stream? stream, CancellationToken token) - { - if (string.IsNullOrEmpty(Url)) return null; - - //Timestamps ts = new Timestamps(); - - Logger?.Invoke(_Header + Method.ToString() + " " + Url); - - try - { - // Setup-Webrequest - Logger?.Invoke(_Header + "setting up web request"); - - if (IgnoreCertificateErrors) ServicePointManager.ServerCertificateValidationCallback = Validator; - - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; - - HttpClientHandler handler = new(); - handler.AllowAutoRedirect = AllowAutoRedirect; - - if (!string.IsNullOrEmpty(CertificateFilename)) - { - X509Certificate2? cert = null; - - if (!string.IsNullOrEmpty(CertificatePassword)) - { - Logger?.Invoke(_Header + "adding certificate including password"); - cert = new X509Certificate2(CertificateFilename, CertificatePassword); - } - else - { - Logger?.Invoke(_Header + "adding certificate without password"); - cert = new X509Certificate2(CertificateFilename); - } - - handler.ClientCertificateOptions = ClientCertificateOption.Manual; - handler.SslProtocols = SslProtocols.Tls12; - handler.ClientCertificates.Add(cert); - } - - HttpClient client = new(handler); - client.Timeout = TimeSpan.FromMilliseconds(_TimeoutMilliseconds); - client.DefaultRequestHeaders.ExpectContinue = false; - client.DefaultRequestHeaders.ConnectionClose = true; - - HttpRequestMessage? message = null; - - if (Method == HttpMethod.Delete) - { - message = new HttpRequestMessage(HttpMethod.Delete, Url); - } - else if (Method == HttpMethod.Get) - { - message = new HttpRequestMessage(HttpMethod.Get, Url); - } - else if (Method == HttpMethod.Head) - { - message = new HttpRequestMessage(HttpMethod.Head, Url); - } - else if (Method == HttpMethod.Options) - { - message = new HttpRequestMessage(HttpMethod.Options, Url); - } -#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER - else if (Method == HttpMethod.Patch) - { - message = new HttpRequestMessage(HttpMethod.Patch, Url); - } -#endif - else if (Method == HttpMethod.Post) - { - message = new HttpRequestMessage(HttpMethod.Post, Url); - } - else if (Method == HttpMethod.Put) - { - message = new HttpRequestMessage(HttpMethod.Put, Url); - } - else if (Method == HttpMethod.Trace) - { - message = new HttpRequestMessage(HttpMethod.Trace, Url); - } - else - { - throw new ArgumentException("HTTP method '" + Method.ToString() + "' is not supported."); - } - - // Write-Request-Body-Data - HttpContent? content = null; - - if (Method != HttpMethod.Get && Method != HttpMethod.Head) - { - if (contentLength > 0 && stream != null) - { - Logger?.Invoke(_Header + "adding " + contentLength + " bytes to request"); - content = new StreamContent(stream, _StreamReadBufferSize); - //content.Headers.ContentLength = ContentLength; - if (!string.IsNullOrEmpty(ContentType)) - content.Headers.ContentType = new MediaTypeHeaderValue(ContentType); - } - } - - message.Content = content; - - if (Headers != null && Headers.Count > 0) - { - for (int i = 0; i < Headers.Count; i++) - { - string? key = Headers.GetKey(i); - string? val = Headers.Get(i); - - if (string.IsNullOrEmpty(key)) continue; - if (string.IsNullOrEmpty(val)) continue; - - Logger?.Invoke(_Header + "adding header " + key + ": " + val); - - if (key.ToLower().Trim().Equals("close")) - { - // do nothing - } - else if (key.ToLower().Trim().Equals("connection")) - { - // do nothing - } - else if (key.ToLower().Trim().Equals("content-length")) - { - // do nothing - } - else if (key.ToLower().Trim().Equals("content-type")) - { - if (message.Content != null) - message.Content.Headers.ContentType = new MediaTypeHeaderValue(val); - } - else - { - client.DefaultRequestHeaders.Add(key, val); - } - } - } - - // Add-Auth-Info - if (!string.IsNullOrEmpty(_Authorization.User)) - { - if (_Authorization.EncodeCredentials) - { - Logger?.Invoke(_Header + "adding encoded credentials for user " + _Authorization.User); - - string authInfo = _Authorization.User + ":" + _Authorization.Password; - authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo)); - client.DefaultRequestHeaders.Add("Authorization", "Basic " + authInfo); - } - else - { - Logger?.Invoke(_Header + "adding plaintext credentials for user " + _Authorization.User); - client.DefaultRequestHeaders.Add("Authorization", "Basic " + _Authorization.User + ":" + _Authorization.Password); - } - } - else if (!string.IsNullOrEmpty(_Authorization.BearerToken)) - { - Logger?.Invoke(_Header + "adding authorization bearer token " + _Authorization.BearerToken); - client.DefaultRequestHeaders.Add("Authorization", "Bearer " + _Authorization.BearerToken); - } - - // Submit-Request-and-Build-Response - HttpResponseMessage? response = null; - try - { - response = await client.SendAsync(message, token).ConfigureAwait(false); - } - catch (Exception) - { - client.Dispose(); - return null; - } - - if (response != null) - { - Logger?.Invoke(_Header + response.StatusCode + " response received after " + DateTime.Now); - - RestResponse ret = new(); - ret.ProtocolVersion = "HTTP/" + response.Version.ToString(); - ret.StatusCode = (int)response.StatusCode; - ret.StatusDescription = response.StatusCode.ToString(); - - if (response.Content != null && response.Content.Headers != null) - { - ret.ContentEncoding = string.Join(",", response.Content.Headers.ContentEncoding); - if (response.Content.Headers.ContentType != null) - ret.ContentType = response.Content.Headers.ContentType.ToString(); - - if (response.Content.Headers.ContentLength != null) - ret.ContentLength = response.Content.Headers.ContentLength.Value; - } - - Logger?.Invoke(_Header + "processing response headers after " + DateTime.Now); - - foreach (var header in response.Headers) - { - string key = header.Key; - string val = string.Join(",", header.Value); - ret.Headers?.Add(key, val); - } - - if (ret.ContentLength > 0) - { - if (response.Content != null) - ret.Data = await response.Content.ReadAsStreamAsync(token); - } - - client.Dispose(); - return ret; - } - - client.Dispose(); - return null; - } - catch (TaskCanceledException) - { - Logger?.Invoke(_Header + "task canceled"); - return null; - } - catch (OperationCanceledException) - { - Logger?.Invoke(_Header + "operation canceled"); - return null; - } - catch (WebException we) - { - // WebException - Logger?.Invoke(_Header + "web exception: " + we.Message); - - RestResponse ret = new(); - ret.Headers = new(); - ret.ContentEncoding = null; - ret.ContentType = null; - ret.ContentLength = 0; - ret.StatusCode = 0; - ret.StatusDescription = null; - ret.Data = null; - - if (we.Response is HttpWebResponse exceptionResponse) - { - ret.ProtocolVersion = "HTTP/" + exceptionResponse.ProtocolVersion.ToString(); - ret.ContentEncoding = exceptionResponse.ContentEncoding; - ret.ContentType = exceptionResponse.ContentType; - ret.ContentLength = exceptionResponse.ContentLength; - ret.StatusCode = (int)exceptionResponse.StatusCode; - ret.StatusDescription = exceptionResponse.StatusDescription; - - Logger?.Invoke(_Header + "server returned status code " + ret.StatusCode); - - if (exceptionResponse.Headers != null && exceptionResponse.Headers.Count > 0) - { - for (int i = 0; i < exceptionResponse.Headers.Count; i++) - { - string key = exceptionResponse.Headers.GetKey(i); - string val = ""; - int valCount = 0; - - string[]? getValues = exceptionResponse.Headers.GetValues(i); - if (getValues != null) - { - foreach (string value in getValues) - { - if (valCount == 0) - { - val += value; - valCount++; - } - else - { - val += "," + value; - valCount++; - } - } - } - - Logger?.Invoke(_Header + "adding exception header " + key + ": " + val); - ret.Headers?.Add(key, val); - } - } - - if (exceptionResponse.ContentLength > 0) - { - Logger?.Invoke(_Header + "attaching exception response stream to response with content length " + exceptionResponse.ContentLength + " bytes"); - ret.ContentLength = exceptionResponse.ContentLength; - ret.Data = exceptionResponse.GetResponseStream(); - } - else - { - ret.ContentLength = 0; - ret.Data = null; - } - } - - return ret; - } - catch (Exception) - { - return null; - } - finally - { - Logger?.Invoke(_Header + "complete (" + DateTime.Now + ")"); - } - } -} diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Http/RestResponse.cs b/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Http/RestResponse.cs deleted file mode 100644 index 9b9ab1a..0000000 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Http/RestResponse.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System.Collections.Specialized; -using System.Text; - -#nullable enable -namespace MsmhToolsClass.MsmhProxyServer; - -/// -/// RESTful response from the server. -/// -public class RestResponse -{ - /// - /// The protocol and version. - /// - public string? ProtocolVersion { get; internal set; } = null; - - private NameValueCollection Headers_ = new(StringComparer.InvariantCultureIgnoreCase); - - /// - /// User-supplied headers. - /// - public NameValueCollection Headers - { - get - { - return Headers_; - } - set - { - if (value == null) Headers_ = new NameValueCollection(StringComparer.InvariantCultureIgnoreCase); - else Headers_ = value; - } - } - - /// - /// The content encoding returned from the server. - /// - public string? ContentEncoding { get; internal set; } = null; - - /// - /// The content type returned from the server. - /// - public string? ContentType { get; internal set; } = null; - - /// - /// The number of bytes contained in the response body byte array. - /// - public long ContentLength { get; internal set; } = 0; - - /// - /// The response URI of the responder. - /// - public string? ResponseURI { get; internal set; } = null; - - /// - /// The HTTP status code returned with the response. - /// - public int StatusCode { get; internal set; } = 0; - - /// - /// The HTTP status description associated with the HTTP status code. - /// - public string? StatusDescription { get; internal set; } = null; - - /// - /// The stream containing the response data returned from the server. - /// - public Stream? Data { get; internal set; } = null; - - /// - /// Read the data stream fully into a byte array. - /// If you use this property, the 'Data' property will be fully read. - /// - public byte[]? DataAsBytes - { - get - { - if (_Data == null && ContentLength > 0 && Data != null && Data.CanRead) - { - _Data = StreamToBytes(Data); - } - - return _Data; - } - } - - /// - /// Read the data stream fully into a string. - /// If you use this property, the 'Data' property will be fully read. - /// - public string? DataAsString - { - get - { - if (_Data == null && ContentLength > 0 && Data != null && Data.CanRead) - { - _Data = StreamToBytes(Data); - } - - if (_Data != null) - { - return Encoding.UTF8.GetString(_Data); - } - - return null; - } - } - - private byte[]? _Data = null; - - /// - /// An organized object containing frequently used response parameters from a RESTful HTTP request. - /// - public RestResponse() - { - - } - - private byte[] StreamToBytes(Stream input) - { - byte[] buffer = new byte[16 * 1024]; - using MemoryStream ms = new(); - int read; - - while ((read = input.Read(buffer, 0, buffer.Length)) > 0) - { - ms.Write(buffer, 0, read); - } - - byte[] ret = ms.ToArray(); - return ret; - } - - /// - /// Creates a human-readable string of the object. - /// - /// String. - public override string ToString() - { - string ret = ""; - ret += "REST Response" + Environment.NewLine; - - if (Headers != null && Headers.Count > 0) - { - ret += " Headers" + Environment.NewLine; - for (int i = 0; i < Headers.Count; i++) - { - string? key = Headers.GetKey(i); - string? val = Headers.Get(i); - - if (string.IsNullOrEmpty(key)) continue; - if (string.IsNullOrEmpty(val)) continue; - - ret += " | " + key + ": " + val + Environment.NewLine; - } - } - - if (!string.IsNullOrEmpty(ContentEncoding)) - ret += " Content Encoding : " + ContentEncoding + Environment.NewLine; - if (!string.IsNullOrEmpty(ContentType)) - ret += " Content Type : " + ContentType + Environment.NewLine; - if (!string.IsNullOrEmpty(ResponseURI)) - ret += " Response URI : " + ResponseURI + Environment.NewLine; - - ret += " Status Code : " + StatusCode + Environment.NewLine; - ret += " Status Description : " + StatusDescription + Environment.NewLine; - ret += " Content Length : " + ContentLength + Environment.NewLine; - - ret += " Data : "; - if (Data != null && ContentLength > 0) ret += "[stream]"; - else ret += "[none]"; - ret += Environment.NewLine; - - return ret; - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/MsmhProxyServer.cs b/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/MsmhProxyServer.cs deleted file mode 100644 index e129a5b..0000000 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/MsmhProxyServer.cs +++ /dev/null @@ -1,548 +0,0 @@ -using MsmhToolsClass.ProxyServerPrograms; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Net; -using System.Net.Sockets; -// CopyRight GPLv3 MSasanMH 2023 - -namespace MsmhToolsClass.MsmhProxyServer; - -public partial class MsmhProxyServer -{ - //======================================= DNS Support - public ProxyProgram.Dns DNSProgram = new(); - public void EnableDNS(ProxyProgram.Dns dnsProgram) - { - DNSProgram = dnsProgram; - } - - //======================================= Fragment Support: Static - public static ProxyProgram.Fragment StaticFragmentProgram { get; set; } = new(); - public void EnableStaticFragment(ProxyProgram.Fragment fragmentProgram) - { - StaticFragmentProgram = fragmentProgram; - } - - //--- Constant - public ProxyProgram.Fragment FragmentProgram = new(); - public void EnableFragment(ProxyProgram.Fragment fragmentProgram) - { - FragmentProgram = fragmentProgram; - } - - //======================================= UpStream Proxy Support - public ProxyProgram.UpStreamProxy UpStreamProxyProgram = new(); - public void EnableUpStreamProxy(ProxyProgram.UpStreamProxy upStreamProxyProgram) - { - UpStreamProxyProgram = upStreamProxyProgram; - } - - //======================================= Rules Support - public ProxyProgram.Rules RulesProgram = new(); - public void EnableRules(ProxyProgram.Rules rules) - { - RulesProgram = rules; - } - - //======================================= Start Proxy - internal ProxySettings ProxySettings_ = new(); - public SettingsSSL SettingsSSL_ { get; internal set; } = new(false); - - internal TunnelManager TunnelManager_ = new(); - private TcpListener? TcpListener_; - - private CancellationTokenSource? CancelTokenSource_; - private CancellationToken CancelToken_; - - private System.Timers.Timer KillOnOverloadTimer { get; set; } = new(5000); - private float CpuUsage { get; set; } = 0; - - private bool Cancel { get; set; } = false; - - private Thread? MainThread; - - public event EventHandler? OnRequestReceived; - public event EventHandler? OnErrorOccurred; - public event EventHandler? OnDebugInfoReceived; - public static readonly int MaxDataSize = 65536; - public Stats Stats { get; private set; } = new(); - public bool IsRunning { get; private set; } = false; - - private CancellationTokenSource CTS = new(); - private readonly ConcurrentQueue MaxRequestsQueue = new(); - private readonly ConcurrentQueue MaxRequestsQueue2 = new(); - internal static readonly int MaxRequestsDivide = 20; // 1000 / 20 = 50 ms - - public MsmhProxyServer() - { - // Set Defult DNS to System DNS - ProxyProgram.Dns defaultDns = new(); - defaultDns.Set(ProxyProgram.Dns.Mode.System, null, null, 2); - DNSProgram = defaultDns; - } - - public async Task EnableSSL(bool enableSSL, string? rootCA_Path, string? rootCA_KeyPath) - { - SettingsSSL_.EnableSSL = enableSSL; - SettingsSSL_.RootCA_Path = rootCA_Path; - SettingsSSL_.RootCA_KeyPath = rootCA_KeyPath; - await SettingsSSL_.Build(); - } - - public async Task EnableSSL(SettingsSSL settingsSSL) - { - SettingsSSL_ = settingsSSL; - await SettingsSSL_.Build(); - } - - public void Start(IPAddress ipAddress, int port, int maxRequestsPerSec, int requestTimeoutSec, float killOnCpuUsage, bool blockPort80) - { - ProxySettings_ = new(); - ProxySettings_.ListenerIpAddress = ipAddress; - ProxySettings_.ListenerPort = port; - ProxySettings_.MaxRequests = maxRequestsPerSec; - ProxySettings_.RequestTimeoutSec = requestTimeoutSec; - ProxySettings_.KillOnCpuUsage = killOnCpuUsage; - ProxySettings_.BlockPort80 = blockPort80; - - Start(ProxySettings_); - } - - public void Start(ProxySettings proxySettings) - { - if (IsRunning) return; - IsRunning = true; - - ProxySettings_ = proxySettings; - - Stats = new Stats(); - - Welcome(); - - TunnelManager_ = new(); - - CancelTokenSource_ = new(); - CancelToken_ = CancelTokenSource_.Token; - - Cancel = false; - - MaxRequestsTimer(); - - KillOnOverloadTimer.Elapsed += KillOnOverloadTimer_Elapsed; - KillOnOverloadTimer.Start(); - - ThreadStart threadStart = new(AcceptConnections); - MainThread = new(threadStart); - if (OperatingSystem.IsWindows()) - MainThread.SetApartmentState(ApartmentState.STA); - MainThread.Start(); - } - - private void Welcome() - { - // Event - string msgEvent = $"Proxy Server starting on {ProxySettings_.ListenerIpAddress}:{ProxySettings_.ListenerPort}"; - OnRequestReceived?.Invoke(msgEvent, EventArgs.Empty); - OnDebugInfoReceived?.Invoke(msgEvent, EventArgs.Empty); - } - - private async void MaxRequestsTimer() - { - await Task.Run(async () => - { - while (true) - { - if (Cancel) break; - await Task.Delay(5); - - bool peek = MaxRequestsQueue.TryPeek(out DateTime dt); - if (peek) - { - double diff = (DateTime.UtcNow - dt).TotalMilliseconds; - if (diff > 1000 / MaxRequestsDivide) // Dequeue If Older Than 50 ms (1000 / 20) - MaxRequestsQueue.TryDequeue(out _); - } - - peek = MaxRequestsQueue2.TryPeek(out dt); - if (peek) - { - double diff = (DateTime.UtcNow - dt).TotalMilliseconds; - if (diff > 1000 / MaxRequestsDivide) // Dequeue If Older Than 50 ms (1000 / 20) - MaxRequestsQueue2.TryDequeue(out _); - } - } - }); - } - - private async void KillOnOverloadTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) - { - if (OperatingSystem.IsWindows() && typeof(PerformanceCounter) != null) - CpuUsage = await ProcessManager.GetCpuUsage(Environment.ProcessId, 1000); - - if (CpuUsage >= ProxySettings_.KillOnCpuUsage) - { - KillAll(); - } - - if (CpuUsage >= 95f) - { - try { Environment.Exit(0); } catch (Exception) { } - ProcessManager.KillProcessByPID(Environment.ProcessId); - } - } - - /// - /// Kill all active requests - /// - public void KillAll() - { - CTS.Cancel(); - if (TunnelManager_ != null) - { - var dic = TunnelManager_.GetTunnels(); - Debug.WriteLine(dic.Count); - foreach (var item in dic) - { - Debug.WriteLine(item.Key); - TunnelManager_.Remove(item.Key); - } - } - } - - public void Stop() - { - if (IsRunning && TcpListener_ != null && CancelTokenSource_ != null) - { - IsRunning = false; - CancelTokenSource_.Cancel(true); - Cancel = true; - TcpListener_.Stop(); - - KillAll(); - - MaxRequestsQueue.Clear(); - MaxRequestsQueue2.Clear(); - - KillOnOverloadTimer.Stop(); - IsRunning = TcpListener_.Server.IsBound; - Goodbye(); - } - } - - private void Goodbye() - { - // Event - string msgEvent = "Proxy Server stopped."; - OnRequestReceived?.Invoke(msgEvent, EventArgs.Empty); - OnDebugInfoReceived?.Invoke(msgEvent, EventArgs.Empty); - } - - public int ListeningPort - { - get => ProxySettings_.ListenerPort; - } - - public bool IsFragmentActive - { - get => FragmentProgram.FragmentMode != ProxyProgram.Fragment.Mode.Disable || StaticFragmentProgram.FragmentMode != ProxyProgram.Fragment.Mode.Disable; - } - - public int ActiveTunnels - { - get => TunnelManager_.Count; - } - - public int MaxRequests - { - get => ProxySettings_ != null ? ProxySettings_.MaxRequests : 0; - } - - private async void AcceptConnections() - { - if (Cancel) return; - - try - { - TcpListener_ = new(ProxySettings_.ListenerIpAddress, ProxySettings_.ListenerPort); - TcpListener_.Start(); - Task.Delay(200).Wait(); - - if (TcpListener_ != null) - { - IsRunning = TcpListener_.Server.IsBound; - - while (!Cancel) - { - TcpClient tcpClient = await TcpListener_.AcceptTcpClientAsync().ConfigureAwait(false); - if (ProxySettings_.KillOnCpuUsage != 0 && CpuUsage < ProxySettings_.KillOnCpuUsage) - if (tcpClient.Connected) ProcessConnectionSync(tcpClient); - if (CancelToken_.IsCancellationRequested || Cancel) break; - } - } - else - { - IsRunning = false; - } - } - catch (Exception ex) - { - // Event Error - if (!CancelToken_.IsCancellationRequested || !Cancel) - { - string msgEventErr = $"Accept Connections: {ex.Message}"; - OnErrorOccurred?.Invoke(msgEventErr, EventArgs.Empty); - } - } - } - - private void ProcessConnectionSync(TcpClient tcpClient) - { - Task.Run(() => ClientConnected(tcpClient)); - } - - private async void ClientConnected(TcpClient tcpClient) - { - if (tcpClient.Client.LocalEndPoint is not IPEndPoint lep || tcpClient.Client.RemoteEndPoint is not IPEndPoint rep) - { - try { tcpClient.Dispose(); } catch (Exception) { } - return; - } - - // Count Max Requests - MaxRequestsQueue.Enqueue(DateTime.UtcNow); - if (ProxySettings_.MaxRequests >= MaxRequestsDivide) - { - if (MaxRequestsQueue.Count >= ProxySettings_.MaxRequests / MaxRequestsDivide) // Check for 50 ms (1000 / 20) - { - // Event - string blockEvent = $"Recevied {MaxRequestsQueue.Count * MaxRequestsDivide} Requests Per Second - Request Denied Due To Max Requests of {ProxySettings_.MaxRequests}."; - Debug.WriteLine(blockEvent); - OnRequestReceived?.Invoke(blockEvent, EventArgs.Empty); - try { tcpClient.Dispose(); } catch (Exception) { } - return; - } - - if (MaxRequestsQueue2.Count >= ProxySettings_.MaxRequests / MaxRequestsDivide) // Check for 50 ms (1000 / 20) - { - // Event - string blockEvent = $"Recevied {MaxRequestsQueue2.Count * MaxRequestsDivide} Requests Per Second - Request Denied Due To Max Requests of {ProxySettings_.MaxRequests}."; - Debug.WriteLine(blockEvent); - OnRequestReceived?.Invoke(blockEvent, EventArgs.Empty); - try { tcpClient.Dispose(); } catch (Exception) { } - return; - } - } - - // Generate unique int - int connectionId; - try - { - connectionId = Guid.NewGuid().GetHashCode() + BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0); - } - catch (Exception) - { - connectionId = Guid.NewGuid().GetHashCode(); - } - - tcpClient.NoDelay = true; - - // Create Client - ProxyClient proxyClient = new(tcpClient.Client); - - // Get First Packet - GetFirstPacket getFirstPacket = new(); - await getFirstPacket.Get(proxyClient); - - if (getFirstPacket.ProxyName == null) - { - proxyClient.Disconnect(tcpClient); - return; - } - - // Create Request - CTS = new(); - ProxyRequest? req = null; - if (getFirstPacket.ProxyName == Proxy.Name.HTTP || getFirstPacket.ProxyName == Proxy.Name.HTTPS) - req = await ProxyRequest.RequestHttpRemote(getFirstPacket.Packet, CTS.Token); - else if (getFirstPacket.ProxyName == Proxy.Name.Socks4) - req = await ProxyRequest.RequestSocks4Remote(proxyClient, getFirstPacket.Packet, CTS.Token); - else if (getFirstPacket.ProxyName == Proxy.Name.Socks5) - req = await ProxyRequest.RequestSocks5Remote(proxyClient, getFirstPacket.Packet, CTS.Token); - - req = await ApplyPrograms(lep.Address, req); - - if (req == null) - { - proxyClient.Disconnect(tcpClient); - return; - } - - // Create Tunnel - ProxyTunnel proxyTunnel = new(connectionId, proxyClient, req, ProxySettings_, SettingsSSL_); - proxyTunnel.Open(RulesProgram, UpStreamProxyProgram); - - proxyTunnel.OnTunnelDisconnected += ProxyTunnel_OnTunnelDisconnected; - proxyTunnel.OnDataReceived += ProxyTunnel_OnDataReceived; - - TunnelManager_.Add(connectionId, proxyTunnel); - } - - private void ProxyTunnel_OnTunnelDisconnected(object? sender, EventArgs e) - { - try - { - if (sender is not ProxyTunnel st) return; - - if (st.KillOnTimeout.IsRunning) - { - st.KillOnTimeout.Reset(); - st.KillOnTimeout.Stop(); - } - - st.OnTunnelDisconnected -= ProxyTunnel_OnTunnelDisconnected; - st.OnDataReceived -= ProxyTunnel_OnDataReceived; - st.ClientSSL?.Disconnect(); - - TunnelManager_.Remove(st.ConnectionId); - Debug.WriteLine($"{st.Req.Address} disconnected"); - } - catch (Exception ex) - { - Debug.WriteLine("ProxyTunnel_OnTunnelDisconnected: " + ex.Message); - } - } - - private void ProxyTunnel_OnDataReceived(object? sender, EventArgs e) - { - try - { - if (sender is not ProxyTunnel t) return; - - t.Client.OnDataReceived += async (s, e) => - { - // Client Received == Remote Sent - if (!t.KillOnTimeout.IsRunning) t.KillOnTimeout.Start(); - t.KillOnTimeout.Restart(); - if (e.Buffer.Length > 0) - { - if (t.Req.ApplyFragment) - Send(e.Buffer, t); - else - await t.RemoteClient.SendAsync(e.Buffer); - - lock (Stats) - { - Stats.AddBytes(e.Buffer.Length, ByteType.Sent); - } - } - - t.KillOnTimeout.Restart(); - await t.Client.StartReceiveAsync(); - t.KillOnTimeout.Restart(); - }; - - t.Client.OnDataSent += (s, e) => - { - // Client Sent == Remote Received - if (!t.KillOnTimeout.IsRunning) t.KillOnTimeout.Start(); - t.KillOnTimeout.Restart(); - lock (Stats) - { - Stats.AddBytes(e.Buffer.Length, ByteType.Received); - } - }; - - t.RemoteClient.OnDataReceived += async (s, e) => - { - t.KillOnTimeout.Restart(); - - if (e.Buffer.Length > 0) - await t.Client.SendAsync(e.Buffer); - - t.KillOnTimeout.Restart(); - await t.RemoteClient.StartReceiveAsync(); - t.KillOnTimeout.Restart(); - }; - - // Handle SSL - if (t.ClientSSL != null) - { - t.ClientSSL.OnClientDataReceived += (s, e) => - { - t.KillOnTimeout.Restart(); - - if (e.Buffer.Length > 0) - { - // Can't be implement here. Ex: "The WriteAsync method cannot be called when another write operation is pending" - //await t.ClientSSL.RemoteStream.WriteAsync(e.Buffer); - - lock (Stats) - { - Stats.AddBytes(e.Buffer.Length, ByteType.Sent); - } - } - t.ClientSSL.OnClientDataReceived -= null; - }; - - t.ClientSSL.OnRemoteDataReceived += (s, e) => - { - t.KillOnTimeout.Restart(); - - if (e.Buffer.Length > 0) - { - // Can't be implement here. Ex: "The WriteAsync method cannot be called when another write operation is pending" - //await t.ClientSSL.ClientStream.WriteAsync(e.Buffer); - - lock (Stats) - { - Stats.AddBytes(e.Buffer.Length, ByteType.Received); - } - } - t.ClientSSL.OnRemoteDataReceived -= null; - }; - } - } - catch (Exception ex) - { - Debug.WriteLine("ProxyTunnel_OnDataReceived: " + ex.Message); - } - } - - private void Send(byte[] data, ProxyTunnel t) - { - try - { - if (t.RemoteClient.Socket_ != null && t.RemoteClient.Socket_.Connected) - { - if (FragmentProgram.FragmentMode == ProxyProgram.Fragment.Mode.Disable) - { - // Static - ProxyProgram.Fragment bp = StaticFragmentProgram; - bp.DestHostname = t.Req.Address; - bp.DestPort = t.Req.Port; - if (bp.FragmentMode == ProxyProgram.Fragment.Mode.Program) - { - ProxyProgram.Fragment.ProgramMode programMode = new(data, t.RemoteClient.Socket_); - programMode.Send(bp); - } - else - t.RemoteClient.Socket_.Send(data); - } - else - { - // Const - ProxyProgram.Fragment bp = FragmentProgram; - bp.DestHostname = t.Req.Address; - bp.DestPort = t.Req.Port; - ProxyProgram.Fragment.ProgramMode programMode = new(data, t.RemoteClient.Socket_); - programMode.Send(bp); - } - } - } - catch (Exception ex) - { - Debug.WriteLine("Send: " + ex.Message); - } - } - -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Settings.cs b/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Settings.cs deleted file mode 100644 index 0555cd4..0000000 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/Settings.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Net; - -#nullable enable -namespace MsmhToolsClass.MsmhProxyServer; - -/// -/// Proxy server settings. -/// -public class ProxySettings -{ - private IPAddress ListenerIpAddress_ { get; set; } = IPAddress.Any; - - /// - /// The DNS hostname or IP address on which to listen. - /// - public IPAddress ListenerIpAddress - { - get => ListenerIpAddress_; - set - { - ListenerIpAddress_ = value ?? throw new ArgumentNullException(nameof(ListenerIpAddress)); - } - } - - private int ListenerPort_ { get; set; } = 10800; - - /// - /// The TCP port on which to listen. - /// - public int ListenerPort - { - get => ListenerPort_; - set - { - if (value < 0 || value > 65535) throw new ArgumentOutOfRangeException(nameof(ListenerPort)); - ListenerPort_ = value; - } - } - - private int MaxRequests_ { get; set; } = 600; - - /// - /// Maximum number of threads per second. (Min: 20) - /// - public int MaxRequests - { - get => MaxRequests_; - set - { - MaxRequests_ = value >= MsmhProxyServer.MaxRequestsDivide ? value : 600; - } - } - - /// - /// Kill Request if didn't receive data for n seconds. Default: 0 Sec (Disabled) - /// - public int RequestTimeoutSec { get; set; } = 0; - - /// - /// Kill Requests If CPU Usage is Higher than this Value. (Windows Only) - /// - public float KillOnCpuUsage { get; set; } = 40; - - public bool BlockPort80 { get; set; } = false; - -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/SettingsSSL.cs b/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/SettingsSSL.cs deleted file mode 100644 index 1c806b6..0000000 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/SettingsSSL.cs +++ /dev/null @@ -1,160 +0,0 @@ -using System.Diagnostics; -using System.Net; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; - -namespace MsmhToolsClass.MsmhProxyServer; - -public class SettingsSSL -{ - public bool EnableSSL { get; set; } = false; - public X509Certificate2 RootCA { get; private set; } - public string? RootCA_Path { get; set; } - public string? RootCA_KeyPath { get; set; } - public bool ChangeSni { get; set; } = true; - public string DefaultSni { get; set; } = string.Empty; - - public SettingsSSL(bool enableSSL) - { - EnableSSL = enableSSL; - RootCA = new(Array.Empty()); - if (!EnableSSL) - { - RootCA.Dispose(); - return; - } - } - - public async Task Build() - { - if (!EnableSSL) return; - - try - { - if (!string.IsNullOrEmpty(RootCA_Path) && File.Exists(RootCA_Path)) - { - // Read From File - X509Certificate2? rootCACert = BuildByFile(RootCA_Path, RootCA_KeyPath); - if (rootCACert != null) RootCA = new(rootCACert); - } - else - { - string path = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "RootCA")); - string rootCA_Path = path + ".crt"; - string rootCA_KeyPath = path + ".key"; - - string issuerSubjectName = "Msmh Proxy Server Authority"; - X509Certificate2? rootCACert; - - // Check IF Cert Exist - if (File.Exists(rootCA_Path) && File.Exists(rootCA_KeyPath)) - { - // Read From File - rootCACert = BuildByFile(rootCA_Path, rootCA_KeyPath); - } - else - { - try - { - if (File.Exists(rootCA_Path)) File.Delete(rootCA_Path); - if (File.Exists(rootCA_KeyPath)) File.Delete(rootCA_KeyPath); - } - catch (Exception) - { - // do nothing - } - - bool isInstalled = CertificateTool.IsCertificateInstalled(issuerSubjectName, StoreName.Root, StoreLocation.CurrentUser); - if (isInstalled) - { - while (true) - { - bool uninstalled = CertificateTool.UninstallCertificate(issuerSubjectName, StoreName.Root, StoreLocation.CurrentUser); - if (uninstalled) break; - } - } - - // Generate - IPAddress gateway = NetworkTool.GetDefaultGateway() ?? IPAddress.Loopback; - rootCACert = CertificateTool.GenerateRootCertificate(gateway, issuerSubjectName, out RSA privateKey); - - // Save Cert To File - await rootCACert.SaveToFileAsCrt(path); - - // Save Private Key To File - await privateKey.SavePrivateKeyToFile(path); - - string pass = Guid.NewGuid().ToString(); - if (!rootCACert.HasPrivateKey) - rootCACert = rootCACert.CopyWithPrivateKey(privateKey); - rootCACert = new(rootCACert.Export(X509ContentType.Pfx, pass), pass); - } - - if (rootCACert != null) - { - RootCA_Path = rootCA_Path; - RootCA_KeyPath = rootCA_KeyPath; - - RootCA = new(rootCACert); - } - } - } - catch (Exception ex) - { - RootCA.Dispose(); - EnableSSL = false; - Debug.WriteLine("SettingsSSL: " + ex.Message); - } - - // Check for "m_safeCertContext is an invalid handle" - try - { - _ = RootCA.Subject; - } - catch (Exception) - { - RootCA.Dispose(); - EnableSSL = false; - } - - if (EnableSSL && !string.IsNullOrEmpty(RootCA_Path) && File.Exists(RootCA_Path)) - { - // Check If Cert is Installed - bool isInstalled = CertificateTool.IsCertificateInstalled(RootCA.Subject, StoreName.Root, StoreLocation.CurrentUser); - if (!isInstalled) - { - // Install Cert - bool certInstalled = CertificateTool.InstallCertificate(RootCA_Path, StoreName.Root, StoreLocation.CurrentUser); - if (!certInstalled) - { - // User refused to install cert - RootCA.Dispose(); - EnableSSL = false; - } - } - } - } - - private X509Certificate2? BuildByFile(string rootCA_Path, string? rootCA_KeyPath) - { - try - { - X509Certificate2 rootCACert = new(X509Certificate2.CreateFromCertFile(rootCA_Path)); - - string pass = Guid.NewGuid().ToString(); - if (!rootCACert.HasPrivateKey && !string.IsNullOrEmpty(rootCA_KeyPath) && File.Exists(rootCA_KeyPath)) - { - RSA rootCAKey = RSA.Create(); - rootCAKey.ImportFromPem(File.ReadAllText(rootCA_KeyPath).ToCharArray()); - rootCACert = rootCACert.CopyWithPrivateKey(rootCAKey); - } - rootCACert = new(rootCACert.Export(X509ContentType.Pfx, pass), pass); - return new(rootCACert); - } - catch (Exception ex) - { - Debug.WriteLine("SettingsSSL BuildByFile: " + ex.Message); - return null; - } - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/TunnelManager.cs b/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/TunnelManager.cs deleted file mode 100644 index 80c0d98..0000000 --- a/MsmhToolsClass/MsmhToolsClass/MsmhProxyServer/TunnelManager.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Net.Sockets; - -#nullable enable -namespace MsmhToolsClass.MsmhProxyServer; - -internal class TunnelManager -{ - private readonly ConcurrentDictionary> Tunnels = new(); - - /// - /// Construct the Tunnel Manager. - /// - public TunnelManager() - { - - } - - internal void Add(int threadId, ProxyTunnel curr) - { - try - { - Tunnels.GetOrAdd(threadId, id => new Lazy(curr)); - } - catch (Exception ex) - { - Debug.WriteLine("TunnelManager Add: " + ex.Message); - } - } - - internal void Remove(int threadId) - { - try - { - if (Tunnels.ContainsKey(threadId)) - { - ProxyTunnel curr = Tunnels[threadId].Value; - if (curr != null) - { - if (curr.Client.Socket_ != null && curr.Client.Socket_.Connected) - { - curr.Client.Socket_.Shutdown(SocketShutdown.Both); - curr.Client.Socket_.Close(); - } - if (curr.RemoteClient.Socket_ != null && curr.RemoteClient.Socket_.Connected) - { - curr.RemoteClient.Socket_.Shutdown(SocketShutdown.Both); - curr.RemoteClient.Socket_.Close(); - } - Tunnels.TryRemove(threadId, out Lazy? _); - } - } - } - catch (Exception ex) - { - Debug.WriteLine("TunnelManager Remove: " + ex.Message); - } - } - - internal Dictionary> GetTunnels() - { - Dictionary> tempDic = new(Tunnels); - return tempDic; - } - - public int Count - { - get - { - int count = 0; - lock (Tunnels) - { - count = Tunnels.Count; - } - return count; - } - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/NetworkInterfaces.cs b/MsmhToolsClass/MsmhToolsClass/NetworkInterfaces.cs index fb1378b..0e296a0 100644 --- a/MsmhToolsClass/MsmhToolsClass/NetworkInterfaces.cs +++ b/MsmhToolsClass/MsmhToolsClass/NetworkInterfaces.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Globalization; using System.Management; +using System.Net; using System.Net.NetworkInformation; using System.Text.RegularExpressions; @@ -414,7 +415,16 @@ private static string GetStatusInfo(ushort n) /// public NetworkInterface? NIC { get; private set; } - public List DnsAddresses { get; private set; } = new(); + public bool IsIPv4ProtocolSupported { get; private set; } + public bool IsIPv6ProtocolSupported { get; private set; } + public List AnycastAddresses { get; private set; } = new(); + public List DhcpServerAddresses { get; private set; } = new(); + public List DnsAddresses { get; private set; } = new(); + public string DnsSuffix { get; private set; } = string.Empty; + public List GatewayAddresses { get; private set; } = new(); + public List MulticastAddresses { get; private set; } = new(); + public List UnicastAddresses { get; private set; } = new(); + public List WinsServersAddresses { get; private set; } = new(); /// /// Get NIC Information @@ -911,31 +921,7 @@ private void Read(ManagementBaseObject m, NetworkInterface? nic) NIC ??= NetworkTool.GetNICByName(NetConnectionID); // Get DNS Addresses - if (NIC != null) - { - try - { - IPAddressCollection dnss = NIC.GetIPProperties().DnsAddresses; - for (int n = 0; n < dnss.Count; n++) - DnsAddresses.Add(dnss[n].ToString()); - - if (string.IsNullOrEmpty(MACAddress)) - { - string macAddress = NIC.GetPhysicalAddress().ToString(); - if (!string.IsNullOrEmpty(macAddress)) - { - try - { - string regex = "(.{2})(.{2})(.{2})(.{2})(.{2})(.{2})"; - string replace = "$1:$2:$3:$4:$5:$6"; - MACAddress = Regex.Replace(macAddress, regex, replace); - } - catch (Exception) { } - } - } - } - catch (Exception) { } - } + if (NIC != null) ReadInternal(NIC); } private void Read(NetworkInterface? nic) @@ -965,27 +951,54 @@ private void Read(NetworkInterface? nic) else if (nic.OperationalStatus == OperationalStatus.Up) NetConnectionStatus = 2; else if (nic.OperationalStatus == OperationalStatus.NotPresent) NetConnectionStatus = 4; - IPAddressCollection dnss = nic.GetIPProperties().DnsAddresses; - for (int n = 0; n < dnss.Count; n++) - DnsAddresses.Add(dnss[n].ToString()); - GUID = nic.Id; - string macAddress = nic.GetPhysicalAddress().ToString(); - if (!string.IsNullOrEmpty(macAddress)) + if (nic.Speed > 0) Speed = Convert.ToUInt64(nic.Speed); + + ReadInternal(nic); + } + catch (Exception) { } + } + + private void ReadInternal(NetworkInterface nic) + { + try + { + IsIPv4ProtocolSupported = nic.Supports(NetworkInterfaceComponent.IPv4); + IsIPv6ProtocolSupported = nic.Supports(NetworkInterfaceComponent.IPv6); + + IPInterfaceProperties ipInterfaceProperties = nic.GetIPProperties(); + + // Addresses + AnycastAddresses.AddRange(ipInterfaceProperties.AnycastAddresses); + DhcpServerAddresses.AddRange(ipInterfaceProperties.DhcpServerAddresses); + DnsAddresses.AddRange(ipInterfaceProperties.DnsAddresses); + DnsSuffix = ipInterfaceProperties.DnsSuffix; + GatewayAddresses.AddRange(ipInterfaceProperties.GatewayAddresses); + MulticastAddresses.AddRange(ipInterfaceProperties.MulticastAddresses); + UnicastAddresses.AddRange(ipInterfaceProperties.UnicastAddresses); + WinsServersAddresses.AddRange(ipInterfaceProperties.WinsServersAddresses); + + // MAC Address + if (string.IsNullOrEmpty(MACAddress)) { - try + string macAddress = nic.GetPhysicalAddress().ToString(); + if (!string.IsNullOrEmpty(macAddress)) { - string regex = "(.{2})(.{2})(.{2})(.{2})(.{2})(.{2})"; - string replace = "$1:$2:$3:$4:$5:$6"; - MACAddress = Regex.Replace(macAddress, regex, replace); + try + { + string regex = "(.{2})(.{2})(.{2})(.{2})(.{2})(.{2})"; + string replace = "$1:$2:$3:$4:$5:$6"; + MACAddress = Regex.Replace(macAddress, regex, replace); + } + catch (Exception) { } } - catch (Exception) { } } - - if (nic.Speed > 0) Speed = Convert.ToUInt64(nic.Speed); } - catch (Exception) { } + catch (Exception ex) + { + Debug.WriteLine("NetworkInterfaces_ReadInternal: " + ex.Message); + } } //== TEST diff --git a/MsmhToolsClass/MsmhToolsClass/NetworkTool.cs b/MsmhToolsClass/MsmhToolsClass/NetworkTool.cs index 5a867e4..b78436d 100644 --- a/MsmhToolsClass/MsmhToolsClass/NetworkTool.cs +++ b/MsmhToolsClass/MsmhToolsClass/NetworkTool.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Globalization; using System.Management; +using System.Management.Automation; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; @@ -12,6 +13,63 @@ namespace MsmhToolsClass; public static class NetworkTool { + public static bool IsIPv4Supported() + { + bool result = false; + Socket? socket = null; + + try + { + socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + if (Socket.OSSupportsIPv4) result = true; + } + catch (Exception) + { + result = false; + } + + socket?.Dispose(); + return result; + } + + public static bool IsIPv6Supported() + { + bool result = false; + Socket? socket = null; + + try + { + socket = new(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); + if (Socket.OSSupportsIPv6) result = true; + } + catch (Exception) + { + result = false; + } + + socket?.Dispose(); + return result; + } + + public static HttpMethod ParseHttpMethod(string method) + { + method = method.Trim().ToLower(); + var httpMethod = method switch + { + "get" => HttpMethod.Get, + "head" => HttpMethod.Head, + "put" => HttpMethod.Put, + "post" => HttpMethod.Post, + "connect" => HttpMethod.Post, + "delete" => HttpMethod.Delete, + "patch" => HttpMethod.Patch, + "options" => HttpMethod.Options, + "trace" => HttpMethod.Trace, + _ => HttpMethod.Get, + }; + return httpMethod; + } + /// /// IP to Host using Nslookup (Windows Only) /// @@ -100,13 +158,13 @@ public static void GetUrlDetails(string url, int defaultPort, out string scheme, scheme = string.Empty; // Strip xxxx:// - if (url.Contains("//")) + if (url.Contains("://")) { - string[] split = url.Split("//"); + string[] split = url.Split("://"); if (split.Length > 0) if (!string.IsNullOrEmpty(split[0])) - scheme = $"{split[0]}//"; + scheme = $"{split[0]}://"; if (split.Length > 1) if (!string.IsNullOrEmpty(split[1])) @@ -178,9 +236,18 @@ public static void GetHostDetails(string hostIpPort, int defaultPort, out string if (isInt) port = result; } } + else if (hostIpPort.Contains('.')) // Host OR IPv4 + { + host0 = hostIpPort; + } + else + { + // There Is No Host + host0 = string.Empty; + } host = host0; - + // Get Base Host if (!IsIp(host, out _) && host.Contains('.')) { @@ -228,6 +295,7 @@ public static void GetHostDetails(string hostIpPort, int defaultPort, out string public static bool IsLocalIP(string ipv4) { + if (string.IsNullOrEmpty(ipv4)) return false; string ip = ipv4.Trim(); return ip.ToLower().Equals("localhost") || ip.Equals("0.0.0.0") || ip.StartsWith("10.") || ip.StartsWith("127.") || ip.StartsWith("192.168.") || ip.StartsWith("172.16.") || ip.StartsWith("172.17.") || ip.StartsWith("172.18.") || ip.StartsWith("172.19.") || @@ -260,13 +328,13 @@ public static bool IsLocalIP(string ipv4) return company; } - public static IPAddress? GetLocalIPv4(string remoteHostToCheck = "8.8.8.8") + public static IPAddress? GetLocalIPv4(string ipv4ToCheck = "8.8.8.8", int portToCheck = 53) { try { IPAddress? localIP; - using Socket socket = new(AddressFamily.InterNetwork, SocketType.Dgram, 0); - socket.Connect(remoteHostToCheck, 80); + using Socket socket = new(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + socket.Connect(ipv4ToCheck, portToCheck); IPEndPoint? endPoint = socket.LocalEndPoint as IPEndPoint; localIP = endPoint?.Address; return localIP; @@ -278,13 +346,13 @@ public static bool IsLocalIP(string ipv4) } } - public static IPAddress? GetLocalIPv6(string remoteHostToCheck = "8.8.8.8") + public static IPAddress? GetLocalIPv6(string ipv6ToCheck = "2001:4860:4860::8888", int portToCheck = 53) { try { IPAddress? localIP; - using Socket socket = new(AddressFamily.InterNetworkV6, SocketType.Dgram, 0); - socket.Connect(remoteHostToCheck, 80); + using Socket socket = new(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); + socket.Connect(ipv6ToCheck, portToCheck); IPEndPoint? endPoint = socket.LocalEndPoint as IPEndPoint; localIP = endPoint?.Address; return localIP; @@ -383,6 +451,11 @@ public static bool IsLocalIP(string ipv4) return null; } + public static bool IsDomainNameValid(string domain) + { + return Uri.CheckHostName(domain) != UriHostNameType.Unknown; + } + public static bool IsIp(string ipStr, out IPAddress? ip) { ip = null; @@ -399,32 +472,41 @@ public static bool IsIPv4(IPAddress iPAddress) public static bool IsIPv4Valid(string ipString, out IPAddress? iPAddress) { iPAddress = null; - if (string.IsNullOrWhiteSpace(ipString)) return false; - if (!ipString.Contains('.')) return false; - if (ipString.Count(c => c == '.') != 3) return false; - if (ipString.StartsWith('.')) return false; - if (ipString.EndsWith('.')) return false; - string[] splitValues = ipString.Split('.'); - if (splitValues.Length != 4) return false; - - foreach (string splitValue in splitValues) - { - // 0x and 0xx are not valid - if (splitValue.Length > 1) + + try + { + if (string.IsNullOrWhiteSpace(ipString)) return false; + if (!ipString.Contains('.')) return false; + if (ipString.Count(c => c == '.') != 3) return false; + if (ipString.StartsWith('.')) return false; + if (ipString.EndsWith('.')) return false; + string[] splitValues = ipString.Split('.'); + if (splitValues.Length != 4) return false; + + foreach (string splitValue in splitValues) { - bool isInt1 = int.TryParse(splitValue.AsSpan(0, 1), out int first); - if (isInt1 && first == 0) return false; + // 0x and 0xx are not valid + if (splitValue.Length > 1) + { + bool isInt1 = int.TryParse(splitValue.AsSpan(0, 1), out int first); + if (isInt1 && first == 0) return false; + } + + bool isInt2 = int.TryParse(splitValue, out int testInt); + if (!isInt2) return false; + if (testInt < 0 || testInt > 255) return false; } - bool isInt2 = int.TryParse(splitValue, out int testInt); - if (!isInt2) return false; - if (testInt < 0 || testInt > 255) return false; + bool isIP = IPAddress.TryParse(ipString, out IPAddress? outIP); + if (!isIP) return false; + iPAddress = outIP; + return true; + } + catch (Exception ex) + { + Debug.WriteLine("NetworkTool IsIPv4Valid: " + ex.Message); + return false; } - - bool isIP = IPAddress.TryParse(ipString, out IPAddress? outIP); - if (!isIP) return false; - iPAddress = outIP; - return true; } public static bool IsIPv6(IPAddress iPAddress) @@ -433,24 +515,84 @@ public static bool IsIPv6(IPAddress iPAddress) } /// - /// Windows Only + /// Is IP Protocol Supported By ISP (Windows Only) /// /// Ipv4 Or Ipv6 /// public static bool IsIpProtocolReachable(string ipStr) { if (!OperatingSystem.IsWindows()) return true; - string args = $"-n 2 {ipStr}"; + string args = $"-n 1 {ipStr}"; string content = ProcessManager.Execute(out _, "ping", null, args, true, false); return !content.Contains("transmit failed") && !content.Contains("General failure"); } + /// + /// All Platforms + /// + /// Port + public static bool IsPortOpen(int port) + { + if (OperatingSystem.IsWindows()) return ProcessManager.GetProcessPidsByUsingPort(port).Any(); + else return !IsPortAvailable(port); + } + + /// + /// All Platforms + /// + /// Port + public static bool IsPortAvailable(int port) + { + bool isAvailable = true; + + try + { + IPGlobalProperties iPGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); + + try + { + TcpConnectionInformation[] tcps = iPGlobalProperties.GetActiveTcpConnections(); + for (int n = 0; n < tcps.Length; n++) + { + TcpConnectionInformation tcp = tcps[n]; + if (tcp.LocalEndPoint.Port == port) + { + isAvailable = false; + break; + } + } + } + catch (Exception) { } + + if (isAvailable) + { + try + { + IPEndPoint[] udps = iPGlobalProperties.GetActiveUdpListeners(); + for (int n = 0; n < udps.Length; n++) + { + IPEndPoint ep = udps[n]; + if (ep.Port == port) + { + isAvailable = false; + break; + } + } + } + catch (Exception) { } + } + } + catch (Exception) { } + + return isAvailable; + } + public static bool IsPortOpen(string host, int port, double timeoutSeconds) { try { using TcpClient client = new(); - var result = client.BeginConnect(host, port, null, null); + IAsyncResult result = client.BeginConnect(host, port, null, null); bool success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(timeoutSeconds)); client.EndConnect(result); return success; @@ -478,9 +620,9 @@ public NetworkInterface? NIC } } public bool IsUpAndRunning { get; set; } = false; - public string DnsAddressPrimary { get; set; } = string.Empty; public bool IsDnsSetToLoopback { get; set; } = false; public bool IsDnsSetToAny { get; set; } = false; + public bool NonLocalDnsDetected { get; set; } = false; } /// @@ -517,34 +659,32 @@ public static List GetAllNetworkInterfaces() try { up = Convert.ToUInt16(m["NetConnectionStatus"]); } catch (Exception) { } bool isUpAndRunning = up == 2; // Connected - // Get Prinary DNS Address - string dnsAddressPrimary = string.Empty; + // Get DNS Addresses + bool isDnsSetToLoopback = false; + bool isDnsSetToAny = false; + bool nonLocalDnsDetected = false; + if (nic != null) { - try + IPAddressCollection dnss = nic.GetIPProperties().DnsAddresses; + for (int n = 0; n < dnss.Count; n++) { - IPAddressCollection dnss = nic.GetIPProperties().DnsAddresses; - if (dnss.Any()) dnsAddressPrimary = dnss[0].ToString(); + IPAddress dns = dnss[n]; + if (dns.Equals(IPAddress.Loopback)) isDnsSetToLoopback = true; + if (dns.Equals(IPAddress.Any)) isDnsSetToAny = true; + if (!dns.Equals(IPAddress.Loopback) && !dns.Equals(IPAddress.IPv6Loopback) && + !dns.Equals(IPAddress.Any) && !dns.Equals(IPAddress.IPv6Any)) nonLocalDnsDetected = true; } - catch (Exception) { } } - // Get IsDnsSetToLoopback - bool isDnsSetToLoopback = dnsAddressPrimary.Equals(IPAddress.Loopback.ToString()) || - dnsAddressPrimary.Equals(IPAddress.IPv6Loopback.ToString()); - - // Get IsDnsSetToAny - bool isDnsSetToAny = dnsAddressPrimary.Equals(IPAddress.Any.ToString()) || - dnsAddressPrimary.Equals(IPAddress.IPv6Any.ToString()); - NICResult nicr = new() { NIC_Name = netId0, NIC = nic, IsUpAndRunning = isUpAndRunning, - DnsAddressPrimary = dnsAddressPrimary, IsDnsSetToLoopback = isDnsSetToLoopback, - IsDnsSetToAny = isDnsSetToAny + IsDnsSetToAny = isDnsSetToAny, + NonLocalDnsDetected = nonLocalDnsDetected }; nicsList1.Add(nicr); } @@ -590,29 +730,29 @@ public static List GetNetworkInterfaces() { bool isUpAndRunning = nic.OperationalStatus == OperationalStatus.Up; - // Get Prinary DNS Address - string dnsAddressPrimary = string.Empty; - try + // Get DNS Addresses + bool isDnsSetToLoopback = false; + bool isDnsSetToAny = false; + bool nonLocalDnsDetected = false; + + IPAddressCollection dnss = nic.GetIPProperties().DnsAddresses; + for (int n = 0; n < dnss.Count; n++) { - IPAddressCollection dnss = nic.GetIPProperties().DnsAddresses; - if (dnss.Any()) dnsAddressPrimary = dnss[0].ToString(); + IPAddress dns = dnss[n]; + if (dns.Equals(IPAddress.Loopback) || dns.Equals(IPAddress.IPv6Loopback)) isDnsSetToLoopback = true; + if (dns.Equals(IPAddress.Any) || dns.Equals(IPAddress.IPv6Any)) isDnsSetToAny = true; + if (!dns.Equals(IPAddress.Loopback) && !dns.Equals(IPAddress.IPv6Loopback) && + !dns.Equals(IPAddress.Any) && !dns.Equals(IPAddress.IPv6Any)) nonLocalDnsDetected = true; } - catch (Exception) { } - - // Get IsDnsSetToLoopback - bool isDnsSetToLoopback = dnsAddressPrimary.Equals(IPAddress.Loopback.ToString()); - - // Get IsDnsSetToAny - bool isDnsSetToAny = dnsAddressPrimary.Equals(IPAddress.Any.ToString()); NICResult nicr = new() { NIC_Name = nic.Name, NIC = nic, IsUpAndRunning = isUpAndRunning, - DnsAddressPrimary = dnsAddressPrimary, IsDnsSetToLoopback = isDnsSetToLoopback, - IsDnsSetToAny = isDnsSetToAny + IsDnsSetToAny = isDnsSetToAny, + NonLocalDnsDetected = nonLocalDnsDetected }; nicsList.Add(nicr); } @@ -633,7 +773,7 @@ public static async Task EnableNICAsync(string nicName) public static void EnableNIC(string nicName) { string args = $"interface set interface \"{nicName}\" enable"; - ProcessManager.ExecuteOnly("netsh", args, true, true); + ProcessManager.ExecuteOnly("netsh", null, args, true, true); } public static async Task DisableNICAsync(string nicName) @@ -645,7 +785,61 @@ public static async Task DisableNICAsync(string nicName) public static void DisableNIC(string nicName) { string args = $"interface set interface \"{nicName}\" disable"; - ProcessManager.ExecuteOnly("netsh", args, true, true); + ProcessManager.ExecuteOnly("netsh", null, args, true, true); + } + + public static async Task EnableNicIPv6(string nicName) + { + bool success = false; + string args = $"Enable-NetAdapterBinding -Name '{nicName}' -ComponentID ms_tcpip6"; + try + { + await Task.Run(() => + { + using PowerShell ps = PowerShell.Create(RunspaceMode.NewRunspace); + + // Run As Admin + ps.AddCommand("Set-ExecutionPolicy") + .AddParameter("Scope", "Process") + .AddParameter("ExecutionPolicy", "Bypass") + .Invoke(); + + ps.AddScript(args).Invoke(); + success = !ps.HadErrors; + }); + } + catch (Exception ex) + { + Debug.WriteLine("EnableNicIPv6: " + ex.Message); + } + return success; + } + + public static async Task DisableNicIPv6(string nicName) + { + bool success = false; + string args = $"Disable-NetAdapterBinding -Name \"{nicName}\" -ComponentID ms_tcpip6"; + try + { + await Task.Run(() => + { + using PowerShell ps = PowerShell.Create(RunspaceMode.NewRunspace); + + // Run As Admin + ps.AddCommand("Set-ExecutionPolicy") + .AddParameter("Scope", "Process") + .AddParameter("ExecutionPolicy", "Bypass") + .Invoke(); + + ps.AddScript(args).Invoke(); + success = !ps.HadErrors; + }); + } + catch (Exception ex) + { + Debug.WriteLine("DisableNicIPv6: " + ex.Message); + } + return success; } public static NetworkInterface? GetNICByName(string name) @@ -678,6 +872,45 @@ public static void DisableNIC(string nicName) return null; } + /// + /// Set's the IPv4 DNS Server of the local machine (Windows Only) + /// + /// NIC Name + /// Comma seperated list of DNS server addresses + /// Requires a reference to the System.Management namespace + public static async Task SetDnsIPv4(string nicName, string dnsServers) + { + if (!OperatingSystem.IsWindows()) return; + // Requires Elevation + // Only netsh can set DNS on Windows 7 + if (string.IsNullOrEmpty(nicName)) return; + + try + { + string dnsServer1 = dnsServers; + string dnsServer2 = string.Empty; + if (dnsServers.Contains(',')) + { + string[] split = dnsServers.Split(','); + dnsServer1 = split[0].Trim(); + dnsServer2 = split[1].Trim(); + } + + string processName = "netsh"; + string processArgs1 = $"interface ipv4 delete dnsservers \"{nicName}\" all"; + string processArgs2 = $"interface ipv4 set dnsservers \"{nicName}\" static {dnsServer1} primary"; + string processArgs3 = $"interface ipv4 add dnsservers \"{nicName}\" {dnsServer2} index=2"; + await ProcessManager.ExecuteAsync(processName, null, processArgs1, true, true); + await ProcessManager.ExecuteAsync(processName, null, processArgs2, true, true); + if (!string.IsNullOrEmpty(dnsServer2)) + await ProcessManager.ExecuteAsync(processName, null, processArgs3, true, true); + } + catch (Exception ex) + { + Debug.WriteLine("SetDnsIPv4: " + ex.Message); + } + } + /// /// Set's the IPv4 DNS Server of the local machine (Windows Only) /// @@ -694,12 +927,12 @@ public static async Task SetDnsIPv4(NetworkInterface nic, string dnsServers) } /// - /// Set's the IPv4 DNS Server of the local machine (Windows Only) + /// Set's the IPv6 DNS Server of the local machine (Windows Only) /// /// NIC Name /// Comma seperated list of DNS server addresses /// Requires a reference to the System.Management namespace - public static async Task SetDnsIPv4(string nicName, string dnsServers) + public static async Task SetDnsIPv6(string nicName, string dnsServers) { if (!OperatingSystem.IsWindows()) return; // Requires Elevation @@ -718,9 +951,9 @@ public static async Task SetDnsIPv4(string nicName, string dnsServers) } string processName = "netsh"; - string processArgs1 = $"interface ipv4 delete dnsservers \"{nicName}\" all"; - string processArgs2 = $"interface ipv4 set dnsservers \"{nicName}\" static {dnsServer1} primary"; - string processArgs3 = $"interface ipv4 add dnsservers \"{nicName}\" {dnsServer2} index=2"; + string processArgs1 = $"interface ipv6 delete dnsservers \"{nicName}\" all"; + string processArgs2 = $"interface ipv6 set dnsservers \"{nicName}\" static {dnsServer1} primary"; + string processArgs3 = $"interface ipv6 add dnsservers \"{nicName}\" {dnsServer2} index=2"; await ProcessManager.ExecuteAsync(processName, null, processArgs1, true, true); await ProcessManager.ExecuteAsync(processName, null, processArgs2, true, true); if (!string.IsNullOrEmpty(dnsServer2)) @@ -728,21 +961,23 @@ public static async Task SetDnsIPv4(string nicName, string dnsServers) } catch (Exception ex) { - Debug.WriteLine("SetDnsIPv4: " + ex.Message); + Debug.WriteLine("SetDnsIPv6: " + ex.Message); } } /// - /// Unset IPv4 DNS to DHCP (Windows Only) + /// Set's the IPv6 DNS Server of the local machine (Windows Only) /// - /// Network Interface - public static async Task UnsetDnsIPv4(NetworkInterface nic) + /// NIC address + /// Comma seperated list of DNS server addresses + /// Requires a reference to the System.Management namespace + public static async Task SetDnsIPv6(NetworkInterface nic, string dnsServers) { if (!OperatingSystem.IsWindows()) return; - // Requires Elevation - Can't Unset DNS when there is no Internet connectivity but netsh can :) + // Requires Elevation if (nic == null) return; - await UnsetDnsIPv4(nic.Name); + await SetDnsIPv6(nic.Name, dnsServers); } /// @@ -770,6 +1005,19 @@ public static async Task UnsetDnsIPv4(string nicName) } } + /// + /// Unset IPv4 DNS to DHCP (Windows Only) + /// + /// Network Interface + public static async Task UnsetDnsIPv4(NetworkInterface nic) + { + if (!OperatingSystem.IsWindows()) return; + // Requires Elevation - Can't Unset DNS when there is no Internet connectivity but netsh can :) + if (nic == null) return; + + await UnsetDnsIPv4(nic.Name); + } + /// /// Unset IPv4 DNS by seting DNS to Static /// @@ -796,19 +1044,6 @@ public static async Task UnsetDnsIPv4(string nicName, string dns1, string? dns2) await SetDnsIPv4(nicName, dnsServers); } - /// - /// Unset IPv4 DNS to DHCP (Windows Only) - /// - /// Network Interface - public static async Task UnsetDnsIPv6(NetworkInterface nic) - { - if (!OperatingSystem.IsWindows()) return; - // Requires Elevation - Can't Unset DNS when there is no Internet connectivity but netsh can :) - if (nic == null) return; - - await UnsetDnsIPv6(nic.Name); - } - /// /// Unset IPv4 DNS to DHCP (Windows Only) /// @@ -834,6 +1069,19 @@ public static async Task UnsetDnsIPv6(string nicName) } } + /// + /// Unset IPv4 DNS to DHCP (Windows Only) + /// + /// Network Interface + public static async Task UnsetDnsIPv6(NetworkInterface nic) + { + if (!OperatingSystem.IsWindows()) return; + // Requires Elevation - Can't Unset DNS when there is no Internet connectivity but netsh can :) + if (nic == null) return; + + await UnsetDnsIPv6(nic.Name); + } + /// /// Is DNS Set to 127.0.0.1 - Using Nslookup (Windows Only) /// @@ -861,8 +1109,8 @@ public static bool IsDnsSetToLocal(out string host, out string ip) { line = line.Replace("address:", string.Empty).Trim(); ip = line; - if (ip.Equals("127.0.0.1")) result = true; if (ip.Equals(IPAddress.Loopback.ToString())) result = true; + if (ip.Equals(IPAddress.IPv6Loopback.ToString())) result = true; } } return result; @@ -1003,11 +1251,105 @@ public static async Task IsInternetAliveAsync(string? ipStr = null, int ti } } + public static async Task GetHttpStatusCode(string urlOrDomain, string? ip, int timeoutMs, bool useSystemProxy, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null, CancellationToken ct = default) + { + HttpStatusCode result = HttpStatusCode.RequestTimeout; + if (string.IsNullOrWhiteSpace(urlOrDomain)) return result; + HttpResponseMessage? response = null; + + urlOrDomain = urlOrDomain.Trim(); + GetUrlDetails(urlOrDomain, 443, out string scheme, out string host, out _, out _, out int port, out string path, out _); + if (string.IsNullOrEmpty(scheme)) + { + scheme = "https://"; + urlOrDomain = $"{scheme}{host}:{port}{path}"; + } + string url = urlOrDomain; + //Debug.WriteLine("GetHttpStatusCode: " + url); + if (!string.IsNullOrEmpty(ip)) + { + ip = ip.Trim(); + url = $"{scheme}{ip}:{port}{path}"; + //Debug.WriteLine("GetHttpStatusCode: " + url); + } + + try + { + Uri uri = new(url, UriKind.Absolute); + + using HttpClientHandler handler = new(); + handler.AllowAutoRedirect = true; + if (useSystemProxy) + { + // WebRequest.GetSystemWebProxy() Can't always detect System Proxy + proxyScheme = GetSystemProxy(); // Reading from Registry + if (!string.IsNullOrEmpty(proxyScheme)) + { + //Debug.WriteLine("GetHttpStatusCode: " + proxyScheme); + NetworkCredential credential = CredentialCache.DefaultNetworkCredentials; + handler.Proxy = new WebProxy(proxyScheme, true, null, credential); + handler.Credentials = credential; + handler.UseProxy = true; + } + else + { + Debug.WriteLine("GetHttpStatusCode: System Proxy Is Null."); + handler.UseProxy = false; + } + } + else if (!string.IsNullOrEmpty(proxyScheme)) + { + //Debug.WriteLine("GetHttpStatusCode: " + proxyScheme); + NetworkCredential credential = new(proxyUser, proxyPass); + handler.Proxy = new WebProxy(proxyScheme, true, null, credential); + handler.Credentials = credential; + handler.UseProxy = true; + } + else handler.UseProxy = false; + + // Ignore Cert Check To Make It Faster + handler.ClientCertificateOptions = ClientCertificateOption.Manual; + handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => true; + + // Get + using HttpRequestMessage message = new(HttpMethod.Get, uri); + message.Headers.TryAddWithoutValidation("User-Agent", "Other"); // Proxy Test Protocol Depends On This + message.Headers.TryAddWithoutValidation("Accept", "text/html,application/xhtml+xml,application/xml"); + message.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip, deflate"); + message.Headers.TryAddWithoutValidation("Accept-Charset", "ISO-8859-1"); + + if (!string.IsNullOrEmpty(ip)) + { + message.Headers.TryAddWithoutValidation("host", host); + } + + using HttpClient httpClient = new(handler); + httpClient.Timeout = TimeSpan.FromMilliseconds(timeoutMs); + response = await httpClient.SendAsync(message, ct).ConfigureAwait(false); + //response.EnsureSuccessStatusCode(); + } + catch (HttpRequestException hre) + { + if (hre.StatusCode != null) result = (HttpStatusCode)hre.StatusCode; + } + catch (Exception) { } + + if (response != null) + { + result = response.StatusCode; + //Debug.WriteLine("GetHttpStatusCode: " + result); + try { response.Dispose(); } catch (Exception) { } + } + + return result; + } + public static async Task GetHeaders(string urlOrDomain, string? ip, int timeoutMs, bool useSystemProxy, string? proxyScheme = null, string? proxyUser = null, string? proxyPass = null) { + if (string.IsNullOrWhiteSpace(urlOrDomain)) return string.Empty; HttpResponseMessage? response = null; - urlOrDomain = urlOrDomain.ToLower().Trim(); + urlOrDomain = urlOrDomain.Trim(); GetUrlDetails(urlOrDomain, 443, out string scheme, out string host, out _, out _, out int port, out string path, out _); if (string.IsNullOrEmpty(scheme)) { @@ -1015,12 +1357,12 @@ public static async Task GetHeaders(string urlOrDomain, string? ip, int urlOrDomain = $"{scheme}{host}:{port}{path}"; } string url = urlOrDomain; - Debug.WriteLine("GetHeaders: " + url); + //Debug.WriteLine("GetHeaders: " + url); if (!string.IsNullOrEmpty(ip)) { ip = ip.Trim(); url = $"{scheme}{ip}:{port}{path}"; - Debug.WriteLine("GetHeaders: " + url); + //Debug.WriteLine("GetHeaders: " + url); } try @@ -1035,7 +1377,7 @@ public static async Task GetHeaders(string urlOrDomain, string? ip, int proxyScheme = GetSystemProxy(); // Reading from Registry if (!string.IsNullOrEmpty(proxyScheme)) { - Debug.WriteLine("GetHeaders: " + proxyScheme); + //Debug.WriteLine("GetHeaders: " + proxyScheme); NetworkCredential credential = CredentialCache.DefaultNetworkCredentials; handler.Proxy = new WebProxy(proxyScheme, true, null, credential); handler.Credentials = credential; @@ -1049,21 +1391,20 @@ public static async Task GetHeaders(string urlOrDomain, string? ip, int } else if (!string.IsNullOrEmpty(proxyScheme)) { - Debug.WriteLine("GetHeaders: " + proxyScheme); + //Debug.WriteLine("GetHeaders: " + proxyScheme); NetworkCredential credential = new(proxyUser, proxyPass); handler.Proxy = new WebProxy(proxyScheme, true, null, credential); handler.Credentials = credential; handler.UseProxy = true; } - else - handler.UseProxy = false; + else handler.UseProxy = false; // Ignore Cert Check To Make It Faster handler.ClientCertificateOptions = ClientCertificateOption.Manual; handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => true; // Get Only Header - using HttpRequestMessage message = new(HttpMethod.Head, uri); + using HttpRequestMessage message = new(HttpMethod.Get, uri); message.Headers.TryAddWithoutValidation("User-Agent", "Other"); message.Headers.TryAddWithoutValidation("Accept", "text/html,application/xhtml+xml,application/xml"); message.Headers.TryAddWithoutValidation("Accept-Encoding", "gzip, deflate"); @@ -1081,20 +1422,27 @@ public static async Task GetHeaders(string urlOrDomain, string? ip, int catch (Exception) { } string result = string.Empty; - if (response != null) - { - result += response.StatusCode.ToString(); - Debug.WriteLine("GetHeaders: " + result); - result += Environment.NewLine + response.Headers.ToString(); - try { response.Dispose(); } catch (Exception) { } - } - result = result.Trim(); - if (string.IsNullOrEmpty(result) && !urlOrDomain.Contains("://www.")) + try { - urlOrDomain = $"{scheme}www.{host}:{port}{path}"; - result = await GetHeaders(urlOrDomain, ip, timeoutMs, useSystemProxy, proxyScheme, proxyUser, proxyPass); + if (response != null) + { + result += response.StatusCode.ToString(); + //Debug.WriteLine("GetHeaders: " + result); + result += Environment.NewLine + response.Headers.ToString(); + try { response.Dispose(); } catch (Exception) { } + } + result = result.ReplaceLineEndings(); + if (result.StartsWith(Environment.NewLine)) result = result.TrimStart(Environment.NewLine); + result = result.Trim(); + + if (string.IsNullOrEmpty(result) && !urlOrDomain.Contains("://www.")) + { + urlOrDomain = $"{scheme}www.{host}:{port}{path}"; + result = await GetHeaders(urlOrDomain, ip, timeoutMs, useSystemProxy, proxyScheme, proxyUser, proxyPass); + } } + catch (Exception) { } return result; } diff --git a/MsmhToolsClass/MsmhToolsClass/ProcessConsole.cs b/MsmhToolsClass/MsmhToolsClass/ProcessConsole.cs index a7f2911..487814c 100644 --- a/MsmhToolsClass/MsmhToolsClass/ProcessConsole.cs +++ b/MsmhToolsClass/MsmhToolsClass/ProcessConsole.cs @@ -12,10 +12,7 @@ public class ProcessConsole public event EventHandler? StandardDataReceived; public event EventHandler? ErrorDataReceived; - public ProcessConsole() - { - - } + public ProcessConsole() { } public string GetStdout => Stdout; public string GetStderr => Stderr; @@ -26,67 +23,75 @@ public ProcessConsole() /// public int Execute(string processName, string? args = null, bool hideWindow = true, bool runAsAdmin = false, string? workingDirectory = null, ProcessPriorityClass processPriorityClass = ProcessPriorityClass.Normal) { - int pid; - // Create process - Process_ = new(); - Process_.StartInfo.FileName = processName; + try + { + int pid; + // Create process + Process_ = new(); + Process_.StartInfo.FileName = processName; - if (args != null) - Process_.StartInfo.Arguments = args; + if (!string.IsNullOrEmpty(args)) + Process_.StartInfo.Arguments = args; - if (hideWindow) - { - Process_.StartInfo.CreateNoWindow = true; - Process_.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; - } - else - { - Process_.StartInfo.CreateNoWindow = false; - Process_.StartInfo.WindowStyle = ProcessWindowStyle.Normal; - } + if (hideWindow) + { + Process_.StartInfo.CreateNoWindow = true; + Process_.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + } + else + { + Process_.StartInfo.CreateNoWindow = false; + Process_.StartInfo.WindowStyle = ProcessWindowStyle.Normal; + } - if (runAsAdmin) - { - Process_.StartInfo.Verb = "runas"; - } - else - { - Process_.StartInfo.Verb = ""; - } + if (runAsAdmin) + { + Process_.StartInfo.Verb = "runas"; + } + else + { + Process_.StartInfo.Verb = ""; + } - // Redirect input output to get ability of sending and reading process output - Process_.StartInfo.UseShellExecute = false; - Process_.StartInfo.RedirectStandardInput = true; - Process_.StartInfo.RedirectStandardOutput = true; - Process_.StartInfo.RedirectStandardError = true; + // Redirect input output to get ability of sending and reading process output + Process_.StartInfo.UseShellExecute = false; + Process_.StartInfo.RedirectStandardInput = true; + Process_.StartInfo.RedirectStandardOutput = true; + Process_.StartInfo.RedirectStandardError = true; - if (workingDirectory != null) - Process_.StartInfo.WorkingDirectory = workingDirectory; + if (!string.IsNullOrEmpty(workingDirectory)) + Process_.StartInfo.WorkingDirectory = workingDirectory; - try - { - Process_.Start(); + try + { + Process_.Start(); + + // Set process priority + Process_.PriorityClass = processPriorityClass; + pid = Process_.Id; + } + catch (Exception ex) + { + pid = -1; + Debug.WriteLine("ProcessConsole Execute Start: " + ex.Message); + } + + Process_.OutputDataReceived -= Process__OutputDataReceived; + Process_.OutputDataReceived += Process__OutputDataReceived; + Process_.ErrorDataReceived -= Process__ErrorDataReceived; + Process_.ErrorDataReceived += Process__ErrorDataReceived; - // Set process priority - Process_.PriorityClass = processPriorityClass; - pid = Process_.Id; + Process_.BeginOutputReadLine(); + Process_.BeginErrorReadLine(); + + Pid = pid; + return pid; } catch (Exception ex) { - pid = -1; - Debug.WriteLine($"ExecuteOnly: {ex.Message}"); + Debug.WriteLine("ProcessConsole Execute: " + ex.Message); + return -1; } - - Process_.OutputDataReceived -= Process__OutputDataReceived; - Process_.OutputDataReceived += Process__OutputDataReceived; - Process_.ErrorDataReceived -= Process__ErrorDataReceived; - Process_.ErrorDataReceived += Process__ErrorDataReceived; - - Process_.BeginOutputReadLine(); - Process_.BeginErrorReadLine(); - - Pid = pid; - return pid; } private void Process__OutputDataReceived(object sender, DataReceivedEventArgs e) @@ -114,7 +119,7 @@ private void Process__ErrorDataReceived(object sender, DataReceivedEventArgs e) /// /// Command /// Returns True if success - public async Task SendCommandAsync(string command, int delayMS = 100, int timeoutSec = 5) + public async Task SendCommandAsync(string command, int delayMS = 10, int timeoutSec = 5) { try { diff --git a/MsmhToolsClass/MsmhToolsClass/ProcessManager.cs b/MsmhToolsClass/MsmhToolsClass/ProcessManager.cs index a239a53..ba1b4ee 100644 --- a/MsmhToolsClass/MsmhToolsClass/ProcessManager.cs +++ b/MsmhToolsClass/MsmhToolsClass/ProcessManager.cs @@ -299,13 +299,19 @@ public static string Execute(out Process process, string processName, Dictionary /// /// Execute and returns PID, if fails return -1 /// - public static int ExecuteOnly(string processName, string? args = null, bool hideWindow = true, bool runAsAdmin = false, string? workingDirectory = null, ProcessPriorityClass processPriorityClass = ProcessPriorityClass.Normal) + public static int ExecuteOnly(string processName, Dictionary? environmentVariables = null, string? args = null, bool hideWindow = true, bool runAsAdmin = false, string? workingDirectory = null, ProcessPriorityClass processPriorityClass = ProcessPriorityClass.Normal) { int pid; // Create process Process process0 = new(); process0.StartInfo.FileName = processName; + if (environmentVariables != null) + { + foreach (KeyValuePair kvp in environmentVariables) + process0.StartInfo.EnvironmentVariables[kvp.Key] = kvp.Value; + } + if (args != null) process0.StartInfo.Arguments = args; @@ -450,54 +456,48 @@ public static int GetFirstPidByName(string processName) } //----------------------------------------------------------------------------------- /// - /// Returns process PID, if faild returns -1 + /// Returns A List Of PIDs (Windows Only) /// - public static int GetProcessPidByListeningPort(int port) + public static List GetProcessPidsByUsingPort(int port) { - string netstatArgs = "-a -n -o"; - string? stdout = Execute(out Process process, "netstat", null, netstatArgs); - if (!string.IsNullOrWhiteSpace(stdout)) + List list = new(); + if (!OperatingSystem.IsWindows()) return list; + + try { - List lines = stdout.SplitToLines(); - for (int n = 0; n < lines.Count; n++) + string netstatArgs = "-a -n -o"; + string? stdout = Execute(out Process process, "netstat", null, netstatArgs); + if (!string.IsNullOrWhiteSpace(stdout)) { - string line = lines[n]; - if (!string.IsNullOrWhiteSpace(line) && line.Contains("LISTENING") && line.Contains($":{port} ")) + List lines = stdout.SplitToLines(); + for (int n = 0; n < lines.Count; n++) { - string[] split1 = line.Split("LISTENING", StringSplitOptions.TrimEntries); - bool isBool = int.TryParse(split1[1], out int pid); - if (isBool) + string line = lines[n].Trim(); + if (!string.IsNullOrEmpty(line) && line.Contains($":{port} ") && !line.Contains("FIN_WAIT_2")) { - process?.Dispose(); - return pid; + string[] splitLine = line.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + if (splitLine.Length > 2) + { + string localAddress = splitLine[1]; + if (localAddress.EndsWith($":{port}")) + { + string pidStr = splitLine[^1]; // Last Column Is PID + bool isBool = int.TryParse(pidStr, out int pid); + if (isBool && pid != 0 && !list.IsContain(pid)) list.Add(pid); + } + } } } } + process?.Dispose(); } - process?.Dispose(); - return -1; - } - //----------------------------------------------------------------------------------- - /// - /// Returns process name, if failed returns string.empty - /// - public static string GetProcessNameByListeningPort(int port) - { - int pid = GetProcessPidByListeningPort(port); - if (pid != -1) + catch (Exception ex) { - try - { - using Process process = Process.GetProcessById(pid); - return process.ProcessName; - } - catch (Exception ex) - { - Debug.WriteLine("Get Process Name By Listening Port:"); - Debug.WriteLine(ex.Message); - } + Debug.WriteLine("ProcessManager GetProcessPidsByUsingPort: " + ex.Message); } - return string.Empty; + + return list; } //----------------------------------------------------------------------------------- /// diff --git a/MsmhToolsClass/MsmhToolsClass/ProxifiedTcpClient/HttpProxyClient.cs b/MsmhToolsClass/MsmhToolsClass/ProxifiedClients/HttpTcpClient.cs similarity index 79% rename from MsmhToolsClass/MsmhToolsClass/ProxifiedTcpClient/HttpProxyClient.cs rename to MsmhToolsClass/MsmhToolsClass/ProxifiedClients/HttpTcpClient.cs index 8069ba4..d1456c9 100644 --- a/MsmhToolsClass/MsmhToolsClass/ProxifiedTcpClient/HttpProxyClient.cs +++ b/MsmhToolsClass/MsmhToolsClass/ProxifiedClients/HttpTcpClient.cs @@ -23,7 +23,7 @@ using System.Globalization; using System.Diagnostics; -namespace MsmhToolsClass.ProxifiedTcpClient; +namespace MsmhToolsClass.ProxifiedClients; /// /// HTTP connection proxy class. This class implements the HTTP standard proxy protocol. @@ -37,24 +37,22 @@ namespace MsmhToolsClass.ProxifiedTcpClient; /// /// /// -public class HttpProxyClient +public class HttpTcpClient { - private string _proxyHost = IPAddress.Loopback.ToString(); // Default - private int _proxyPort = 8080; // Default + private readonly string _proxyHost = IPAddress.Loopback.ToString(); // Default + private readonly int _proxyPort = 8080; // Default private readonly string _proxyUsername = string.Empty; private readonly string _proxyPassword = string.Empty; private HttpResponseCodes? _respCode; private string? _respText; private TcpClient? _tcpClient; - private TcpClient? _tcpClientCached; - private HttpVersions _httpVersion = HttpVersions.Version1_0; + private readonly HttpVersions _httpVersion = HttpVersions.Version1_0; private const string HTTP_PROXY_CONNECT_CMD = "CONNECT {0}:{1} HTTP/{2}\r\nHOST: {0}:{1}\r\n\r\n"; private const string HTTP_PROXY_AUTHENTICATE_CMD = "CONNECT {0}:{1} HTTP/{3}\r\nHOST: {0}:{1}\r\nProxy-Authorization: Basic {2}\r\n\r\n"; private const int WAIT_FOR_DATA_INTERVAL = 50; // 50 ms private const int WAIT_FOR_DATA_TIMEOUT = 15000; // 15 seconds - private const string PROXY_NAME = "HTTP"; /// /// HTTP header version enumeration. @@ -115,26 +113,12 @@ private enum HttpResponseCodes HTTPVersionNotSupported = 505 } - /// - /// Constructor. - /// - public HttpProxyClient() { } - - /// - /// Creates a HTTP proxy client object using the supplied TcpClient object connection. - /// - /// A TcpClient connection object. - public HttpProxyClient(TcpClient tcpClient) - { - _tcpClientCached = tcpClient; - } - /// /// Constructor. /// /// Host name or IP address of the proxy server. /// Port number for the proxy server. - public HttpProxyClient(string proxyHost, int proxyPort) + public HttpTcpClient(string proxyHost, int proxyPort) { if (proxyPort <= 0 || proxyPort > 65535) return; // "port must be greater than zero and less than 65535" @@ -150,7 +134,7 @@ public HttpProxyClient(string proxyHost, int proxyPort) /// Port number to connect to the proxy server. /// Username for the proxy server. /// Password for the proxy server. - public HttpProxyClient(string proxyHost, int proxyPort, string? proxyUsername, string? proxyPassword) + public HttpTcpClient(string proxyHost, int proxyPort, string? proxyUsername, string? proxyPassword) { if (proxyPort <= 0 || proxyPort > 65535) return; // "port must be greater than zero and less than 65535" @@ -165,54 +149,6 @@ public HttpProxyClient(string proxyHost, int proxyPort, string? proxyUsername, s _proxyPassword = proxyPassword; } - /// - /// Gets or sets host name or IP address of the proxy server. - /// - public string ProxyHost - { - get { return _proxyHost; } - set { _proxyHost = value; } - } - - /// - /// Gets or sets port number for the proxy server. - /// - public int ProxyPort - { - get { return _proxyPort; } - set { _proxyPort = value; } - } - - /// - /// Gets String representing the name of the proxy. - /// - /// This property will always return the value 'HTTP' - public string ProxyName - { - get { return PROXY_NAME; } - } - - /// - /// Gets or sets the TcpClient object. - /// This property can be set prior to executing CreateConnection to use an existing TcpClient connection. - /// - public TcpClient TcpClient - { - //get { return _tcpClientCached; } - set { _tcpClientCached = value; } - } - - /// - /// Gets or sets the HTTP header version. - /// Some proxy servers are very picky about the specific HTTP header version specified in the connection and - /// authentication strings. The default is version 1.0 but can be changed to version 1.1. - /// - public HttpVersions HttpVersion - { - get { return _httpVersion; } - set { _httpVersion = value; } - } - /// /// Creates a remote TCP connection through a proxy server to the destination host on the destination port. /// @@ -231,23 +167,15 @@ public HttpVersions HttpVersion { try { - // if we have no cached tcpip connection then create one - if (_tcpClientCached == null) - { - if (string.IsNullOrEmpty(_proxyHost)) return null; + if (string.IsNullOrEmpty(_proxyHost)) return null; - if (_proxyPort <= 0 || _proxyPort > 65535) return null; + if (_proxyPort <= 0 || _proxyPort > 65535) return null; - // create new tcp client object to the proxy server - _tcpClient = new(); + // create new tcp client object to the proxy server + _tcpClient = new(); - // attempt to open the connection - await _tcpClient.ConnectAsync(_proxyHost, _proxyPort); - } - else - { - _tcpClient = _tcpClientCached; - } + // attempt to open the connection + await _tcpClient.ConnectAsync(_proxyHost, _proxyPort); // send connection command to proxy host for the specified destination host and port await SendConnectionCommand(destinationHost, destinationPort); @@ -299,7 +227,7 @@ private async Task SendConnectionCommand(string host, int port) // create an byte response array byte[] response = new byte[_tcpClient.ReceiveBufferSize]; StringBuilder sbuilder = new(); - int bytes = 0; + int bytes; long total = 0; do @@ -367,7 +295,7 @@ private void HandleProxyCommandError(string host, int port) Debug.WriteLine(msg); } - private async Task WaitForData(NetworkStream stream) + private static async Task WaitForData(NetworkStream stream) { int sleepTime = 0; while (!stream.DataAvailable) @@ -394,10 +322,6 @@ private void ParseResponse(string response) private void ParseCodeAndText(string line) { - int begin = 0; - int end = 0; - string val = string.Empty; - if (!line.Contains("HTTP", StringComparison.CurrentCulture)) { string msg = $"No HTTP response received from proxy destination. Server response: {line}."; @@ -405,10 +329,10 @@ private void ParseCodeAndText(string line) return; } - begin = line.IndexOf(" ") + 1; - end = line.IndexOf(" ", begin); + int begin = line.IndexOf(" ") + 1; + int end = line.IndexOf(" ", begin); - val = line[begin..end]; + string val = line[begin..end]; if (!int.TryParse(val, out int code)) { diff --git a/MsmhToolsClass/MsmhToolsClass/ProxifiedClients/ProxifiedTcpClient.cs b/MsmhToolsClass/MsmhToolsClass/ProxifiedClients/ProxifiedTcpClient.cs new file mode 100644 index 0000000..9dd365d --- /dev/null +++ b/MsmhToolsClass/MsmhToolsClass/ProxifiedClients/ProxifiedTcpClient.cs @@ -0,0 +1,55 @@ +using System.Net; +using System.Net.Sockets; + +namespace MsmhToolsClass.ProxifiedClients; + +public class ProxifiedTcpClient +{ + private string? ProxyScheme { get; set; } + private string? ProxyUser { get; set; } + private string? ProxyPass { get; set; } + + public ProxifiedTcpClient(string? proxyScheme, string? proxyUser, string? proxyPass) + { + ProxyScheme = proxyScheme; + ProxyUser = proxyUser; + ProxyPass = proxyPass; + } + + public async Task<(bool isSuccess, TcpClient? proxifiedTcpClient)> TryGetConnectedProxifiedTcpClient(string host, int port) + { + if (!string.IsNullOrEmpty(ProxyScheme)) + { + NetworkTool.GetUrlDetails(ProxyScheme, 443, out _, out string proxyHost, out _, out _, out int proxyPort, out _, out _); + + try + { + if (ProxyScheme.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) || ProxyScheme.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) + { + HttpTcpClient httpTcpClient = new(proxyHost, proxyPort, ProxyUser, ProxyPass); + TcpClient? proxifiedClient = await httpTcpClient.CreateConnection(host, port).ConfigureAwait(false); + if (proxifiedClient != null) + { + return (true, proxifiedClient); + } + } + else if (ProxyScheme.StartsWith("socks5://", StringComparison.InvariantCultureIgnoreCase)) + { + Socks5TcpClient socks5TcpClient = new(proxyHost, proxyPort, ProxyUser, ProxyPass); + TcpClient? proxifiedClient = await socks5TcpClient.CreateConnection(host, port).ConfigureAwait(false); + if (proxifiedClient != null) + { + return (true, proxifiedClient); + } + } + } + catch (Exception) { } + } + return (false, null); + } + + public async Task<(bool isSuccess, TcpClient? proxifiedTcpClient)> TryGetConnectedProxifiedTcpClient(IPEndPoint ep) + { + return await TryGetConnectedProxifiedTcpClient(ep.Address.ToString(), ep.Port).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/ProxifiedTcpClient/Socks5ProxyClient.cs b/MsmhToolsClass/MsmhToolsClass/ProxifiedClients/Socks5TcpClient.cs similarity index 87% rename from MsmhToolsClass/MsmhToolsClass/ProxifiedTcpClient/Socks5ProxyClient.cs rename to MsmhToolsClass/MsmhToolsClass/ProxifiedClients/Socks5TcpClient.cs index e590ed4..f6d3892 100644 --- a/MsmhToolsClass/MsmhToolsClass/ProxifiedTcpClient/Socks5ProxyClient.cs +++ b/MsmhToolsClass/MsmhToolsClass/ProxifiedClients/Socks5TcpClient.cs @@ -22,7 +22,7 @@ using System.Net.Sockets; using System.Diagnostics; -namespace MsmhToolsClass.ProxifiedTcpClient; +namespace MsmhToolsClass.ProxifiedClients; /// /// Socks5 connection proxy class. This class implements the Socks5 standard proxy protocol. @@ -30,17 +30,14 @@ namespace MsmhToolsClass.ProxifiedTcpClient; /// /// This implementation supports TCP proxy connections with a Socks v5 server. /// -public class Socks5ProxyClient +public class Socks5TcpClient { - private string _proxyHost = IPAddress.Loopback.ToString(); // Default - private int _proxyPort = 1080; // Default - private string _proxyUsername = string.Empty; - private string _proxyPassword = string.Empty; + private readonly string _proxyHost = IPAddress.Loopback.ToString(); // Default + private readonly int _proxyPort = 1080; // Default + private readonly string _proxyUsername = string.Empty; + private readonly string _proxyPassword = string.Empty; private SocksAuthentication? _proxyAuthMethod; private TcpClient? _tcpClient; - private TcpClient? _tcpClientCached; - - private const string PROXY_NAME = "SOCKS5"; private const byte SOCKS5_VERSION_NUMBER = 5; private const byte SOCKS5_RESERVED = 0x00; @@ -78,29 +75,12 @@ private enum SocksAuthentication UsernamePassword } - /// - /// Create a Socks5 proxy client object. - /// - public Socks5ProxyClient() - { - - } - - /// - /// Creates a Socks5 proxy client object using the supplied TcpClient object connection. - /// - /// A TcpClient connection object. - public Socks5ProxyClient(TcpClient tcpClient) - { - _tcpClientCached = tcpClient; - } - /// /// Create a Socks5 proxy client object. /// /// Host name or IP address of the proxy server. /// Port used to connect to proxy server. - public Socks5ProxyClient(string proxyHost, int proxyPort) + public Socks5TcpClient(string proxyHost, int proxyPort) { if (proxyPort <= 0 || proxyPort > 65535) return; // "port must be greater than zero and less than 65535" @@ -116,7 +96,7 @@ public Socks5ProxyClient(string proxyHost, int proxyPort) /// Port used to connect to proxy server. /// Proxy authentication username. /// Proxy authentication password. - public Socks5ProxyClient(string proxyHost, int proxyPort, string? proxyUsername, string? proxyPassword) + public Socks5TcpClient(string proxyHost, int proxyPort, string? proxyUsername, string? proxyPassword) { if (proxyPort <= 0 || proxyPort > 65535) return; // "port must be greater than zero and less than 65535" @@ -131,43 +111,6 @@ public Socks5ProxyClient(string proxyHost, int proxyPort, string? proxyUsername, _proxyPassword = proxyPassword; } - /// - /// Gets or sets host name or IP address of the proxy server. - /// - public string ProxyHost - { - get { return _proxyHost; } - set { _proxyHost = value; } - } - - /// - /// Gets or sets port used to connect to proxy server. - /// - public int ProxyPort - { - get { return _proxyPort; } - set { _proxyPort = value; } - } - - /// - /// Gets String representing the name of the proxy. - /// - /// This property will always return the value 'SOCKS5' - public string ProxyName - { - get { return PROXY_NAME; } - } - - /// - /// Gets or sets the TcpClient object. - /// This property can be set prior to executing CreateConnection to use an existing TcpClient connection. - /// - public TcpClient TcpClient - { - //get { return _tcpClientCached; } - set { _tcpClientCached = value; } - } - /// /// Creates a remote TCP connection through a proxy server to the destination host on the destination port. /// @@ -187,25 +130,15 @@ public TcpClient TcpClient try { - // if we have no cached tcpip connection then create one - if (_tcpClientCached == null) - { - if (string.IsNullOrEmpty(_proxyHost)) - return null; + if (string.IsNullOrEmpty(_proxyHost)) return null; - if (_proxyPort <= 0 || _proxyPort > 65535) - return null; + if (_proxyPort <= 0 || _proxyPort > 65535) return null; - // create new tcp client object to the proxy server - _tcpClient = new TcpClient(); + // create new tcp client object to the proxy server + _tcpClient = new TcpClient(); - // attempt to open the connection - await _tcpClient.ConnectAsync(_proxyHost, _proxyPort); - } - else - { - _tcpClient = _tcpClientCached; - } + // attempt to open the connection + await _tcpClient.ConnectAsync(_proxyHost, _proxyPort); // determine which authentication method the client would like to use DetermineClientAuthMethod(); @@ -377,7 +310,7 @@ private async Task NegotiateServerAuthMethod() } } - private byte GetDestAddressType(string host) + private static byte GetDestAddressType(string host) { bool result = IPAddress.TryParse(host, out IPAddress? ipAddr); @@ -392,7 +325,7 @@ private byte GetDestAddressType(string host) }; } - private byte[]? GetDestAddressBytes(byte addressType, string host) + private static byte[]? GetDestAddressBytes(byte addressType, string host) { switch (addressType) { @@ -413,7 +346,7 @@ private byte GetDestAddressType(string host) } } - private byte[] GetDestPortBytes(int value) + private static byte[] GetDestPortBytes(int value) { byte[] array = new byte[2]; array[0] = Convert.ToByte(value / 256); @@ -500,7 +433,7 @@ private async Task SendCommand(byte command, string destinationHost, int destina HandleProxyCommandError(response, destinationHost, destinationPort); } - private void HandleProxyCommandError(byte[] response, string destinationHost, int destinationPort) + private static void HandleProxyCommandError(byte[] response, string destinationHost, int destinationPort) { byte replyCode = response[1]; byte addrType = response[3]; diff --git a/MsmhToolsClass/MsmhToolsClass/ProxifiedTcpClient/Utils.cs b/MsmhToolsClass/MsmhToolsClass/ProxifiedClients/Utils.cs similarity index 80% rename from MsmhToolsClass/MsmhToolsClass/ProxifiedTcpClient/Utils.cs rename to MsmhToolsClass/MsmhToolsClass/ProxifiedClients/Utils.cs index 6e75bbc..dbe0ec8 100644 --- a/MsmhToolsClass/MsmhToolsClass/ProxifiedTcpClient/Utils.cs +++ b/MsmhToolsClass/MsmhToolsClass/ProxifiedClients/Utils.cs @@ -1,11 +1,10 @@ -using System; using System.Text; using System.Globalization; using System.Net.Sockets; using System.Net; using System.ComponentModel; -namespace MsmhToolsClass.ProxifiedTcpClient; +namespace MsmhToolsClass.ProxifiedClients; internal static class Utils { @@ -33,55 +32,47 @@ public static string HexEncode(byte[] data) /// e.g. 0x55 ==> "55", also left pads with 0 so that 0x01 is "01" and not "1" public static string HexEncode(byte[] data, bool insertColonDelimiter, int length) { - if (data == null) - throw new ArgumentNullException(nameof(data)); - StringBuilder buffer = new(length * 2); - int len = data.Length; - for (int i = 0; i < len; i++) + try { - buffer.Append(data[i].ToString("x").PadLeft(2, '0')); //same as "%02X" in C - if (insertColonDelimiter && i < len - 1) - buffer.Append(':'); + int len = data.Length; + for (int i = 0; i < len; i++) + { + buffer.Append(data[i].ToString("x").PadLeft(2, '0')); //same as "%02X" in C + if (insertColonDelimiter && i < len - 1) + buffer.Append(':'); + } } + catch (Exception) { } + return buffer.ToString(); } internal static string GetHost(TcpClient client) { - if (client == null) - throw new ArgumentNullException(nameof(client)); - string host = string.Empty; + try { if (client.Client.RemoteEndPoint != null) host = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString(); } - catch (Exception) - { - // do nothing - }; + catch (Exception) { } return host; } internal static string GetPort(TcpClient client) { - if (client == null) - throw new ArgumentNullException(nameof(client)); - string port = ""; + try { if (client.Client.RemoteEndPoint != null) port = ((System.Net.IPEndPoint)client.Client.RemoteEndPoint).Port.ToString(CultureInfo.InvariantCulture); } - catch (Exception) - { - // do nothing - }; + catch (Exception) { } return port; } diff --git a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/BlackWhiteList.cs b/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/BlackWhiteList.cs deleted file mode 100644 index 9262cc0..0000000 --- a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/BlackWhiteList.cs +++ /dev/null @@ -1,99 +0,0 @@ -namespace MsmhToolsClass.ProxyServerPrograms; - -public partial class ProxyProgram -{ - public class BlackWhiteList - { - public enum Mode - { - BlackListFile, - BlackListText, - WhiteListFile, - WhiteListText, - Disable - } - - public Mode ListMode { get; private set; } = Mode.Disable; - public string PathOrText { get; private set; } = string.Empty; - public string TextContent { get; private set; } = string.Empty; - private List BWList { get; set; } = new(); - - public BlackWhiteList() { } - - /// - /// Set Black White List Database - /// - /// Mode - /// e.g. Each line: google.com - public void Set(Mode mode, string filePathOrText) - { - ListMode = mode; - PathOrText = filePathOrText; - - if (ListMode == Mode.Disable) return; - - if (ListMode == Mode.BlackListFile || ListMode == Mode.WhiteListFile) - { - try - { - TextContent = File.ReadAllText(Path.GetFullPath(filePathOrText)); - } - catch (Exception) { } - } - else if (ListMode == Mode.BlackListText || ListMode == Mode.WhiteListText) - TextContent = filePathOrText; - - if (!string.IsNullOrEmpty(TextContent) || !string.IsNullOrWhiteSpace(TextContent)) - { - TextContent += Environment.NewLine; - BWList = TextContent.SplitToLines(); - } - } - - // If True Return, If false Continue - public bool IsMatch(string destHostname) - { - string destHostnameNoWWW = destHostname; - if (destHostnameNoWWW.StartsWith("www.")) - destHostnameNoWWW = destHostnameNoWWW.TrimStart("www."); - - if (BWList.Any()) - { - for (int n = 0; n < BWList.Count; n++) - { - string host = BWList[n].Trim(); - if (!string.IsNullOrEmpty(host) && !host.StartsWith("//")) // Add Support Comment // - { - if (host.StartsWith("www.")) host = host.TrimStart("www."); - - // If Match - if (!host.StartsWith("*.")) - { - // No Wildcard - if (host.Equals(destHostnameNoWWW)) return match(); - } - else - { - // Wildcard - host = host[2..]; - if (!destHostnameNoWWW.Equals(host) && destHostnameNoWWW.EndsWith(host)) return match(); - } - } - } - } - - // If Not Match - return notMatch(); - - bool match() - { - return ListMode == Mode.BlackListFile || ListMode == Mode.BlackListText; - } - - bool notMatch() - { - return ListMode == Mode.WhiteListFile || ListMode == Mode.WhiteListText; - } - } - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Dns.cs b/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Dns.cs deleted file mode 100644 index 132b059..0000000 --- a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Dns.cs +++ /dev/null @@ -1,219 +0,0 @@ -using MsmhToolsClass.DnsTool; - -namespace MsmhToolsClass.ProxyServerPrograms; - -public partial class ProxyProgram -{ - public class Dns - { - public enum Mode - { - System, - DoH, - PlainDNS, - Disable - } - - public Mode DNSMode { get; private set; } = Mode.Disable; - public string? DNS { get; private set; } - public string? DnsCleanIp { get; private set; } - public int TimeoutSec { get; private set; } = 10; - public bool ChangeCloudflareIP { get; set; } = false; - - /// - /// Only for DoH Mode - /// - public string? ProxyScheme { get; private set; } - public string? Host { get; protected set; } - public string? CloudflareCleanIP { get; private set; } - public List CloudflareIPs { get; private set; } = new(); - - public Dns() { } - - public void Set(Mode mode, string? dns, string? dnsCleanIp, int timeoutSec, string? proxyScheme = null) - { - // Set - DNSMode = mode; - DNS = dns; - DnsCleanIp = dnsCleanIp ?? string.Empty; - TimeoutSec = timeoutSec; - ProxyScheme = proxyScheme; - - if (DNSMode == Mode.Disable) return; - if (string.IsNullOrEmpty(dns)) return; - - // Get Host - string host = dns; - if (DNSMode == Mode.DoH) - { - if (host.StartsWith("https://")) host = host[8..]; - if (host.Contains('/')) - { - string[] split = host.Split('/'); - host = split[0]; - } - } - else if (DNSMode == Mode.PlainDNS) - { - if (host.Contains(':')) - { - string[] split = host.Split(':'); - host = split[0]; - } - } - Host = host; - } - - /// - /// Redirect all Cloudflare IPs to a clean IP - /// - /// CF Clean IP - /// e.g. 103.21.244.0 - 103.21.244.255\n198.41.128.0 - 198.41.143.255 - public void SetCloudflareIPs(string cfCleanIP, string? cfIpRange = null) - { - if (!string.IsNullOrEmpty(cfIpRange)) - cfIpRange += Environment.NewLine; - ChangeCloudflareIP = true; - CloudflareCleanIP = cfCleanIP; - - // Built-in CF IPs - string defaultCfIPs = "103.21.244.0 - 103.21.244.255\n"; - defaultCfIPs += "103.22.200.0 - 103.22.200.255\n"; - defaultCfIPs += "103.31.4.0 - 103.31.5.255\n"; - defaultCfIPs += "104.16.0.0 - 104.31.255.255\n"; - defaultCfIPs += "108.162.192.0 - 108.162.207.255\n"; - defaultCfIPs += "131.0.72.0 - 131.0.75.255\n"; - defaultCfIPs += "141.101.64.0 - 141.101.65.255\n"; - defaultCfIPs += "162.158.0.0 - 162.158.3.255\n"; - defaultCfIPs += "172.64.0.0 - 172.67.255.255\n"; - defaultCfIPs += "173.245.48.0 - 173.245.48.255\n"; - defaultCfIPs += "188.114.96.0 - 188.114.99.255\n"; - defaultCfIPs += "190.93.240.0 - 190.93.243.255\n"; - defaultCfIPs += "197.234.240.0 - 197.234.243.255\n"; - defaultCfIPs += "198.41.128.0 - 198.41.143.255"; - - if (string.IsNullOrEmpty(cfIpRange) || string.IsNullOrWhiteSpace(cfIpRange)) - CloudflareIPs = defaultCfIPs.SplitToLines(); - else - CloudflareIPs = cfIpRange.SplitToLines(); - } - - private bool IsCfIP(string ipString) - { - try - { - string[] ips = ipString.Split('.'); - int ip1 = int.Parse(ips[0]); - int ip2 = int.Parse(ips[1]); - int ip3 = int.Parse(ips[2]); - int ip4 = int.Parse(ips[3]); - - for (int n = 0; n < CloudflareIPs.Count; n++) - { - string ipRange = CloudflareIPs[n].Trim(); - - if (!string.IsNullOrEmpty(ipRange)) - { - string[] split = ipRange.Split('-'); - string ipMin = split[0].Trim(); - string ipMax = split[1].Trim(); - - string[] ipMins = ipMin.Split('.'); - int ipMin1 = int.Parse(ipMins[0]); - int ipMin2 = int.Parse(ipMins[1]); - int ipMin3 = int.Parse(ipMins[2]); - int ipMin4 = int.Parse(ipMins[3]); - - string[] ipMaxs = ipMax.Split('.'); - int ipMax1 = int.Parse(ipMaxs[0]); - int ipMax2 = int.Parse(ipMaxs[1]); - int ipMax3 = int.Parse(ipMaxs[2]); - int ipMax4 = int.Parse(ipMaxs[3]); - - if (ip1 >= ipMin1 && ip1 <= ipMax1) - if (ip2 >= ipMin2 && ip2 <= ipMax2) - if (ip3 >= ipMin3 && ip3 <= ipMax3) - if (ip4 >= ipMin4 && ip4 <= ipMax4) - return true; - } - } - return false; - } - catch (Exception) - { - return false; - } - } - - public async Task Get(string destHostname) - { - if (string.IsNullOrEmpty(destHostname)) return string.Empty; - - // Don't resolve current Dns to avoid loop - if (destHostname.Equals(Host)) return destHostname; - if (destHostname.Equals(DnsCleanIp)) return destHostname; - - // Get - if (DNSMode == Mode.System) - { - string ipString = GetIP.GetIpFromSystem(destHostname, false); // Try Ipv4 - if (string.IsNullOrEmpty(ipString)) - ipString = GetIP.GetIpFromSystem(destHostname, true); // Try Ipv6 - if (!ChangeCloudflareIP) - return string.IsNullOrEmpty(ipString) ? destHostname : ipString; - else - { - if (string.IsNullOrEmpty(ipString)) return destHostname; - else - { - return IsCfIP(ipString) ? CloudflareCleanIP ?? ipString : ipString; - } - } - } - else if (DNSMode == Mode.DoH) - { - if (string.IsNullOrEmpty(DNS)) return string.Empty; - - string ipString = await GetIP.GetIpFromDohUsingWireFormat(destHostname, DNS, TimeoutSec, ProxyScheme); - if (!ChangeCloudflareIP) - return string.IsNullOrEmpty(ipString) ? destHostname : ipString; - else - { - if (string.IsNullOrEmpty(ipString)) return destHostname; - else - { - return IsCfIP(ipString) ? CloudflareCleanIP ?? ipString : ipString; - } - } - } - else if (DNSMode == Mode.PlainDNS) - { - if (string.IsNullOrEmpty(DNS)) return string.Empty; - - string plainDnsIP = DNS; - int plainDnsPort = 53; - - if (DNS.Contains(':')) - { - string[] dnsIpPort = DNS.Split(':'); - plainDnsIP = dnsIpPort[0]; - plainDnsPort = Convert.ToInt32(dnsIpPort[1]); - } - - string ipString = await GetIP.GetIpFromPlainDNS(destHostname, plainDnsIP, plainDnsPort, TimeoutSec); - if (!ChangeCloudflareIP) - return string.IsNullOrEmpty(ipString) ? destHostname : ipString; - else - { - if (string.IsNullOrEmpty(ipString)) return destHostname; - else - { - return IsCfIP(ipString) ? CloudflareCleanIP ?? ipString : ipString; - } - } - } - else if (DNSMode == Mode.Disable) return destHostname; - else return destHostname; - } - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/DontBypass.cs b/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/DontBypass.cs deleted file mode 100644 index 4b9d726..0000000 --- a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/DontBypass.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace MsmhToolsClass.ProxyServerPrograms; - -public partial class ProxyProgram -{ - public class DontBypass - { - public enum Mode - { - File, - Text, - Disable - } - - public Mode DontBypassMode { get; private set; } = Mode.Disable; - public string PathOrText { get; private set; } = string.Empty; - public string TextContent { get; private set; } = string.Empty; - private List DontBypassList { get; set; } = new(); - - public DontBypass() { } - - /// - /// Set DontBypass Database - /// - /// Mode - /// e.g. Each line: google.com - public void Set(Mode mode, string filePathOrText) - { - DontBypassMode = mode; - PathOrText = filePathOrText; - - if (DontBypassMode == Mode.Disable) return; - - if (DontBypassMode == Mode.File) - { - try - { - TextContent = File.ReadAllText(Path.GetFullPath(filePathOrText)); - } - catch (Exception) { } - } - else if (DontBypassMode == Mode.Text) - TextContent = filePathOrText; - - if (!string.IsNullOrEmpty(TextContent) || !string.IsNullOrWhiteSpace(TextContent)) - { - TextContent += Environment.NewLine; - DontBypassList = TextContent.SplitToLines(); - } - } - - // If True Don't Bypass, If false Bypass - public bool IsMatch(string destHostname) - { - string destHostnameNoWWW = destHostname; - if (destHostnameNoWWW.StartsWith("www.")) - destHostnameNoWWW = destHostnameNoWWW.TrimStart("www."); - - if (DontBypassList.Any()) - { - for (int n = 0; n < DontBypassList.Count; n++) - { - string host = DontBypassList[n].Trim(); - if (!string.IsNullOrEmpty(host) && !host.StartsWith("//")) // Add Support Comment // - { - if (host.StartsWith("www.")) host = host.TrimStart("www."); - - // If Match - if (!host.StartsWith("*.")) - { - // No Wildcard - if (host.Equals(destHostnameNoWWW)) return true; - } - else - { - // Wildcard - host = host[2..]; - if (!destHostnameNoWWW.Equals(host) && destHostnameNoWWW.EndsWith(host)) return true; - } - } - } - } - - // If Not Match - return false; - } - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/DpiBypass.cs b/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/DpiBypass.cs deleted file mode 100644 index ce81c38..0000000 --- a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/DpiBypass.cs +++ /dev/null @@ -1,550 +0,0 @@ -using System; -using System.Diagnostics; -using System.Net.Sockets; - -namespace MsmhToolsClass.ProxyServerPrograms; - -public partial class ProxyProgram -{ - public class DPIBypass - { - public Mode DPIBypassMode { get; private set; } = Mode.Disable; - public ChunkMode DPIChunkMode { get; private set; } - public int BeforeSniChunks { get; private set; } = 0; - public int SniChunks { get; private set; } = 0; - public int AntiPatternOffset { get; private set; } = 2; - public int FragmentDelay { get; private set; } = 0; - public string? DestHostname { get; set; } - public int DestPort { get; set; } - - public event EventHandler? OnChunkDetailsReceived; - - public DPIBypass() { } - - public void Set(Mode mode, int beforeSniChunks, ChunkMode chunkMode, int sniChunks, int antiPatternOffset, int fragmentDelay) - { - DPIBypassMode = mode; - BeforeSniChunks = beforeSniChunks; - DPIChunkMode = chunkMode; - SniChunks = sniChunks; - AntiPatternOffset = antiPatternOffset; - FragmentDelay = fragmentDelay; - } - - // Max Data Length = 65536 - private const int MaxDataLength = 65536; - public enum Mode - { - Program, - Disable - } - - public enum ChunkMode - { - SNI, - SniExtension, - AllExtensions - } - - public class ProgramMode - { - private byte[] Data { get; set; } - private Socket Socket { get; set; } - - public ProgramMode(byte[] data, Socket socket) - { - Data = data; - Socket = socket; - } - - public void Send(DPIBypass bp) - { - int offset = bp.AntiPatternOffset; - Random random = new(); - - // Anti Pattern Fragment Chunks - int beforeSniChunks = random.Next(bp.BeforeSniChunks - offset, bp.BeforeSniChunks + offset); - if (beforeSniChunks <= 0) beforeSniChunks = 1; - if (beforeSniChunks > Data.Length) beforeSniChunks = Data.Length; - - int sniChunks = random.Next(bp.SniChunks - offset, bp.SniChunks + offset); - if (sniChunks <= 0) sniChunks = 1; - if (sniChunks > Data.Length) sniChunks = Data.Length; - - //Test(Data, Socket, beforeSniChunks, sniChunks, offset, bp); - - if (bp.DPIChunkMode == ChunkMode.AllExtensions) - SendDataInFragmentAllExtensions(Data, Socket, beforeSniChunks, sniChunks, offset, bp); - else if (bp.DPIChunkMode == ChunkMode.SniExtension) - SendDataInFragmentSniExtension(Data, Socket, beforeSniChunks, sniChunks, offset, bp); - else if (bp.DPIChunkMode == ChunkMode.SNI) - SendDataInFragmentSNI(Data, Socket, beforeSniChunks, sniChunks, offset, bp); - } - - private void Test(byte[] data, Socket socket, int beforeSniChunks, int sniChunks, int offset, DPIBypass bp) - { - Debug.WriteLine("Send Data in TEST"); - // Create packets - List packets = new(); - packets.Clear(); - - SniModifire sniModifire = new(data); - if (sniModifire.HasSni) - { - packets.Add(sniModifire.ModifiedData); - SendPackets(sniModifire.ModifiedData, socket, bp, packets); - } - else - { - packets.Add(data); - SendPackets(data, socket, bp, packets); - } - - } - - private void SendDataInFragmentAllExtensions(byte[] data, Socket socket, int beforeSniChunks, int sniChunks, int offset, DPIBypass bp) - { - //Debug.WriteLine("SendDataInFragmentAllExtensions"); - // Create packets - List packets = new(); - packets.Clear(); - - SniReader sniReader = new(data); - - if (beforeSniChunks == 1 && sniChunks == 1) - { - packets.Add(data); - } - else - { - if (sniReader.HasTlsExtensions) - { - int prevIndex; - int pos = 0; - SniReader.TlsExtensions allExtensions = sniReader.AllExtensions; - - pos += allExtensions.StartIndex; - prevIndex = pos - allExtensions.StartIndex; - - // Create packet before SNI - int beforeSniLength = allExtensions.StartIndex - prevIndex; - if (beforeSniLength > 0) - { - byte[] beforeSNI = new byte[beforeSniLength]; - Buffer.BlockCopy(data, prevIndex, beforeSNI, 0, beforeSniLength); - - List chunkedbeforeSNI = ChunkDataNormal(beforeSNI, beforeSniChunks, offset); - packets = packets.Concat(chunkedbeforeSNI).ToList(); - //Debug.WriteLine($"{prevIndex} ======> {beforeSniLength}"); - } - - // Create SNI packet - List chunkedSNI = ChunkDataNormal(allExtensions.Data, sniChunks, offset); - packets = packets.Concat(chunkedSNI).ToList(); - - //Debug.WriteLine($"{beforeSniLength} ====== {sni.SniStartIndex}"); - //Debug.WriteLine($"{sni.SniStartIndex} ======> {sni.SniStartIndex + sni.SniLength}"); - Debug.WriteLine("==-----== " + (sniReader.AllExtensions.StartIndex + sniReader.AllExtensions.Length) + " of " + data.Length); - pos = allExtensions.StartIndex + allExtensions.Length; - - // Create packet after SNI - if (pos < data.Length) - { - int afterSniStartIndex = pos; - int afterSniLength = data.Length - pos; - byte[] afterSni = new byte[afterSniLength]; - Buffer.BlockCopy(data, afterSniStartIndex, afterSni, 0, afterSniLength); - packets.Add(afterSni); - - //Debug.WriteLine($"{sni.SniStartIndex + sni.SniLength} ====== {afterSniStartIndex}"); - //Debug.WriteLine($"{afterSniStartIndex} ======> {afterSniStartIndex + afterSniLength}"); - //Debug.WriteLine($"{afterSniStartIndex + afterSniLength} ====== {data.Length}"); - } - } - else - { - packets.Add(data); - } - } - - SendPackets(data, socket, bp, packets); - } - - private void SendDataInFragmentSniExtension(byte[] data, Socket socket, int beforeSniChunks, int sniChunks, int offset, DPIBypass bp) - { - //Debug.WriteLine("SendDataInFragmentSniExtension"); - // Create packets - List packets = new(); - packets.Clear(); - - SniReader sniReader = new(data); - if (sniReader.SniExtensionList.Count > 1) Debug.WriteLine($"=======================> We Have {sniReader.SniExtensionList.Count} SNI Extensions."); - - if (beforeSniChunks == 1 && sniChunks == 1) - { - packets.Add(data); - } - else - { - if (sniReader.HasSniExtension) - { - int prevIndex; - int pos = 0; - for (int n = 0; n < sniReader.SniExtensionList.Count; n++) - { - SniReader.SniExtension sniExtension = sniReader.SniExtensionList[n]; - - pos += sniExtension.StartIndex; - prevIndex = pos - sniExtension.StartIndex; - - // Create packet before SNI - int beforeSniLength = sniExtension.StartIndex - prevIndex; - if (beforeSniLength > 0) - { - byte[] beforeSNI = new byte[beforeSniLength]; - Buffer.BlockCopy(data, prevIndex, beforeSNI, 0, beforeSniLength); - - List chunkedbeforeSNI = ChunkDataNormal(beforeSNI, beforeSniChunks, offset); - packets = packets.Concat(chunkedbeforeSNI).ToList(); - //Debug.WriteLine($"{prevIndex} ======> {beforeSniLength}"); - } - - // Create SNI packet - List chunkedSNI = ChunkDataNormal(sniExtension.Data, sniChunks, offset); - packets = packets.Concat(chunkedSNI).ToList(); - - //Debug.WriteLine($"{beforeSniLength} ====== {sni.SniStartIndex}"); - //Debug.WriteLine($"{sni.SniStartIndex} ======> {sni.SniStartIndex + sni.SniLength}"); - - pos = sniExtension.StartIndex + sniExtension.Length; - - // Last round - if (n == sniReader.SniExtensionList.Count - 1) - { - // Create packet after SNI - if (pos < data.Length) - { - int afterSniStartIndex = pos; - int afterSniLength = data.Length - pos; - byte[] afterSni = new byte[afterSniLength]; - Buffer.BlockCopy(data, afterSniStartIndex, afterSni, 0, afterSniLength); - packets.Add(afterSni); - - //Debug.WriteLine($"{sni.SniStartIndex + sni.SniLength} ====== {afterSniStartIndex}"); - //Debug.WriteLine($"{afterSniStartIndex} ======> {afterSniStartIndex + afterSniLength}"); - //Debug.WriteLine($"{afterSniStartIndex + afterSniLength} ====== {data.Length}"); - } - } - } - } - else - { - packets.Add(data); - } - } - - SendPackets(data, socket, bp, packets); - } - - private void SendDataInFragmentSNI(byte[] data, Socket socket, int beforeSniChunks, int sniChunks, int offset, DPIBypass bp) - { - //Debug.WriteLine("SendDataInFragmentSNI"); - // Create packets - List packets = new(); - packets.Clear(); - - SniReader sniReader = new(data); - if (sniReader.SniList.Count > 1) Debug.WriteLine($"=======================> We Have {sniReader.SniList.Count} SNIs."); - - if (beforeSniChunks == 1 && sniChunks == 1) - { - packets.Add(data); - } - else - { - if (sniReader.HasSni) - { - int prevIndex; - int pos = 0; - for (int n = 0; n < sniReader.SniList.Count; n++) - { - SniReader.SNI sni = sniReader.SniList[n]; - - pos += sni.StartIndex; - prevIndex = pos - sni.StartIndex; - - // Create packet before SNI - int beforeSniLength = sni.StartIndex - prevIndex; - if (beforeSniLength > 0) - { - byte[] beforeSNI = new byte[beforeSniLength]; - Buffer.BlockCopy(data, prevIndex, beforeSNI, 0, beforeSniLength); - - List chunkedbeforeSNI = ChunkDataNormal(beforeSNI, beforeSniChunks, offset); - packets = packets.Concat(chunkedbeforeSNI).ToList(); - //Debug.WriteLine($"{prevIndex} ======> {beforeSniLength}"); - } - - // Create SNI packet - List chunkedSNI = ChunkDataNormal(sni.Data, sniChunks, offset); - packets = packets.Concat(chunkedSNI).ToList(); - - //Debug.WriteLine($"{beforeSniLength} ====== {sni.SniStartIndex}"); - //Debug.WriteLine($"{sni.SniStartIndex} ======> {sni.SniStartIndex + sni.SniLength}"); - - pos = sni.StartIndex + sni.Length; - - // Last round - if (n == sniReader.SniList.Count - 1) - { - // Create packet after SNI - if (pos < data.Length) - { - int afterSniStartIndex = pos; - int afterSniLength = data.Length - pos; - byte[] afterSni = new byte[afterSniLength]; - Buffer.BlockCopy(data, afterSniStartIndex, afterSni, 0, afterSniLength); - packets.Add(afterSni); - - //Debug.WriteLine($"{sni.SniStartIndex + sni.SniLength} ====== {afterSniStartIndex}"); - //Debug.WriteLine($"{afterSniStartIndex} ======> {afterSniStartIndex + afterSniLength}"); - //Debug.WriteLine($"{afterSniStartIndex + afterSniLength} ====== {data.Length}"); - } - } - } - } - else - { - packets.Add(data); - } - } - - SendPackets(data, socket, bp, packets); - } - - private List ChunkDataNormal(byte[] data, int chunks, int offset) - { - //Debug.WriteLine("ChunkDataNormal"); - // Create chunk packets - Random random = new(); - List chunkPackets = new(); - chunkPackets.Clear(); - int prevIndex; - int nn = 0; - int sum = 0; - for (int n = 0; n < data.Length; n++) - { - try - { - // Anti Pattern Fragment Size - int fragmentSize = data.Length / chunks; - - int fragmentSizeOut = random.Next(fragmentSize - offset, fragmentSize + offset); - if (fragmentSizeOut <= 0) fragmentSizeOut = 1; - if (fragmentSizeOut > data.Length) fragmentSizeOut = data.Length; - nn += fragmentSizeOut; - - if (nn > data.Length) - { - fragmentSizeOut = data.Length - (nn - fragmentSizeOut); - //Debug.WriteLine(fragmentSizeOut); - } - //Debug.WriteLine(fragmentSizeOut); - - sum += fragmentSizeOut; - byte[] fragmentData = new byte[fragmentSizeOut]; - prevIndex = sum - fragmentSizeOut; - Buffer.BlockCopy(data, prevIndex, fragmentData, 0, fragmentSizeOut); - chunkPackets.Add(fragmentData); - - if (sum >= data.Length) break; - } - catch (Exception ex) - { - chunkPackets.Clear(); - string msgEvent = $"Error, Creating normal packets: {ex.Message}"; - Debug.WriteLine(msgEvent); - return chunkPackets; - } - } - - return chunkPackets; - } - - private List ChunkDataNormal2(byte[] data, int fragmentSize) - { - Debug.WriteLine("ChunkDataNormal2"); - // Create chunk packets - List chunkPackets = new(); - chunkPackets.Clear(); - var fragments = data.Chunk(fragmentSize); - for (int n = 0; n < fragments.Count(); n++) - { - try - { - byte[] fragment = fragments.ToArray()[n]; - chunkPackets.Add(fragment); - } - catch (Exception ex) - { - chunkPackets.Clear(); - string msgEvent = $"Error, Creating normal2 packets: {ex.Message}"; - Debug.WriteLine(msgEvent); - return chunkPackets; - } - } - - return chunkPackets; - } - - private List ChunkDataNormal3(byte[] data, int fragmentSize) - { - Debug.WriteLine("ChunkDataNormal3"); - // Create chunk packets - List chunkPackets = new(); - chunkPackets.Clear(); - var fragments = ChunkViaMemory(data, fragmentSize); - for (int n = 0; n < fragments.Count(); n++) - { - try - { - byte[] fragment = fragments.ToArray()[n].ToArray(); - chunkPackets.Add(fragment); - } - catch (Exception ex) - { - chunkPackets.Clear(); - string msgEvent = $"Error, Creating normal3 packets: {ex.Message}"; - Debug.WriteLine(msgEvent); - return chunkPackets; - } - } - - return chunkPackets; - } - - private List ChunkDataRandom(byte[] data, int fragmentChunks) - { - Debug.WriteLine("ChunkDataRandom"); - //// Calculate fragment chunks from size - //int fragmentChunks = data.Length / fragmentSize; - //if (fragmentChunks <= 0) fragmentChunks = 1; - //if (fragmentChunks > data.Length) fragmentChunks = data.Length; - - // Create chunk packets - List packets = new(); - packets.Clear(); - fragmentChunks = Math.Min(fragmentChunks, data.Length); - List indices; - if (fragmentChunks < data.Length) - indices = GenerateRandomIndices(1, data.Length - 1, fragmentChunks - 1); - else - indices = Enumerable.Range(0, data.Length - 1).ToList(); - indices.Sort(); - - int prevIndex = 0; - for (int n = 0; n < indices.Count; n++) - { - try - { - int index = indices[n]; - byte[] fragmentData = new byte[index - prevIndex]; - Buffer.BlockCopy(data, prevIndex, fragmentData, 0, fragmentData.Length); - packets.Add(fragmentData); - prevIndex = index; - } - catch (Exception ex) - { - packets.Clear(); - string msgEvent = $"Error, Creating random packets: {ex.Message}"; - Debug.WriteLine(msgEvent); - return packets; - } - } - - try - { - byte[] lastFragmentData = new byte[data.Length - prevIndex]; - Buffer.BlockCopy(data, prevIndex, lastFragmentData, 0, lastFragmentData.Length); - packets.Add(lastFragmentData); - } - catch (Exception ex) - { - packets.Clear(); - string msgEvent = $"Error, Creating last random packet: {ex.Message}"; - Debug.WriteLine(msgEvent); - return packets; - } - - return packets; - } - - private void SendPackets(byte[] data, Socket socket, DPIBypass bp, List packets) - { - // Check packets - int allLength = 0; - for (int i = 0; i < packets.Count; i++) - allLength += packets[i].Length; - - if (allLength != data.Length) - { - Debug.WriteLine($"{allLength} == {data.Length}, Chunks: {packets.Count}"); - packets.Clear(); - return; - } - - // Send packets - for (int i = 0; i < packets.Count; i++) - { - try - { - byte[] fragmentData = packets[i]; - if (socket == null) return; - socket.Send(fragmentData); - if (bp.FragmentDelay > 0) - Task.Delay(bp.FragmentDelay).Wait(); - } - catch (Exception ex) - { - string msgEvent = $"Error, Send Packets: {ex.Message}"; - Debug.WriteLine(msgEvent); - return; - } - } - - string chunkDetailsEvent = $"{bp.DestHostname}:{bp.DestPort} Length: {data.Length}"; - if (packets.Count > 1) - chunkDetailsEvent += $", Chunks: {packets.Count}"; - bp.OnChunkDetailsReceived?.Invoke(chunkDetailsEvent, EventArgs.Empty); - } - } - - private static List GenerateRandomIndices(int minValue, int maxValue, int count) - { - Random random = new(); - HashSet indicesSet = new(); - - while (indicesSet.Count < count) - { - indicesSet.Add(random.Next(minValue, maxValue)); - } - - return new List(indicesSet); - } - - private static IEnumerable> ChunkViaMemory(T[] data, int size) - { - var chunks = data.Length / size; - for (int i = 0; i < chunks; i++) - { - yield return data.AsMemory(i * size, size); - } - var leftOver = data.Length % size; - if (leftOver > 0) - { - yield return data.AsMemory(chunks * size, leftOver); - } - } - - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/FakeDns.cs b/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/FakeDns.cs deleted file mode 100644 index fd8d4fa..0000000 --- a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/FakeDns.cs +++ /dev/null @@ -1,119 +0,0 @@ -namespace MsmhToolsClass.ProxyServerPrograms; - -public partial class ProxyProgram -{ - public class FakeDns - { - public enum Mode - { - File, - Text, - Disable - } - - public Mode FakeDnsMode { get; private set; } = Mode.Disable; - public string PathOrText { get; private set; } = string.Empty; - public string TextContent { get; private set; } = string.Empty; - private List Host_Ip_List { get; set; } = new(); - - public FakeDns() { } - - /// - /// Set Fake DNS Database - /// - /// Mode - /// e.g. Each line: dns.google.com|8.8.8.8 - public void Set(Mode mode, string filePathOrText) - { - FakeDnsMode = mode; - PathOrText = filePathOrText; - - if (FakeDnsMode == Mode.Disable) return; - - if (FakeDnsMode == Mode.File) - { - try - { - TextContent = File.ReadAllText(Path.GetFullPath(filePathOrText)); - } - catch (Exception) { } - } - else if (FakeDnsMode == Mode.Text) - TextContent = filePathOrText; - - if (!string.IsNullOrEmpty(TextContent) || !string.IsNullOrWhiteSpace(TextContent)) - { - TextContent += Environment.NewLine; - Host_Ip_List = TextContent.SplitToLines(); - } - } - - public string Get(string destHostname) - { - string destHostnameNoWWW = destHostname; - if (destHostnameNoWWW.StartsWith("www.")) - destHostnameNoWWW = destHostnameNoWWW.TrimStart("www."); - - if (destHostnameNoWWW.EndsWith('/')) destHostnameNoWWW = destHostnameNoWWW[0..^1]; - - if (Host_Ip_List.Any()) - { - for (int n = 0; n < Host_Ip_List.Count; n++) - { - string hostIP = Host_Ip_List[n].Trim(); - if (!string.IsNullOrEmpty(hostIP)) - if (split(hostIP, out string destIP)) - return destIP; - } - } - - return destHostname; - - bool split(string hostIP, out string destIP) - { - // Add Support Comment // - if (hostIP.StartsWith("//")) - { - destIP = destHostname; return false; - } - else - { - try - { - if (hostIP.Contains('|')) - { - string[] split = hostIP.Split('|'); - string host = split[0].Trim(); - if (host.StartsWith("www.")) host = host.TrimStart("www."); - string ip = split[1].Trim(); // IP or Fake SNI - - if (!host.StartsWith("*.")) - { - // No Wildcard - if (host.Equals(destHostnameNoWWW)) - { - destIP = ip; return true; - } - } - else - { - // Wildcard - host = host[2..]; - string hostWithDot = $".{host}"; - - if (!destHostnameNoWWW.Equals(host) && destHostnameNoWWW.EndsWith(hostWithDot)) - { - destIP = ip; - return true; - } - } - } - } - catch (Exception) { } - - destIP = destHostname; return false; - } - } - } - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/FakeSni.cs b/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/FakeSni.cs deleted file mode 100644 index 18c22e3..0000000 --- a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/FakeSni.cs +++ /dev/null @@ -1,134 +0,0 @@ -namespace MsmhToolsClass.ProxyServerPrograms; - -public partial class ProxyProgram -{ - public class FakeSni - { - public enum Mode - { - File, - Text, - Disable - } - - public Mode FakeSniMode { get; private set; } = Mode.Disable; - public string PathOrText { get; private set; } = string.Empty; - public string TextContent { get; private set; } = string.Empty; - private List Host_Sni_List { get; set; } = new(); - - public FakeSni() { } - - /// - /// Set Fake DNS Database - /// - /// Mode - /// e.g. Each line: dns.google.com|8.8.8.8 - public void Set(Mode mode, string filePathOrText) - { - FakeSniMode = mode; - PathOrText = filePathOrText; - - if (FakeSniMode == Mode.Disable) return; - - if (FakeSniMode == Mode.File) - { - try - { - TextContent = File.ReadAllText(Path.GetFullPath(filePathOrText)); - } - catch (Exception) { } - } - else if (FakeSniMode == Mode.Text) - TextContent = filePathOrText; - - if (!string.IsNullOrEmpty(TextContent) || !string.IsNullOrWhiteSpace(TextContent)) - { - TextContent += Environment.NewLine; - Host_Sni_List = TextContent.SplitToLines(); - } - } - - public string Get(string destHostname) - { - string destHostnameNoWWW = destHostname; - if (destHostnameNoWWW.StartsWith("www.")) - destHostnameNoWWW = destHostnameNoWWW.TrimStart("www."); - - if (destHostnameNoWWW.EndsWith('/')) destHostnameNoWWW = destHostnameNoWWW[0..^1]; - - if (Host_Sni_List.Any()) - { - for (int n = 0; n < Host_Sni_List.Count; n++) - { - string hostSni = Host_Sni_List[n].Trim(); - if (!string.IsNullOrEmpty(hostSni)) - if (split(hostSni, out string destSni)) - return destSni; - } - } - - return destHostname; - - bool split(string hostSni, out string destSni) - { - // Add Support Comment // - if (hostSni.StartsWith("//")) - { - destSni = destHostname; return false; - } - else - { - try - { - if (hostSni.Contains('|')) - { - string[] split = hostSni.Split('|'); - string host = split[0].Trim(); - if (host.StartsWith("www.")) host = host.TrimStart("www."); - string sni = split[1].Trim(); // IP or Fake SNI - - if (string.IsNullOrEmpty(sni)) - { - destSni = destHostname; return false; - } - - if (!host.StartsWith("*.")) - { - // No Wildcard - if (host.Equals(destHostnameNoWWW)) - { - destSni = sni; return true; - } - } - else - { - // Wildcard - host = host[2..]; - string hostWithDot = $".{host}"; - - if (!destHostnameNoWWW.Equals(host) && destHostnameNoWWW.EndsWith(hostWithDot)) - { - if (!sni.StartsWith("*.")) - { - destSni = sni; - return true; - } - else - { - // Support: xxxx.example.com -> xxxx.domain.com - string ipOrSniWithDot = sni[1..]; - destSni = destHostnameNoWWW.Replace(hostWithDot, ipOrSniWithDot); - return true; - } - } - } - } - } - catch (Exception) { } - - destSni = destHostname; return false; - } - } - } - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Fragment.cs b/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Fragment.cs deleted file mode 100644 index ea594e0..0000000 --- a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Fragment.cs +++ /dev/null @@ -1,549 +0,0 @@ -using System.Diagnostics; -using System.Net.Sockets; - -namespace MsmhToolsClass.ProxyServerPrograms; - -public partial class ProxyProgram -{ - public class Fragment - { - public Mode FragmentMode { get; private set; } = Mode.Disable; - public ChunkMode DPIChunkMode { get; private set; } - public int BeforeSniChunks { get; private set; } = 0; - public int SniChunks { get; private set; } = 0; - public int AntiPatternOffset { get; private set; } = 2; - public int FragmentDelay { get; private set; } = 0; - public string? DestHostname { get; set; } - public int DestPort { get; set; } - - public event EventHandler? OnChunkDetailsReceived; - - public Fragment() { } - - public void Set(Mode mode, int beforeSniChunks, ChunkMode chunkMode, int sniChunks, int antiPatternOffset, int fragmentDelay) - { - FragmentMode = mode; - BeforeSniChunks = beforeSniChunks; - DPIChunkMode = chunkMode; - SniChunks = sniChunks; - AntiPatternOffset = antiPatternOffset; - FragmentDelay = fragmentDelay; - } - - // Max Data Length = 65536 - private const int MaxDataLength = 65536; - public enum Mode - { - Program, - Disable - } - - public enum ChunkMode - { - SNI, - SniExtension, - AllExtensions - } - - public class ProgramMode - { - private byte[] Data { get; set; } - private Socket Socket { get; set; } - - public ProgramMode(byte[] data, Socket socket) - { - Data = data; - Socket = socket; - } - - public void Send(Fragment bp) - { - int offset = bp.AntiPatternOffset; - Random random = new(); - - // Anti Pattern Fragment Chunks - int beforeSniChunks = random.Next(bp.BeforeSniChunks - offset, bp.BeforeSniChunks + offset); - if (beforeSniChunks <= 0) beforeSniChunks = 1; - if (beforeSniChunks > Data.Length) beforeSniChunks = Data.Length; - - int sniChunks = random.Next(bp.SniChunks - offset, bp.SniChunks + offset); - if (sniChunks <= 0) sniChunks = 1; - if (sniChunks > Data.Length) sniChunks = Data.Length; - - //Test(Data, Socket, beforeSniChunks, sniChunks, offset, bp); - - if (bp.DPIChunkMode == ChunkMode.AllExtensions) - SendDataInFragmentAllExtensions(Data, Socket, beforeSniChunks, sniChunks, offset, bp); - else if (bp.DPIChunkMode == ChunkMode.SniExtension) - SendDataInFragmentSniExtension(Data, Socket, beforeSniChunks, sniChunks, offset, bp); - else if (bp.DPIChunkMode == ChunkMode.SNI) - SendDataInFragmentSNI(Data, Socket, beforeSniChunks, sniChunks, offset, bp); - } - - private void Test(byte[] data, Socket socket, int beforeSniChunks, int sniChunks, int offset, Fragment bp) - { - Debug.WriteLine("Send Data in TEST"); - // Create packets - List packets = new(); - packets.Clear(); - - SniModifire sniModifire = new(data); - if (sniModifire.HasSni) - { - packets.Add(sniModifire.ModifiedData); - SendPackets(sniModifire.ModifiedData, socket, bp, packets); - } - else - { - packets.Add(data); - SendPackets(data, socket, bp, packets); - } - - } - - private void SendDataInFragmentAllExtensions(byte[] data, Socket socket, int beforeSniChunks, int sniChunks, int offset, Fragment bp) - { - //Debug.WriteLine("SendDataInFragmentAllExtensions"); - // Create packets - List packets = new(); - packets.Clear(); - - SniReader sniReader = new(data); - - if (beforeSniChunks == 1 && sniChunks == 1) - { - packets.Add(data); - } - else - { - if (sniReader.HasTlsExtensions) - { - int prevIndex; - int pos = 0; - SniReader.TlsExtensions allExtensions = sniReader.AllExtensions; - - pos += allExtensions.StartIndex; - prevIndex = pos - allExtensions.StartIndex; - - // Create packet before SNI - int beforeSniLength = allExtensions.StartIndex - prevIndex; - if (beforeSniLength > 0) - { - byte[] beforeSNI = new byte[beforeSniLength]; - Buffer.BlockCopy(data, prevIndex, beforeSNI, 0, beforeSniLength); - - List chunkedbeforeSNI = ChunkDataNormal(beforeSNI, beforeSniChunks, offset); - packets = packets.Concat(chunkedbeforeSNI).ToList(); - //Debug.WriteLine($"{prevIndex} ======> {beforeSniLength}"); - } - - // Create SNI packet - List chunkedSNI = ChunkDataNormal(allExtensions.Data, sniChunks, offset); - packets = packets.Concat(chunkedSNI).ToList(); - - //Debug.WriteLine($"{beforeSniLength} ====== {sni.SniStartIndex}"); - //Debug.WriteLine($"{sni.SniStartIndex} ======> {sni.SniStartIndex + sni.SniLength}"); - Debug.WriteLine("==-----== " + (sniReader.AllExtensions.StartIndex + sniReader.AllExtensions.Length) + " of " + data.Length); - pos = allExtensions.StartIndex + allExtensions.Length; - - // Create packet after SNI - if (pos < data.Length) - { - int afterSniStartIndex = pos; - int afterSniLength = data.Length - pos; - byte[] afterSni = new byte[afterSniLength]; - Buffer.BlockCopy(data, afterSniStartIndex, afterSni, 0, afterSniLength); - packets.Add(afterSni); - - //Debug.WriteLine($"{sni.SniStartIndex + sni.SniLength} ====== {afterSniStartIndex}"); - //Debug.WriteLine($"{afterSniStartIndex} ======> {afterSniStartIndex + afterSniLength}"); - //Debug.WriteLine($"{afterSniStartIndex + afterSniLength} ====== {data.Length}"); - } - } - else - { - packets.Add(data); - } - } - - SendPackets(data, socket, bp, packets); - } - - private void SendDataInFragmentSniExtension(byte[] data, Socket socket, int beforeSniChunks, int sniChunks, int offset, Fragment bp) - { - //Debug.WriteLine("SendDataInFragmentSniExtension"); - // Create packets - List packets = new(); - packets.Clear(); - - SniReader sniReader = new(data); - if (sniReader.SniExtensionList.Count > 1) Debug.WriteLine($"=======================> We Have {sniReader.SniExtensionList.Count} SNI Extensions."); - - if (beforeSniChunks == 1 && sniChunks == 1) - { - packets.Add(data); - } - else - { - if (sniReader.HasSniExtension) - { - int prevIndex; - int pos = 0; - for (int n = 0; n < sniReader.SniExtensionList.Count; n++) - { - SniReader.SniExtension sniExtension = sniReader.SniExtensionList[n]; - - pos += sniExtension.StartIndex; - prevIndex = pos - sniExtension.StartIndex; - - // Create packet before SNI - int beforeSniLength = sniExtension.StartIndex - prevIndex; - if (beforeSniLength > 0) - { - byte[] beforeSNI = new byte[beforeSniLength]; - Buffer.BlockCopy(data, prevIndex, beforeSNI, 0, beforeSniLength); - - List chunkedbeforeSNI = ChunkDataNormal(beforeSNI, beforeSniChunks, offset); - packets = packets.Concat(chunkedbeforeSNI).ToList(); - //Debug.WriteLine($"{prevIndex} ======> {beforeSniLength}"); - } - - // Create SNI packet - List chunkedSNI = ChunkDataNormal(sniExtension.Data, sniChunks, offset); - packets = packets.Concat(chunkedSNI).ToList(); - - //Debug.WriteLine($"{beforeSniLength} ====== {sni.SniStartIndex}"); - //Debug.WriteLine($"{sni.SniStartIndex} ======> {sni.SniStartIndex + sni.SniLength}"); - - pos = sniExtension.StartIndex + sniExtension.Length; - - // Last round - if (n == sniReader.SniExtensionList.Count - 1) - { - // Create packet after SNI - if (pos < data.Length) - { - int afterSniStartIndex = pos; - int afterSniLength = data.Length - pos; - byte[] afterSni = new byte[afterSniLength]; - Buffer.BlockCopy(data, afterSniStartIndex, afterSni, 0, afterSniLength); - packets.Add(afterSni); - - //Debug.WriteLine($"{sni.SniStartIndex + sni.SniLength} ====== {afterSniStartIndex}"); - //Debug.WriteLine($"{afterSniStartIndex} ======> {afterSniStartIndex + afterSniLength}"); - //Debug.WriteLine($"{afterSniStartIndex + afterSniLength} ====== {data.Length}"); - } - } - } - } - else - { - packets.Add(data); - } - } - - SendPackets(data, socket, bp, packets); - } - - private void SendDataInFragmentSNI(byte[] data, Socket socket, int beforeSniChunks, int sniChunks, int offset, Fragment bp) - { - //Debug.WriteLine("SendDataInFragmentSNI"); - // Create packets - List packets = new(); - packets.Clear(); - - SniReader sniReader = new(data); - if (sniReader.SniList.Count > 1) Debug.WriteLine($"=======================> We Have {sniReader.SniList.Count} SNIs."); - - if (beforeSniChunks == 1 && sniChunks == 1) - { - packets.Add(data); - } - else - { - if (sniReader.HasSni) - { - int prevIndex; - int pos = 0; - for (int n = 0; n < sniReader.SniList.Count; n++) - { - SniReader.SNI sni = sniReader.SniList[n]; - - pos += sni.StartIndex; - prevIndex = pos - sni.StartIndex; - - // Create packet before SNI - int beforeSniLength = sni.StartIndex - prevIndex; - if (beforeSniLength > 0) - { - byte[] beforeSNI = new byte[beforeSniLength]; - Buffer.BlockCopy(data, prevIndex, beforeSNI, 0, beforeSniLength); - - List chunkedbeforeSNI = ChunkDataNormal(beforeSNI, beforeSniChunks, offset); - packets = packets.Concat(chunkedbeforeSNI).ToList(); - //Debug.WriteLine($"{prevIndex} ======> {beforeSniLength}"); - } - - // Create SNI packet - List chunkedSNI = ChunkDataNormal(sni.Data, sniChunks, offset); - packets = packets.Concat(chunkedSNI).ToList(); - - //Debug.WriteLine($"{beforeSniLength} ====== {sni.SniStartIndex}"); - //Debug.WriteLine($"{sni.SniStartIndex} ======> {sni.SniStartIndex + sni.SniLength}"); - - pos = sni.StartIndex + sni.Length; - - // Last round - if (n == sniReader.SniList.Count - 1) - { - // Create packet after SNI - if (pos < data.Length) - { - int afterSniStartIndex = pos; - int afterSniLength = data.Length - pos; - byte[] afterSni = new byte[afterSniLength]; - Buffer.BlockCopy(data, afterSniStartIndex, afterSni, 0, afterSniLength); - packets.Add(afterSni); - - //Debug.WriteLine($"{sni.SniStartIndex + sni.SniLength} ====== {afterSniStartIndex}"); - //Debug.WriteLine($"{afterSniStartIndex} ======> {afterSniStartIndex + afterSniLength}"); - //Debug.WriteLine($"{afterSniStartIndex + afterSniLength} ====== {data.Length}"); - } - } - } - } - else - { - packets.Add(data); - } - } - - SendPackets(data, socket, bp, packets); - } - - private List ChunkDataNormal(byte[] data, int chunks, int offset) - { - //Debug.WriteLine("ChunkDataNormal"); - // Create chunk packets - Random random = new(); - List chunkPackets = new(); - chunkPackets.Clear(); - int prevIndex; - int nn = 0; - int sum = 0; - for (int n = 0; n < data.Length; n++) - { - try - { - // Anti Pattern Fragment Size - int fragmentSize = data.Length / chunks; - - int fragmentSizeOut = random.Next(fragmentSize - offset, fragmentSize + offset); - if (fragmentSizeOut <= 0) fragmentSizeOut = 1; - if (fragmentSizeOut > data.Length) fragmentSizeOut = data.Length; - nn += fragmentSizeOut; - - if (nn > data.Length) - { - fragmentSizeOut = data.Length - (nn - fragmentSizeOut); - //Debug.WriteLine(fragmentSizeOut); - } - //Debug.WriteLine(fragmentSizeOut); - - sum += fragmentSizeOut; - byte[] fragmentData = new byte[fragmentSizeOut]; - prevIndex = sum - fragmentSizeOut; - Buffer.BlockCopy(data, prevIndex, fragmentData, 0, fragmentSizeOut); - chunkPackets.Add(fragmentData); - - if (sum >= data.Length) break; - } - catch (Exception ex) - { - chunkPackets.Clear(); - string msgEvent = $"Error, Creating normal packets: {ex.Message}"; - Debug.WriteLine(msgEvent); - return chunkPackets; - } - } - - return chunkPackets; - } - - private List ChunkDataNormal2(byte[] data, int fragmentSize) - { - Debug.WriteLine("ChunkDataNormal2"); - // Create chunk packets - List chunkPackets = new(); - chunkPackets.Clear(); - var fragments = data.Chunk(fragmentSize); - for (int n = 0; n < fragments.Count(); n++) - { - try - { - byte[] fragment = fragments.ToArray()[n]; - chunkPackets.Add(fragment); - } - catch (Exception ex) - { - chunkPackets.Clear(); - string msgEvent = $"Error, Creating normal2 packets: {ex.Message}"; - Debug.WriteLine(msgEvent); - return chunkPackets; - } - } - - return chunkPackets; - } - - private List ChunkDataNormal3(byte[] data, int fragmentSize) - { - Debug.WriteLine("ChunkDataNormal3"); - // Create chunk packets - List chunkPackets = new(); - chunkPackets.Clear(); - var fragments = ChunkViaMemory(data, fragmentSize); - for (int n = 0; n < fragments.Count(); n++) - { - try - { - byte[] fragment = fragments.ToArray()[n].ToArray(); - chunkPackets.Add(fragment); - } - catch (Exception ex) - { - chunkPackets.Clear(); - string msgEvent = $"Error, Creating normal3 packets: {ex.Message}"; - Debug.WriteLine(msgEvent); - return chunkPackets; - } - } - - return chunkPackets; - } - - private List ChunkDataRandom(byte[] data, int fragmentChunks) - { - Debug.WriteLine("ChunkDataRandom"); - //// Calculate fragment chunks from size - //int fragmentChunks = data.Length / fragmentSize; - //if (fragmentChunks <= 0) fragmentChunks = 1; - //if (fragmentChunks > data.Length) fragmentChunks = data.Length; - - // Create chunk packets - List packets = new(); - packets.Clear(); - fragmentChunks = Math.Min(fragmentChunks, data.Length); - List indices; - if (fragmentChunks < data.Length) - indices = GenerateRandomIndices(1, data.Length - 1, fragmentChunks - 1); - else - indices = Enumerable.Range(0, data.Length - 1).ToList(); - indices.Sort(); - - int prevIndex = 0; - for (int n = 0; n < indices.Count; n++) - { - try - { - int index = indices[n]; - byte[] fragmentData = new byte[index - prevIndex]; - Buffer.BlockCopy(data, prevIndex, fragmentData, 0, fragmentData.Length); - packets.Add(fragmentData); - prevIndex = index; - } - catch (Exception ex) - { - packets.Clear(); - string msgEvent = $"Error, Creating random packets: {ex.Message}"; - Debug.WriteLine(msgEvent); - return packets; - } - } - - try - { - byte[] lastFragmentData = new byte[data.Length - prevIndex]; - Buffer.BlockCopy(data, prevIndex, lastFragmentData, 0, lastFragmentData.Length); - packets.Add(lastFragmentData); - } - catch (Exception ex) - { - packets.Clear(); - string msgEvent = $"Error, Creating last random packet: {ex.Message}"; - Debug.WriteLine(msgEvent); - return packets; - } - - return packets; - } - - private void SendPackets(byte[] data, Socket socket, Fragment bp, List packets) - { - // Check packets - int allLength = 0; - for (int i = 0; i < packets.Count; i++) - allLength += packets[i].Length; - - if (allLength != data.Length) - { - Debug.WriteLine($"{allLength} == {data.Length}, Chunks: {packets.Count}"); - packets.Clear(); - return; - } - - // Send packets - for (int i = 0; i < packets.Count; i++) - { - try - { - byte[] fragmentData = packets[i]; - if (socket == null) return; - socket.Send(fragmentData); - if (bp.FragmentDelay > 0) - Task.Delay(bp.FragmentDelay).Wait(); - } - catch (Exception ex) - { - string msgEvent = $"Error, Send Packets: {ex.Message}"; - Debug.WriteLine(msgEvent); - return; - } - } - - string chunkDetailsEvent = $"{bp.DestHostname}:{bp.DestPort} Length: {data.Length}"; - if (packets.Count > 1) - chunkDetailsEvent += $", Chunks: {packets.Count}"; - bp.OnChunkDetailsReceived?.Invoke(chunkDetailsEvent, EventArgs.Empty); - } - } - - private static List GenerateRandomIndices(int minValue, int maxValue, int count) - { - Random random = new(); - HashSet indicesSet = new(); - - while (indicesSet.Count < count) - { - indicesSet.Add(random.Next(minValue, maxValue)); - } - - return new List(indicesSet); - } - - private static IEnumerable> ChunkViaMemory(T[] data, int size) - { - var chunks = data.Length / size; - for (int i = 0; i < chunks; i++) - { - yield return data.AsMemory(i * size, size); - } - var leftOver = data.Length % size; - if (leftOver > 0) - { - yield return data.AsMemory(chunks * size, leftOver); - } - } - - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Rules.cs b/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Rules.cs deleted file mode 100644 index fa37858..0000000 --- a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Rules.cs +++ /dev/null @@ -1,433 +0,0 @@ -using System.Diagnostics; - -namespace MsmhToolsClass.ProxyServerPrograms; - -public partial class ProxyProgram -{ - public partial class Rules - { - public enum Mode - { - File, - Text, - Disable - } - - public Mode RulesMode { get; private set; } = Mode.Disable; - public string PathOrText { get; private set; } = string.Empty; - public string TextContent { get; private set; } = string.Empty; - - private List Rules_List { get; set; } = new(); - private List> Variables { get; set; } = new(); // x = domain.com; - private List Default_BlockPort { get; set; } = new(); // blockport:80,53; - private List Default_Dnss { get; set; } = new(); // dns: - private string Default_DnsDomain { get; set; } = string.Empty; // dnsdomain:; - private string Default_DnsProxyScheme { get; set; } = string.Empty; // dnsproxy:; - private string Default_DnsProxyUser { get; set; } = string.Empty; // &user: - private string Default_DnsProxyPass { get; set; } = string.Empty; // &pass: - private string Default_Sni { get; set; } = string.Empty; // sni:; - private string Default_ProxyScheme { get; set; } = string.Empty; // proxy:; - private bool Default_ProxyIfBlock { get; set; } = false; // &ifblock:1 - private string Default_ProxyUser { get; set; } = string.Empty; // &user: - private string Default_ProxyPass { get; set; } = string.Empty; // &pass: - private List MainRules_List { get; set; } = new(); - - private readonly struct KEYS - { - public static readonly string FirstKey = "FirstKey"; - public static readonly string AllClients = "AllClients"; - public static readonly string BlockPort = "blockport:"; - public static readonly string Dns = "dns:"; - public static readonly string DnsDomain = "dnsdomain:"; - public static readonly string DnsProxy = "dnsproxy:"; - public static readonly string Sni = "sni:"; - public static readonly string Proxy = "proxy:"; - } - - private readonly struct SUB_KEYS - { - public static readonly string FirstKey = "FirstKey"; - public static readonly string IfBlock = "&ifblock:"; - public static readonly string User = "&user:"; - public static readonly string Pass = "&pass:"; - } - - private class MainRules - { - public string Client { get; set; } = string.Empty; - public string Domain { get; set; } = string.Empty; - public bool IsBlock { get; set; } = false; - public List BlockPort { get; set; } = new(); - public bool NoBypass { get; set; } = false; - public string FakeDns { get; set; } = string.Empty; - public List Dnss { get; set; } = new(); - public string DnsDomain { get; set; } = string.Empty; - public string DnsProxyScheme { get; set; } = string.Empty; - public string DnsProxyUser { get; set; } = string.Empty; - public string DnsProxyPass { get; set; } = string.Empty; - public string Sni { get; set; } = string.Empty; - public string ProxyScheme { get; set; } = string.Empty; - public bool ProxyIfBlock { get; set; } = false; - public string ProxyUser { get; set; } = string.Empty; - public string ProxyPass { get; set; } = string.Empty; - } - - public Rules() { } - - public void Set(Mode mode, string filePathOrText) - { - Rules_List.Clear(); - Variables.Clear(); - Default_BlockPort.Clear(); - Default_Dnss.Clear(); - Default_DnsDomain = string.Empty; - Default_DnsProxyScheme = string.Empty; - Default_DnsProxyUser = string.Empty; - Default_DnsProxyPass = string.Empty; - Default_Sni = string.Empty; - Default_ProxyScheme = string.Empty; - Default_ProxyIfBlock = false; - Default_ProxyUser = string.Empty; - Default_ProxyPass = string.Empty; - MainRules_List.Clear(); - - RulesMode = mode; - PathOrText = filePathOrText; - - if (RulesMode == Mode.Disable) return; - - if (RulesMode == Mode.File) - { - try - { - TextContent = File.ReadAllText(Path.GetFullPath(filePathOrText)); - } - catch (Exception) { } - } - else if (RulesMode == Mode.Text) TextContent = filePathOrText; - - if (string.IsNullOrEmpty(TextContent) || string.IsNullOrWhiteSpace(TextContent)) return; - - TextContent += Environment.NewLine; - Rules_List = TextContent.SplitToLines(); - - for (int n = 0; n < Rules_List.Count; n++) - { - string line = Rules_List[n].Trim(); - if (line.StartsWith("//")) continue; // Support Comment // - if (!line.EndsWith(';')) continue; // Must Have ; At The End - if (string.IsNullOrEmpty(line) || string.IsNullOrWhiteSpace(line)) continue; // Line Cannot Be Empty - - // Get Variables - if (line.Contains('=') && !line.Contains(',') && !line.Contains('&')) - { - line = line.TrimEnd(';'); - string[] split = line.Split('='); - if (split.Length == 2) - { - string item1 = split[0].Trim(); - string item2 = split[1].Trim(); - if (!string.IsNullOrEmpty(item1) && !string.IsNullOrEmpty(item2)) - Variables.Add(new Tuple(item1, item2)); - } - } - - // Get Defaults - else if (line.StartsWith(KEYS.BlockPort, StringComparison.InvariantCultureIgnoreCase)) - { - string ports = GetValue(line, KEYS.BlockPort, null, out bool isList, out List list); - if (!isList) // One Port - { - bool success = int.TryParse(ports, out int port); - if (success) Default_BlockPort.Add(port); - } - else // Multiple Ports - { - for (int i = 0; i < list.Count; i++) - { - string portStr = list[i]; - bool success = int.TryParse(portStr, out int port); - if (success) Default_BlockPort.Add(port); - } - } - continue; - } - else if (line.StartsWith(KEYS.Dns, StringComparison.InvariantCultureIgnoreCase)) - { - string dnss = GetValue(line, KEYS.Dns, null, out bool isList, out List list); - if (!isList) // One Dns - { - if (!string.IsNullOrEmpty(dnss)) - Default_Dnss.Add(dnss); - } - else // Multiple Dnss - { - for (int i = 0; i < list.Count; i++) - { - string dns = list[i]; - if (!string.IsNullOrEmpty(dns)) - Default_Dnss.Add(dns); - } - } - } - else if (line.StartsWith(KEYS.DnsDomain, StringComparison.InvariantCultureIgnoreCase)) - { - Default_DnsDomain = GetValue(line, KEYS.DnsDomain, null, out _, out _); - } - else if (line.StartsWith(KEYS.DnsProxy, StringComparison.InvariantCultureIgnoreCase)) - { - Default_DnsProxyScheme = GetValue(line, KEYS.DnsProxy, SUB_KEYS.FirstKey, out _, out _); - Default_DnsProxyUser = GetValue(line, KEYS.DnsProxy, SUB_KEYS.User, out _, out _); - Default_DnsProxyPass = GetValue(line, KEYS.DnsProxy, SUB_KEYS.Pass, out _, out _); - } - else if (line.StartsWith(KEYS.Sni, StringComparison.InvariantCultureIgnoreCase)) - { - Default_Sni = GetValue(line, KEYS.Sni, null, out _, out _); - } - else if (line.StartsWith(KEYS.Proxy, StringComparison.InvariantCultureIgnoreCase)) - { - Default_ProxyScheme = GetValue(line, KEYS.Proxy, SUB_KEYS.FirstKey, out _, out _); - string ifBlock = GetValue(line, KEYS.Proxy, SUB_KEYS.IfBlock, out _, out _).ToLower().Trim(); - if (!string.IsNullOrEmpty(ifBlock)) - Default_ProxyIfBlock = ifBlock.Equals("1") || ifBlock.Equals("true"); - Default_ProxyUser = GetValue(line, KEYS.Proxy, SUB_KEYS.User, out _, out _); - Default_ProxyPass = GetValue(line, KEYS.Proxy, SUB_KEYS.Pass, out _, out _); - } - - // Get MainRules (Client|Domain|Rules) - else if (line.Contains('|')) - { - string[] split = line.Split('|'); - if (split.Length == 2) SetDomainRules(KEYS.AllClients, split[0].Trim(), split[1].Trim()); - if (split.Length == 3) SetDomainRules(split[0].Trim(), split[1].Trim(), split[2].Trim()); - } - } - } - - private void SetDomainRules(string client, string domain, string rules) // rules Ends With ; - { - try - { - MainRules mr = new(); - mr.Client = client; // Client - mr.Domain = domain; // Domain - - // Block - if (rules.Equals("block;", StringComparison.InvariantCultureIgnoreCase)) mr.IsBlock = true; - if (rules.Equals("-;")) mr.IsBlock = true; - - if (!mr.IsBlock) - { - // No Bypass - if (rules.Contains("nobypass;", StringComparison.InvariantCultureIgnoreCase)) mr.NoBypass = true; - if (rules.Contains("--;")) mr.NoBypass = true; - - // Fake DNS - string fakeDnsIpStr = GetValue(rules, KEYS.FirstKey, null, out _, out _); - bool isIp = NetworkTool.IsIp(fakeDnsIpStr, out _); - if (isIp) mr.FakeDns = fakeDnsIpStr; - - // BlockPort - string ports = GetValue(rules, KEYS.BlockPort, null, out bool isList, out List list); - if (!isList) // One Port - { - bool success = int.TryParse(ports, out int port); - if (success) mr.BlockPort.Add(port); - } - else // Multiple Ports - { - for (int i = 0; i < list.Count; i++) - { - string portStr = list[i]; - bool success = int.TryParse(portStr, out int port); - if (success) mr.BlockPort.Add(port); - } - } - if (Default_BlockPort.Any()) - { - try - { - mr.BlockPort = mr.BlockPort.Concat(Default_BlockPort).ToList(); - mr.BlockPort = mr.BlockPort.Distinct().ToList(); - } - catch (Exception) { } - } - - // Dnss - string dnss = GetValue(rules, KEYS.Dns, null, out isList, out list); - if (!isList) // One Dns - { - if (!string.IsNullOrEmpty(dnss)) - mr.Dnss.Add(dnss); - } - else // Multiple Dnss - { - for (int i = 0; i < list.Count; i++) - { - string dns = list[i]; - if (!string.IsNullOrEmpty(dns)) - mr.Dnss.Add(dns); - } - } - if (Default_Dnss.Any()) - { - try - { - mr.Dnss = mr.Dnss.Concat(Default_Dnss).ToList(); - mr.Dnss = mr.Dnss.Distinct().ToList(); - } - catch (Exception) { } - } - - // DnsDomain - mr.DnsDomain = GetValue(rules, KEYS.DnsDomain, null, out _, out _); - if (string.IsNullOrEmpty(mr.DnsDomain)) mr.DnsDomain = Default_DnsDomain; - - // DnsProxy e.g. socks5://127.0.0.1:6666&user:UserName&pass:PassWord - mr.DnsProxyScheme = GetValue(rules, KEYS.DnsProxy, SUB_KEYS.FirstKey, out _, out _); - mr.DnsProxyUser = GetValue(rules, KEYS.DnsProxy, SUB_KEYS.User, out _, out _); - mr.DnsProxyPass = GetValue(rules, KEYS.DnsProxy, SUB_KEYS.Pass, out _, out _); - if (string.IsNullOrEmpty(mr.DnsProxyScheme)) - { - mr.DnsProxyScheme = Default_DnsProxyScheme; - mr.DnsProxyUser = Default_DnsProxyUser; - mr.DnsProxyPass = Default_DnsProxyPass; - } - - // SNI - mr.Sni = GetValue(rules, KEYS.Sni, null, out _, out _); - if (string.IsNullOrEmpty(mr.Sni)) mr.Sni = Default_Sni; - - // Proxy e.g. socks5://127.0.0.1:6666&ifblock:1&user:UserName&pass:PassWord - mr.ProxyScheme = GetValue(rules, KEYS.Proxy, SUB_KEYS.FirstKey, out _, out _); - string ifBlock = GetValue(rules, KEYS.Proxy, SUB_KEYS.IfBlock, out _, out _).ToLower().Trim(); - if (!string.IsNullOrEmpty(ifBlock)) - mr.ProxyIfBlock = ifBlock.Equals("1") || ifBlock.Equals("true"); - mr.ProxyUser = GetValue(rules, KEYS.Proxy, SUB_KEYS.User, out _, out _); - mr.ProxyPass = GetValue(rules, KEYS.Proxy, SUB_KEYS.Pass, out _, out _); - if (string.IsNullOrEmpty(mr.ProxyScheme)) - { - mr.ProxyScheme = Default_ProxyScheme; - mr.ProxyIfBlock = Default_ProxyIfBlock; - mr.ProxyUser = Default_ProxyUser; - mr.ProxyPass = Default_ProxyPass; - } - } - - MainRules_List.Add(mr); - } - catch (Exception ex) - { - Debug.WriteLine("Proxy Rules_SetDomainRules: " + ex.Message); - } - } - - private string GetValue(string line, string key, string? subKey, out bool isList, out List list) - { - string result = line.Trim(); - isList = false; - list = new(); - - try - { - if (result.Contains(key, StringComparison.InvariantCultureIgnoreCase) || key == KEYS.FirstKey) - { - try - { - if (key == KEYS.FirstKey) - { - result = result.Remove(result.IndexOf(';')); - result = result.Trim(); - } - else - { - result = result.Remove(0, result.IndexOf(key, StringComparison.InvariantCultureIgnoreCase) + key.Length); - result = result.Remove(result.IndexOf(';')); - result = result.Trim(); - } - } - catch (Exception) { } - - if (!string.IsNullOrEmpty(subKey)) - { - if (subKey.Equals(SUB_KEYS.FirstKey, StringComparison.InvariantCultureIgnoreCase)) - { - if (result.Contains('&')) - { - try - { - result = result.Remove(result.IndexOf('&')); - } - catch (Exception) { } - } - } - else - { - if (result.Contains(subKey, StringComparison.InvariantCultureIgnoreCase)) - { - try - { - result = result.Remove(0, result.IndexOf(subKey, StringComparison.InvariantCultureIgnoreCase) + subKey.Length); - } - catch (Exception) { } - - if (result.Contains('&') && result.Contains(':')) - { - try - { - result = result.Remove(result.IndexOf('&')); - } - catch (Exception) { } - } - } - } - } - - if (!result.Contains(',')) - { - // Not A List - return ApplyVariables(result); - } - else - { - // It's A List - isList = true; - string[] split = result.Split(','); - for (int n = 0; n < split.Length; n++) - { - string value = split[n].Trim(); - list.Add(ApplyVariables(value)); - } - if (list.Any()) return list[0]; - } - } - } - catch (Exception) { } - - return string.Empty; - } - - private string ApplyVariables(string vari) - { - string result = vari; - try - { - List> variables = Variables.ToList(); - for (int n = 0; n < variables.Count; n++) - { - Tuple tuple = variables[n]; - if (vari.Equals(tuple.Item1)) - { - result = tuple.Item2; break; - } - } - } - catch (Exception ex) - { - Debug.WriteLine("Proxy Rules_ApplyVariables: " + ex.Message); - } - return result; - } - - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Rules_ConnectToUpStream.cs b/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Rules_ConnectToUpStream.cs deleted file mode 100644 index 1cf77df..0000000 --- a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Rules_ConnectToUpStream.cs +++ /dev/null @@ -1,46 +0,0 @@ -using MsmhToolsClass.MsmhProxyServer; -using MsmhToolsClass.ProxifiedTcpClient; -using System.Diagnostics; -using System.Net.Sockets; - -namespace MsmhToolsClass.ProxyServerPrograms; - -public partial class ProxyProgram -{ - public partial class Rules - { - public async Task ConnectToUpStream(ProxyRequest req) - { - RulesResult rr = req.RulesResult; - string destHostname = req.Address; - int destHostPort = req.Port; - - if (!rr.ApplyUpStreamProxy) return null; - if (string.IsNullOrEmpty(rr.ProxyScheme)) return null; - - try - { - NetworkTool.GetUrlDetails(rr.ProxyScheme, 443, out _, out string proxyHost, out _, out _, out int proxyPort, out _, out _); - - if (rr.ProxyScheme.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) || - rr.ProxyScheme.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) - { - - HttpProxyClient httpProxyClient = new(proxyHost, proxyPort, rr.ProxyUser, rr.ProxyPass); - return await httpProxyClient.CreateConnection(destHostname, destHostPort); - } - else if (rr.ProxyScheme.StartsWith("socks5://", StringComparison.InvariantCultureIgnoreCase)) - { - Socks5ProxyClient socks5ProxyClient = new(proxyHost, proxyPort, rr.ProxyUser, rr.ProxyPass); - return await socks5ProxyClient.CreateConnection(destHostname, destHostPort); - } - else return null; - } - catch (Exception ex) - { - Debug.WriteLine("ConnectToUpstream: " + ex.Message); - return null; - } - } - } -} diff --git a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Rules_Get.cs b/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Rules_Get.cs deleted file mode 100644 index 4bff504..0000000 --- a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/Rules_Get.cs +++ /dev/null @@ -1,240 +0,0 @@ -using System.Diagnostics; - -namespace MsmhToolsClass.ProxyServerPrograms; - -public partial class ProxyProgram -{ - public partial class Rules - { - public class RulesResult - { - public bool IsMatch { get; set; } = false; - public bool IsBlackList { get; set; } = false; - public bool IsPortBlock { get; set; } = false; - public bool ApplyDpiBypass { get; set; } = true; - public string DnsServer { get; set; } = string.Empty; - public string DnsCustomDomain { get; set; } = string.Empty; - public string DnsProxyScheme { get; set; } = string.Empty; - public string Dns { get; set; } = string.Empty; - public string Sni { get; set; } = string.Empty; - public bool ApplyUpStreamProxy { get; set; } = false; - public string ProxyScheme { get; set; } = string.Empty; - public bool ApplyUpStreamProxyToBlockedIPs { get; set; } = false; - public string ProxyUser { get; set; } = string.Empty; - public string ProxyPass { get; set; } = string.Empty; - } - - public async Task GetAsync(string client, string host, int port) - { - RulesResult rr = new(); - if (string.IsNullOrEmpty(host)) return rr; - - try - { - rr.Dns = host; - - for (int n = 0; n < MainRules_List.Count; n++) - { - MainRules mr = MainRules_List[n]; - - // Check If Match - bool isClientMatch = !string.IsNullOrEmpty(mr.Client) && (mr.Client.Equals(KEYS.AllClients) || mr.Client.Equals(client)); - bool isDomainMatch = IsDomainMatch(host, mr.Domain, out bool isWildcard, out string hostNoWww, out string ruleHostNoWww); - bool isMatch = isClientMatch && isDomainMatch; - if (!isMatch) continue; - - // Set Match - rr.IsMatch = isMatch; - - // Is Black List - rr.IsBlackList = mr.IsBlock; - if (rr.IsBlackList) break; - - // Is Port Block - - List blockedPorts = mr.BlockPort.ToList(); - for (int i = 0; i < blockedPorts.Count; i++) - { - int blockedPort = blockedPorts[i]; - if (port == blockedPort) - { - rr.IsPortBlock = true; - break; - } - } - if (rr.IsPortBlock) break; - - // Apply DPI Bypass (Fragment & Change SNI) - rr.ApplyDpiBypass = !mr.NoBypass; - - // DNS - if (!string.IsNullOrEmpty(mr.FakeDns)) - { - // Fake DNS - rr.Dns = mr.FakeDns; - } - else - { - // Get IP By Custom DNS - if (mr.Dnss.Any() && !NetworkTool.IsIp(host, out _)) - { - // Get Custom DNS Domain - rr.DnsCustomDomain = host; - if (!string.IsNullOrEmpty(mr.DnsDomain)) - { - if (!mr.DnsDomain.StartsWith("*.")) - { - rr.DnsCustomDomain = mr.DnsDomain; - } - else - { - // Support: xxxx.example.com -> xxxx.domain.com - if (isWildcard) // ruleHostNoWww.StartsWith("*.") - { - if (hostNoWww.EndsWith(ruleHostNoWww[1..])) // Just In Case - { - rr.DnsCustomDomain = hostNoWww.Replace(ruleHostNoWww[1..], mr.DnsDomain[1..]); - } - } - } - } - - for (int i = 0; i < mr.Dnss.Count; i++) - { - string dns = mr.Dnss[i].ToLower().Trim(); - if (string.IsNullOrEmpty(dns)) continue; - - if (dns.Equals("system")) // System - { - rr.Dns = DnsTool.GetIP.GetIpFromSystem(rr.DnsCustomDomain); - } - else - { - DnsTool.DnsReader dnsReader = new(dns, null); - if (dnsReader.Protocol == DnsTool.DnsReader.DnsProtocol.PlainDNS) // Plain DNS - { - DnsTool.GetIP.PlainDnsProtocol plainDnsProtocol = DnsTool.GetIP.PlainDnsProtocol.Both; - if (dns.StartsWith("udp://")) plainDnsProtocol = DnsTool.GetIP.PlainDnsProtocol.UDP; - if (dns.StartsWith("tcp://")) plainDnsProtocol = DnsTool.GetIP.PlainDnsProtocol.TCP; - if (!string.IsNullOrEmpty(dnsReader.IP)) - rr.Dns = await DnsTool.GetIP.GetIpFromPlainDNS(rr.DnsCustomDomain, dnsReader.IP, dnsReader.Port, 3, plainDnsProtocol); - } - else if (dnsReader.Protocol == DnsTool.DnsReader.DnsProtocol.DoH) // DoH - { - bool dohHasProxy = false; - if (!string.IsNullOrEmpty(mr.DnsProxyScheme)) - { - mr.DnsProxyScheme = mr.DnsProxyScheme.ToLower().Trim(); - if (mr.DnsProxyScheme.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || - mr.DnsProxyScheme.StartsWith("https://", StringComparison.OrdinalIgnoreCase) || - mr.DnsProxyScheme.StartsWith("socks5://", StringComparison.OrdinalIgnoreCase)) - dohHasProxy = true; - } - - if (dohHasProxy) - { - rr.Dns = await DnsTool.GetIP.GetIpFromDohUsingWireFormat(rr.DnsCustomDomain, dns, 3, mr.DnsProxyScheme, mr.DnsProxyUser, mr.DnsProxyPass); - if (!string.IsNullOrEmpty(rr.Dns)) - rr.DnsProxyScheme = mr.DnsProxyScheme; - } - else - rr.Dns = await DnsTool.GetIP.GetIpFromDohUsingWireFormat(rr.DnsCustomDomain, dns, 3); - } - } - - // Break If We Have IP - if (!string.IsNullOrEmpty(rr.Dns)) - { - rr.DnsServer = dns; - break; - } - } - - // If All DNSs Failed To Get IP Set To Original Host - if (string.IsNullOrEmpty(rr.Dns)) rr.Dns = host; - } - } - - // SNI - rr.Sni = mr.Sni; - if (!string.IsNullOrEmpty(rr.Sni) && rr.Sni.StartsWith("*.")) - { - // Support: xxxx.example.com -> xxxx.domain.com - if (isWildcard) // ruleHostNoWww.StartsWith("*.") - { - if (hostNoWww.EndsWith(ruleHostNoWww[1..])) // Just In Case - { - rr.Sni = hostNoWww.Replace(ruleHostNoWww[1..], rr.Sni[1..]); - } - } - } - if (string.IsNullOrEmpty(rr.Sni)) rr.Sni = host; // Set SNI To Original Host If Not Defined - - // Upstream Proxy - if (!string.IsNullOrEmpty(mr.ProxyScheme)) - { - mr.ProxyScheme = mr.ProxyScheme.ToLower().Trim(); - if (mr.ProxyScheme.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || - mr.ProxyScheme.StartsWith("https://", StringComparison.OrdinalIgnoreCase) || - mr.ProxyScheme.StartsWith("socks5://", StringComparison.OrdinalIgnoreCase)) - { - rr.ApplyUpStreamProxy = true; - rr.ProxyScheme = mr.ProxyScheme; - rr.ApplyUpStreamProxyToBlockedIPs = mr.ProxyIfBlock; - rr.ProxyUser = mr.ProxyUser; - rr.ProxyPass = mr.ProxyPass; - } - } - - // Break If Match - break; - } - } - catch (Exception ex) - { - Debug.WriteLine("ProxyRules_GetAsync: " + ex.Message); - } - - return rr; - } - - private static bool IsDomainMatch(string host, string ruleHost, out bool isWildcard, out string hostNoWWW, out string ruleHostNoWWW) - { - isWildcard = false; - hostNoWWW = host.ToLower().Trim(); - ruleHostNoWWW = ruleHost.ToLower().Trim(); - - try - { - if (hostNoWWW.StartsWith("www.")) - hostNoWWW = hostNoWWW.TrimStart("www."); - if (hostNoWWW.EndsWith('/')) hostNoWWW = hostNoWWW[0..^1]; - - if (ruleHostNoWWW.StartsWith("www.")) - ruleHostNoWWW = ruleHostNoWWW.TrimStart("www."); - if (ruleHostNoWWW.EndsWith('/')) ruleHostNoWWW = ruleHostNoWWW[0..^1]; - - if (!string.IsNullOrEmpty(ruleHostNoWWW)) - { - if (ruleHostNoWWW.Equals("*")) return true; // No Wildcard - - if (!ruleHostNoWWW.StartsWith("*.")) - { - // No Wildcard - if (ruleHostNoWWW.Equals(hostNoWWW)) return true; - } - else - { - // Wildcard - isWildcard = true; - if (!hostNoWWW.Equals(ruleHostNoWWW[2..]) && hostNoWWW.EndsWith(ruleHostNoWWW[1..])) return true; - } - } - } - catch (Exception) { } - - return false; - } - - } -} diff --git a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/UpStreamProxy.cs b/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/UpStreamProxy.cs deleted file mode 100644 index 61311eb..0000000 --- a/MsmhToolsClass/MsmhToolsClass/ProxyServerPrograms/UpStreamProxy.cs +++ /dev/null @@ -1,80 +0,0 @@ -using MsmhToolsClass.ProxifiedTcpClient; -using System.Net; -using System.Net.Sockets; - -namespace MsmhToolsClass.ProxyServerPrograms; - -public partial class ProxyProgram -{ - public class UpStreamProxy - { - public enum Mode - { - HTTP, - SOCKS5, - Disable - } - - public Mode UpStreamMode { get; private set; } = Mode.Disable; - public string? ProxyHost { get; private set; } - public int ProxyPort { get; private set; } - public string? ProxyUsername { get; private set; } - public string? ProxyPassword { get; private set; } - public bool OnlyApplyToBlockedIps { get; set; } - - public UpStreamProxy() { } - - public void Set(Mode mode, string proxyHost, int proxyPort, bool onlyApplyToBlockedIps) - { - //Set - UpStreamMode = mode; - ProxyHost = proxyHost; - ProxyPort = proxyPort; - OnlyApplyToBlockedIps = onlyApplyToBlockedIps; - } - - public void Set(Mode mode, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, bool onlyApplyToBlockedIps) - { - //Set - UpStreamMode = mode; - ProxyHost = proxyHost; - ProxyPort = proxyPort; - ProxyUsername = proxyUsername; - ProxyPassword = proxyPassword; - OnlyApplyToBlockedIps = onlyApplyToBlockedIps; - } - - public async Task Connect(SocketAsyncEventArgs socketAsyncEventArgs) - { - if (string.IsNullOrEmpty(ProxyHost)) return null; - - if (socketAsyncEventArgs.RemoteEndPoint is not IPEndPoint ipEndPoint) return null; - - return await Connect(ipEndPoint.Address.ToString(), ipEndPoint.Port); - } - - /// - /// Connect TcpClient through Proxy - /// - /// - /// - /// Returns null if cannot connect to proxy - public async Task Connect(string destHostname, int destHostPort) - { - if (string.IsNullOrEmpty(ProxyHost)) return null; - - if (UpStreamMode == Mode.HTTP) - { - HttpProxyClient httpProxyClient = new(ProxyHost, ProxyPort, ProxyUsername, ProxyPassword); - return await httpProxyClient.CreateConnection(destHostname, destHostPort); - } - else if (UpStreamMode == Mode.SOCKS5) - { - Socks5ProxyClient socks5ProxyClient = new(ProxyHost, ProxyPort, ProxyUsername, ProxyPassword); - return await socks5ProxyClient.CreateConnection(destHostname, destHostPort); - } - else - return null; - } - } -} \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/SniReader.cs b/MsmhToolsClass/MsmhToolsClass/SniReader.cs index 6dc15e3..27a1932 100644 --- a/MsmhToolsClass/MsmhToolsClass/SniReader.cs +++ b/MsmhToolsClass/MsmhToolsClass/SniReader.cs @@ -1,5 +1,5 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; +using System.Security.Authentication; using System.Text; namespace MsmhToolsClass; @@ -30,6 +30,7 @@ public class SNI } public string ReasonPhrase { get; private set; } = string.Empty; + public SslProtocols SslProtocol { get; private set; } = SslProtocols.None; public bool HasTlsExtensions { get; private set; } = false; public TlsExtensions AllExtensions { get; private set; } = new(); public bool HasSniExtension { get; private set; } = false; @@ -37,271 +38,291 @@ public class SNI public bool HasSni { get; private set; } = false; public List SniList { get; private set; } = new(); - private readonly byte[] Data = Array.Empty(); private const int TLS_HEADER_LEN = 5; private const int TLS_HANDSHAKE_CONTENT_TYPE = 0x16; private const int TLS_HANDSHAKE_TYPE_CLIENT_HELLO = 0x01; public SniReader(byte[] data) { - Data = data; - - int pos = TLS_HEADER_LEN; - int dataLength = data.Length; - - if (dataLength < TLS_HEADER_LEN) + try { - ReasonPhrase = "TCP payload is not large enough for a TLS header."; - return; - } + int pos = TLS_HEADER_LEN; + int dataLength = data.Length; - // RECORD HEADER - if (data[0] == 1 & 0x80 == 1 && data[2] == 1) - { - ReasonPhrase = "Received SSL 2.0 Client Hello which can not support SNI."; - return; - } - else - { - if (data[0] != TLS_HANDSHAKE_CONTENT_TYPE) + if (dataLength < TLS_HEADER_LEN) { - ReasonPhrase = "Request did not begin with TLS handshake."; + ReasonPhrase = "TCP Payload Is Not Large Enough For A TLS Header."; return; } - int tls_version_major = data[1]; - int tls_version_minor = data[2]; - - if (tls_version_major < 3) + // RECORD HEADER + if (data[0] == 1 & 0x80 == 1 && data[2] == 1) { - ReasonPhrase = $"Received SSL handshake cannot support SNI. Min TLS: {tls_version_minor} Max TLS: {tls_version_major}"; + ReasonPhrase = "Received SSL 2.0 Client Hello Which Can Not Support SNI."; +#pragma warning disable CS0618 // Type or member is obsolete + SslProtocol = SslProtocols.Ssl2; +#pragma warning restore CS0618 // Type or member is obsolete return; } + else + { + if (data[0] != TLS_HANDSHAKE_CONTENT_TYPE) + { + ReasonPhrase = "Request Did Not Begin With TLS Handshake."; + return; + } - // TLS Record Length (Length of handshake message) (2 bytes length) - int len = (data[3] << 8) + data[4]; - dataLength = Math.Min(dataLength, len + TLS_HEADER_LEN); + int tls_version_major = data[1]; + int tls_version_minor = data[2]; - // Check we received entire TLS record length - if (dataLength < len + TLS_HEADER_LEN) - { - ReasonPhrase = "Didn't receive entire TLS record length."; - return; - } +#pragma warning disable CS0618 // Type or member is obsolete + if (tls_version_minor == 0 && tls_version_major == 3) SslProtocol = SslProtocols.Ssl3; +#pragma warning restore CS0618 // Type or member is obsolete + if (tls_version_minor == 1 && tls_version_major == 0) SslProtocol = SslProtocols.Tls; + if (tls_version_minor == 1 && tls_version_major == 1) SslProtocol = SslProtocols.Tls11; + if (tls_version_minor == 1 && tls_version_major == 2) SslProtocol = SslProtocols.Tls12; + if (tls_version_minor == 1 && tls_version_major == 3) SslProtocol = SslProtocols.Tls13; - // HANDSHAKE HEADER - if (pos + 1 > dataLength) - { - ReasonPhrase = "Handshake error."; - return; - } + if (tls_version_major < 3) + { + ReasonPhrase = $"Received SSL Handshake Cannot Support SNI. Min TLS: {tls_version_minor} Max TLS: {tls_version_major}"; + return; + } - // data[5] == 0x01 - if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) - { - ReasonPhrase = "Not a client hello."; - return; - } + // TLS Record Length (Length Of Handshake Message) (2 Bytes) + int len = (data[3] << 8) + data[4]; + dataLength = Math.Min(dataLength, len + TLS_HEADER_LEN); - // Skip Handshake Message Type - pos += 1; + // Check We Received Entire TLS Record Length + if (dataLength < len + TLS_HEADER_LEN) + { + ReasonPhrase = "Didn't Receive Entire TLS Record Length."; + return; + } - // Length of client hello data (3 bytes length) - len = (data[pos] << 16) + (data[pos + 1] << 8) + data[pos + 2]; - // Skip Length of client hello data - pos += 3; + // HANDSHAKE HEADER + if (pos + 1 > dataLength) + { + ReasonPhrase = "Handshake Error."; + return; + } - // CLIENT VERSION (This field is no longer used for negotiation and is hardcoded to the 1.2 version) - pos += 2; + // data[5] == 0x01 + if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) + { + ReasonPhrase = "Not A Client Hello."; + return; + } - // CLIENT RANDOM (32 bytes constant) - pos += 32; + // Skip Handshake Message Type + pos += 1; - // SESSION ID - if (pos + 1 > dataLength) - { - ReasonPhrase = "Session ID error."; - return; - } + // Length of Client Hello Data (3 Bytes) + len = (data[pos] << 16) + (data[pos + 1] << 8) + data[pos + 2]; - // Session ID Length (1 byte length) - len = data[pos]; - pos += 1 + len; + // Skip Length Of Client Hello Data + pos += 3; - // CIPHER SUITES - if (pos + 2 > dataLength) - { - ReasonPhrase = "Cipher Suit error."; - return; - } + // CLIENT VERSION (This Field Is No Longer Used For Negotiation And Is Hardcoded To The 1.2 Version) + pos += 2; - // Cipher Suits Length (2 bytes length) - len = (data[pos] << 8) + data[pos + 1]; - pos += 2 + len; + // CLIENT RANDOM (32 Bytes Constant) + pos += 32; - // COMPRESSION METHODS (TLS 1.3 no longer allows compression, so this field is always a single entry. - // 01 - 1 bytes of compression methods. 00 - assigned value for "null" compression.) - if (pos + 1 > dataLength) - { - ReasonPhrase = "Compression Method error."; - return; - } + // SESSION ID + if (pos + 1 > dataLength) + { + ReasonPhrase = "Session ID Error."; + return; + } - // Compression Methods Length (1 byte length) - len = data[pos]; - pos += 1 + len; + // Session ID Length (1 Byte) + len = data[pos]; + pos += 1 + len; - if (pos == dataLength && tls_version_major == 3 && tls_version_minor == 0) - { - ReasonPhrase = "Received SSL 3.0 handshake without extensions."; - return; - } + // CIPHER SUITES + if (pos + 2 > dataLength) + { + ReasonPhrase = "Cipher Suit Error."; + return; + } - // EXTENSIONS - if (pos + 2 > dataLength) - { - ReasonPhrase = "Extensions error."; - return; - } + // Cipher Suits Length (2 Bytes) + len = (data[pos] << 8) + data[pos + 1]; + pos += 2 + len; - // Extensions Length (2 bytes length) - len = (data[pos] << 8) + data[pos + 1]; - pos += 2; + // COMPRESSION METHODS (TLS 1.3 No Longer Allows Compression, So This Field Is Always A Single Entry. + // 01 - 1 Bytes Of Compression Methods. 00 - Assigned Value For "null" Compression.) + if (pos + 1 > dataLength) + { + ReasonPhrase = "Compression Method Error."; + return; + } - if (pos + len > dataLength) - { - ReasonPhrase = "Wrong Data."; - return; - } + // Compression Methods Length (1 Byte) + len = data[pos]; + pos += 1 + len; - byte[] extensionsData = new byte[len]; - Buffer.BlockCopy(data, pos, extensionsData, 0, extensionsData.Length); + if (pos == dataLength && tls_version_major == 3 && tls_version_minor == 0) + { + ReasonPhrase = "Received SSL 3.0 Handshake Without Extensions."; + return; + } - ParseExtensions(extensionsData, pos); + // EXTENSIONS + if (pos + 2 > dataLength) + { + ReasonPhrase = "Extensions Error."; + return; + } + + // Extensions Length (2 Bytes) + len = (data[pos] << 8) + data[pos + 1]; + pos += 2; + + if (pos + len > dataLength) + { + ReasonPhrase = "Wrong Data."; + return; + } + + byte[] extensionsData = new byte[len]; + Buffer.BlockCopy(data, pos, extensionsData, 0, extensionsData.Length); + + ParseExtensions(extensionsData, pos); + } } + catch (Exception) { } } private void ParseExtensions(byte[] data, int pos0) { - if (data.Length <= 0) return; + try + { + if (data.Length <= 0) return; - HasTlsExtensions = true; - AllExtensions.Data = data; - AllExtensions.Length = data.Length; - AllExtensions.StartIndex = pos0; + HasTlsExtensions = true; + AllExtensions.Data = data; + AllExtensions.Length = data.Length; + AllExtensions.StartIndex = pos0; - int pos = 0; - int len; - // Parse each 4 bytes for the extension header (to avoid index out of range) - while (pos + 4 <= data.Length) - { - len = 2; // Add Extension Type - len += 2; // Add Extension Length (2 bytes length) - - // Add SNI Extension Data - len += (data[pos + 2] << 8) + data[pos + 3]; - - byte[] extData = new byte[len]; - Buffer.BlockCopy(data, pos, extData, 0, len); - - //if (data[pos] == 0x00 && data[pos + 1] == 0x15) // Extension: Padding - if (data[pos] == 0x00 && data[pos + 1] == 0x00) // Extension: SNI - ParseSniExtension(extData, pos0 + pos); - //else if (data[pos] == 0x00 && data[pos + 1] == 0x0b) // Extension: EC Point Formats - //else if (data[pos] == 0x00 && data[pos + 1] == 0x0a) // Extension: Supported Groups - //else if (data[pos] == 0x00 && data[pos + 1] == 0x23) // Extension: Session Ticket - //else if (data[pos] == 0x00 && data[pos + 1] == 0x16) // Extension: Encrypt-Then-MAC - //else if (data[pos] == 0x00 && data[pos + 1] == 0x17) // Extension: Extended Master Secret - //else if (data[pos] == 0x00 && data[pos + 1] == 0x0d) // Extension: Signature Algorithms - //else if (data[pos] == 0x00 && data[pos + 1] == 0x2b) // Extension: Supported Versions - //else if (data[pos] == 0x00 && data[pos + 1] == 0x2d) // Extension: PSK Key Exchange Modes - //else if (data[pos] == 0x00 && data[pos + 1] == 0x33) // Extension: Key Share - - // Advance to the next extension - pos += len; - } + int pos = 0; + int len; + // Parse Each 4 Bytes For The Extension Header (To Avoid Index Out Of Range) + while (pos + 4 <= data.Length) + { + len = 2; // Add Extension Type + len += 2; // Add Extension Length (2 Bytes) + + // Add SNI Extension Data + len += (data[pos + 2] << 8) + data[pos + 3]; + + byte[] extData = new byte[len]; + Buffer.BlockCopy(data, pos, extData, 0, len); + + //if (data[pos] == 0x00 && data[pos + 1] == 0x15) // Extension: Padding + if (data[pos] == 0x00 && data[pos + 1] == 0x00) // Extension: SNI + ParseSniExtension(extData, pos0 + pos); + //else if (data[pos] == 0x00 && data[pos + 1] == 0x0b) // Extension: EC Point Formats + //else if (data[pos] == 0x00 && data[pos + 1] == 0x0a) // Extension: Supported Groups + //else if (data[pos] == 0x00 && data[pos + 1] == 0x23) // Extension: Session Ticket + //else if (data[pos] == 0x00 && data[pos + 1] == 0x16) // Extension: Encrypt-Then-MAC + //else if (data[pos] == 0x00 && data[pos + 1] == 0x17) // Extension: Extended Master Secret + //else if (data[pos] == 0x00 && data[pos + 1] == 0x0d) // Extension: Signature Algorithms + //else if (data[pos] == 0x00 && data[pos + 1] == 0x2b) // Extension: Supported Versions + //else if (data[pos] == 0x00 && data[pos + 1] == 0x2d) // Extension: PSK Key Exchange Modes + //else if (data[pos] == 0x00 && data[pos + 1] == 0x33) // Extension: Key Share + + // Advance to the next extension + pos += len; + } - if (SniList.Any()) - { - HasSni = true; - ReasonPhrase = "Successfully read SNI."; - } - else - { - HasSni = false; - ReasonPhrase = "Wrong Data."; - SniList.Clear(); + if (SniList.Any()) + { + HasSni = true; + ReasonPhrase = "Successfully Read SNI."; + } + else + { + HasSni = false; + ReasonPhrase = "A Handshake Without SNI."; + SniList.Clear(); + } } + catch (Exception) { } } private void ParseSniExtension(byte[] data, int pos0) { - // EXTENSION SERVER NAME - if (data.Length <= 0) return; - - HasSniExtension = true; - SniExtension sniExtension = new(); - sniExtension.Data = data; - sniExtension.Length = data.Length; - sniExtension.StartIndex = pos0; - SniExtensionList.Add(sniExtension); + try + { + // EXTENSION SERVER NAME + if (data.Length <= 0) return; - int pos = 0; + HasSniExtension = true; + SniExtension sniExtension = new(); + sniExtension.Data = data; + sniExtension.Length = data.Length; + sniExtension.StartIndex = pos0; + SniExtensionList.Add(sniExtension); - // Check if it's a server name extension - if (data[pos] == 0x00 && data[pos + 1] == 0x00) - { - pos += 2; // skip server name list length (00 00) - int len; + int pos = 0; - while (pos + 1 < data.Length) + // Check If It's A Server Name Extension + if (data[pos] == 0x00 && data[pos + 1] == 0x00) { - // SNI Extension Data Length (2 bytes length) - len = (data[pos] << 8) + data[pos + 1]; - pos += 2; - - // First and Only List Entry Length (2 bytes length) - len = (data[pos] << 8) + data[pos + 1]; - pos += 2; // skip extension header + pos += 2; // Skip Server Name List Length (00 00) + int len; - // List Entry Type - 0x00 is DNS Hostname (1 byte) - if (data[pos] == 0x00) + while (pos + 1 < data.Length) { - pos += 1; // Skip List Entry Type - - // Hostname Length (2 bytes length) + // SNI Extension Data Length (2 Bytes) len = (data[pos] << 8) + data[pos + 1]; - pos += 2; // Skip Hostname Length + pos += 2; - if (pos + len > data.Length) break; + // First And Only List Entry Length (2 Bytes) + len = (data[pos] << 8) + data[pos + 1]; + pos += 2; // Skip Extension Header - if (len > 0) + // List Entry Type - 0x00 Is DNS Hostname (1 Byte) + if (data[pos] == 0x00) { - byte[] outData = new byte[len]; - Buffer.BlockCopy(data, pos, outData, 0, len); + pos += 1; // Skip List Entry Type + + // Hostname Length (2 Bytes) + len = (data[pos] << 8) + data[pos + 1]; + pos += 2; // Skip Hostname Length - string serverName = Encoding.UTF8.GetString(outData); - //Debug.WriteLine("----------Server Name: " + serverName + ", Length: " + len + ", Whole Data Length: " + Data.Length); + if (pos + len > data.Length) break; - SNI sni = new(); - sni.Data = outData; - sni.Length = len; - sni.ServerName = serverName; - sni.StartIndex = pos0 + pos; + if (len > 0) + { + byte[] outData = new byte[len]; + Buffer.BlockCopy(data, pos, outData, 0, len); - // Add SNI to List - SniList.Add(sni); + string serverName = Encoding.UTF8.GetString(outData); + //Debug.WriteLine("----------Server Name: " + serverName + ", Length: " + len + ", Whole Data Length: " + Data.Length); + + SNI sni = new(); + sni.Data = outData; + sni.Length = len; + sni.ServerName = serverName; + sni.StartIndex = pos0 + pos; + + // Add SNI to List + SniList.Add(sni); + } + } + else + { + Debug.WriteLine("SniReader: Unknown server name extension name type."); } - } - else - { - Debug.WriteLine("SniReader: Unknown server name extension name type."); - } - pos += len; // Skip Hostname + pos += len; // Skip Hostname + } } } - + catch (Exception) { } } } \ No newline at end of file diff --git a/MsmhToolsClass/MsmhToolsClass/WindowsFirewall.cs b/MsmhToolsClass/MsmhToolsClass/WindowsFirewall.cs index 05fc298..61c339c 100644 --- a/MsmhToolsClass/MsmhToolsClass/WindowsFirewall.cs +++ b/MsmhToolsClass/MsmhToolsClass/WindowsFirewall.cs @@ -1,4 +1,6 @@ -namespace MsmhToolsClass; +using System.Diagnostics; + +namespace MsmhToolsClass; public class WindowsFirewall { @@ -24,9 +26,17 @@ public static async Task IsWindowsFirewallEnabledAsync() { return await Task.Run(async () => { - string args = $"/c netsh advfirewall show allprofiles | find \"State\""; - string output = await ProcessManager.ExecuteAsync("cmd", null, args, true, true); - return output.Contains("ON"); + try + { + string args = $"/c netsh advfirewall show allprofiles | find \"State\""; + string output = await ProcessManager.ExecuteAsync("cmd", null, args, true, true); + return output.Contains("ON"); + } + catch (Exception ex) + { + Debug.WriteLine("WindowsFirewall IsWindowsFirewallEnabledAsync: " + ex.Message); + return false; + } }); } @@ -37,9 +47,17 @@ public static async Task IsRuleExistAsync(string ruleName) { return await Task.Run(async () => { - string args = $"advfirewall firewall show rule name=\"{ruleName}\""; - string output = await ProcessManager.ExecuteAsync("netsh", null, args, true, true); - return output.Contains("Ok."); + try + { + string args = $"advfirewall firewall show rule name=\"{ruleName}\""; + string output = await ProcessManager.ExecuteAsync("netsh", null, args, true, true); + return output.Contains("Ok."); + } + catch (Exception ex) + { + Debug.WriteLine("WindowsFirewall IsRuleExistAsync: " + ex.Message); + return false; + } }); } @@ -51,54 +69,76 @@ public static async Task AddOrUpdateRuleAsync(string ruleName, string exeP { return await Task.Run(async () => { - string dir = ruleDirection == RuleDirection.IN ? "in" : "out"; - string action = ruleAction == RuleAction.Allow ? "allow" : ruleAction == RuleAction.Block ? "block" : "bypass"; - - string args = $"advfirewall firewall add rule name=\"{ruleName}\" program=\"{exePath}\" dir={dir} action={action} enable=yes profile=any localip=any remoteip=any protocol=any interfacetype=any"; - bool isRuleExist = await IsRuleExistAsync(ruleName); - if (isRuleExist) - args = $"advfirewall firewall set rule name=\"{ruleName}\" new program=\"{exePath}\" dir={dir} action={action} enable=yes profile=any localip=any remoteip=any protocol=any interfacetype=any"; - - string output = await ProcessManager.ExecuteAsync("netsh", null, args, true, true); - return output.Contains("Ok."); + try + { + string dir = ruleDirection == RuleDirection.IN ? "in" : "out"; + string action = ruleAction == RuleAction.Allow ? "allow" : ruleAction == RuleAction.Block ? "block" : "bypass"; + + string args = $"advfirewall firewall add rule name=\"{ruleName}\" program=\"{exePath}\" dir={dir} action={action} enable=yes profile=any localip=any remoteip=any protocol=any interfacetype=any"; + bool isRuleExist = await IsRuleExistAsync(ruleName); + if (isRuleExist) + args = $"advfirewall firewall set rule name=\"{ruleName}\" new program=\"{exePath}\" dir={dir} action={action} enable=yes profile=any localip=any remoteip=any protocol=any interfacetype=any"; + + string output = await ProcessManager.ExecuteAsync("netsh", null, args, true, true); + return output.Contains("Ok."); + } + catch (Exception ex) + { + Debug.WriteLine("WindowsFirewall AddOrUpdateRuleAsync 1: " + ex.Message); + return false; + } }); } /// - /// Add Or Update Firewall Rules + /// Add Or Update Firewall ProxyRules /// public static async Task AddOrUpdateRuleAsync(List ruleSets) { await Task.Run(async () => { - await Parallel.ForEachAsync(ruleSets, async (rule, ct) => + try { - string ruleName = rule.RuleName; - string exePath = rule.ExePath; - RuleDirection dir = rule.Direction; - RuleAction action = rule.Action; - - await AddOrUpdateRuleAsync(ruleName, exePath, dir, action); - }); + await Parallel.ForEachAsync(ruleSets, async (rule, ct) => + { + string ruleName = rule.RuleName; + string exePath = rule.ExePath; + RuleDirection dir = rule.Direction; + RuleAction action = rule.Action; + + await AddOrUpdateRuleAsync(ruleName, exePath, dir, action); + }); + } + catch (Exception ex) + { + Debug.WriteLine("WindowsFirewall AddOrUpdateRuleAsync 2: " + ex.Message); + } }); } /// - /// Add Or Update Firewall Rules + /// Add Or Update Firewall ProxyRules /// public static void AddOrUpdateRule(List ruleSets) { Task.Run(() => { - Parallel.ForEach(ruleSets, async (rule) => + try { - string ruleName = rule.RuleName; - string exePath = rule.ExePath; - RuleDirection dir = rule.Direction; - RuleAction action = rule.Action; - - await AddOrUpdateRuleAsync(ruleName, exePath, dir, action); - }); + Parallel.ForEach(ruleSets, async (rule) => + { + string ruleName = rule.RuleName; + string exePath = rule.ExePath; + RuleDirection dir = rule.Direction; + RuleAction action = rule.Action; + + await AddOrUpdateRuleAsync(ruleName, exePath, dir, action); + }); + } + catch (Exception ex) + { + Debug.WriteLine("WindowsFirewall AddOrUpdateRule: " + ex.Message); + } }); } @@ -110,9 +150,17 @@ public static async Task DisableRuleAsync(string ruleName) { return await Task.Run(async () => { - string args = $"netsh advfirewall firewall set rule name=\"{ruleName}\" new enable=no"; - string output = await ProcessManager.ExecuteAsync("netsh", null, args, true, true); - return output.Contains("Ok."); + try + { + string args = $"netsh advfirewall firewall set rule name=\"{ruleName}\" new enable=no"; + string output = await ProcessManager.ExecuteAsync("netsh", null, args, true, true); + return output.Contains("Ok."); + } + catch (Exception ex) + { + Debug.WriteLine("WindowsFirewall DisableRuleAsync: " + ex.Message); + return false; + } }); } @@ -124,9 +172,17 @@ public static async Task EnableRuleAsync(string ruleName) { return await Task.Run(async () => { - string args = $"netsh advfirewall firewall set rule name=\"{ruleName}\" new enable=yes"; - string output = await ProcessManager.ExecuteAsync("netsh", null, args, true, true); - return output.Contains("Ok."); + try + { + string args = $"netsh advfirewall firewall set rule name=\"{ruleName}\" new enable=yes"; + string output = await ProcessManager.ExecuteAsync("netsh", null, args, true, true); + return output.Contains("Ok."); + } + catch (Exception ex) + { + Debug.WriteLine("WindowsFirewall EnableRuleAsync: " + ex.Message); + return false; + } }); } @@ -138,9 +194,17 @@ public static async Task DeleteRuleAsync(string ruleName) { return await Task.Run(async () => { - string args = $"advfirewall firewall delete rule name=\"{ruleName}\""; - string output = await ProcessManager.ExecuteAsync("netsh", null, args, true, true); - return output.Contains("Ok."); + try + { + string args = $"advfirewall firewall delete rule name=\"{ruleName}\""; + string output = await ProcessManager.ExecuteAsync("netsh", null, args, true, true); + return output.Contains("Ok."); + } + catch (Exception ex) + { + Debug.WriteLine("WindowsFirewall DeleteRuleAsync: " + ex.Message); + return false; + } }); } diff --git a/MsmhToolsWinFormsClass/MsmhToolsWinFormsClass/Settings.cs b/MsmhToolsWinFormsClass/MsmhToolsWinFormsClass/Settings.cs index f1dbacf..93fec7d 100644 --- a/MsmhToolsWinFormsClass/MsmhToolsWinFormsClass/Settings.cs +++ b/MsmhToolsWinFormsClass/MsmhToolsWinFormsClass/Settings.cs @@ -66,66 +66,73 @@ public void LoadFromXML(string xmlString) public void LoadFromXMLFile(Control form, string xmlFilePath) { - if (xmlFilePath != null && XmlTool.IsValidXMLFile(xmlFilePath)) + try { - // Clear List - SettingList.Clear(); + if (xmlFilePath != null && XmlTool.IsValidXMLFile(xmlFilePath)) + { + // Clear List + SettingList.Clear(); - // Clear XDoc - XElement? settingsx = XDoc.Element("Settings"); - settingsx?.RemoveAll(); + // Clear XDoc + XElement? settingsx = XDoc.Element("Settings"); + settingsx?.RemoveAll(); - // Add Settings to XDoc - XDoc = XDocument.Load(xmlFilePath); + // Add Settings to XDoc + XDoc = XDocument.Load(xmlFilePath); - // Add Settings to List - // Begin Check - var settings = XDoc.Elements("Settings"); - bool settingExist = settings.Any(); - if (settingExist) - { - // Top Exist - XElement? setting0 = XDoc.Element("Settings"); - if (setting0 != null) + // Add Settings to List + // Begin Check + var settings = XDoc.Elements("Settings"); + bool settingExist = settings.Any(); + if (settingExist) { - var controls = setting0.Elements().ToArray(); - bool controlExist = controls.Any(); - if (controlExist) + // Top Exist + XElement? setting0 = XDoc.Element("Settings"); + if (setting0 != null) { - // Control Exist - for (int n1 = 0; n1 < controls.Length; n1++) + var controls = setting0.Elements().ToArray(); + bool controlExist = controls.Any(); + if (controlExist) { - XElement? control0 = controls.ToArray()[n1]; - if (control0 != null) + // Control Exist + for (int n1 = 0; n1 < controls.Length; n1++) { - var controlProperties = control0.Elements(); - bool controlPropertyExist = controlProperties.Any(); - if (controlPropertyExist) + XElement? control0 = controls.ToArray()[n1]; + if (control0 != null) { - // Control Property Exist - for (int n2 = 0; n2 < controlProperties.Count(); n2++) + var controlProperties = control0.Elements(); + bool controlPropertyExist = controlProperties.Any(); + if (controlPropertyExist) { - XElement? controlProperty0 = controlProperties.ToArray()[n2]; - if (controlProperty0 != null) + // Control Property Exist + for (int n2 = 0; n2 < controlProperties.Count(); n2++) { - if (IsControlExistInForm(form, control0.Name.LocalName)) - AddSettingToList(control0.Name.LocalName, controlProperty0.Name.LocalName, controlProperty0.Value); - else + XElement? controlProperty0 = controlProperties.ToArray()[n2]; + if (controlProperty0 != null) { - // Remove old controls settings - XElement? elementToRemove = setting0.Element(control0.Name.LocalName); - elementToRemove?.Remove(); + if (IsControlExistInForm(form, control0.Name.LocalName)) + AddSettingToList(control0.Name.LocalName, controlProperty0.Name.LocalName, controlProperty0.Value); + else + { + // Remove old controls settings + XElement? elementToRemove = setting0.Element(control0.Name.LocalName); + elementToRemove?.Remove(); + } } } } } } + LoadAllSettings(form); } - LoadAllSettings(form); } } } } + catch (Exception ex) + { + Debug.WriteLine("Settings LoadFromXMLFile: " + ex.Message); + } } private static bool IsControlExistInForm(Control form, string controlName) @@ -141,57 +148,73 @@ private static bool IsControlExistInForm(Control form, string controlName) public void LoadAllSettings(Control form) { - List controls = Controllers.GetAllControls(form); - for (int n1 = 0; n1 < controls.Count; n1++) + try { - Control control = controls[n1]; - PropertyInfo[] properties = control.GetType().GetProperties(); - for (int n2 = 0; n2 < properties.Length; n2++) + List controls = Controllers.GetAllControls(form); + for (int n1 = 0; n1 < controls.Count; n1++) { - PropertyInfo property = properties[n2]; - List settingList = SettingList.ToList(); // ToList: Fix: Collection was modified; enumeration operation may not execute. - for (int n3 = 0; n3 < settingList.Count; n3++) + Control control = controls[n1]; + PropertyInfo[] properties = control.GetType().GetProperties(); + for (int n2 = 0; n2 < properties.Length; n2++) { - Setting setting = settingList[n3]; - if (control.Name == setting.ControlName && property.Name == setting.PropertyName && setting.PropertyValue != null) + PropertyInfo property = properties[n2]; + List settingList = SettingList.ToList(); // ToList: Fix: Collection was modified; enumeration operation may not execute. + for (int n3 = 0; n3 < settingList.Count; n3++) { - try - { - TypeConverter typeConverter = TypeDescriptor.GetConverter(property.PropertyType); - if (typeConverter.CanConvertFrom(typeof(string))) - { - property.SetValue(control, typeConverter.ConvertFrom(setting.PropertyValue), null); - break; - } - } - catch (Exception ex1) + Setting setting = settingList[n3]; + if (control.Name == setting.ControlName && property.Name == setting.PropertyName && setting.PropertyValue != null) { - Debug.WriteLine(property.Name + ": " + ex1.Message); try { - property.SetValue(control, Convert.ChangeType(setting.PropertyValue, property.PropertyType), null); - break; + TypeConverter typeConverter = TypeDescriptor.GetConverter(property.PropertyType); + if (typeConverter.CanConvertFrom(typeof(string))) + { + property.SetValue(control, typeConverter.ConvertFrom(setting.PropertyValue), null); + break; + } } - catch (Exception ex2) + catch (Exception ex1) { - Debug.WriteLine(property.Name + ": " + ex2.Message); + Debug.WriteLine(property.Name + ": " + ex1.Message); + try + { + property.SetValue(control, Convert.ChangeType(setting.PropertyValue, property.PropertyType), null); + break; + } + catch (Exception ex2) + { + Debug.WriteLine(property.Name + ": " + ex2.Message); + } } } } } } } + catch (Exception ex) + { + Debug.WriteLine("Settings LoadAllSettings: " + ex.Message); + } } public void Save(string xmlFilePath) { - XmlWriterSettings xmlWriterSettings = new(); - xmlWriterSettings.Async = true; - xmlWriterSettings.Indent = true; - xmlWriterSettings.OmitXmlDeclaration = true; - xmlWriterSettings.Encoding = new UTF8Encoding(false); - using XmlWriter xmlWriter = XmlWriter.Create(xmlFilePath, xmlWriterSettings); - XDoc.Save(xmlWriter); + try + { + XmlWriterSettings xmlWriterSettings = new() + { + Async = true, + Indent = true, + OmitXmlDeclaration = true, + Encoding = new UTF8Encoding(false) + }; + using XmlWriter xmlWriter = XmlWriter.Create(xmlFilePath, xmlWriterSettings); + XDoc.Save(xmlWriter); + } + catch (Exception ex) + { + Debug.WriteLine("Settings Save: " + ex.Message); + } } public async Task SaveAsync(string xmlFilePath) @@ -448,97 +471,111 @@ public void AddSetting(Control control, string propertyName, object propertyValu private void AddSettingToList(string controlName, string propertyName, object propertyValue) { - if (string.IsNullOrWhiteSpace(controlName)) return; - if (string.IsNullOrWhiteSpace(propertyName)) return; - if (propertyValue == null) return; + try + { + if (string.IsNullOrWhiteSpace(controlName)) return; + if (string.IsNullOrWhiteSpace(propertyName)) return; + if (propertyValue == null) return; - string? value = propertyValue.ToString(); - if (value == null) return; + string? value = propertyValue.ToString(); + if (value == null) return; - Setting setting = new(controlName, propertyName, value); + Setting setting = new(controlName, propertyName, value); - // Begin Check - bool alreadyExist = false; - for (int n = 0; n < SettingList.Count; n++) - { - Setting s = SettingList[n]; - if (controlName.Equals(s.ControlName) && propertyName.Equals(s.PropertyName)) + // Begin Check + bool alreadyExist = false; + for (int n = 0; n < SettingList.Count; n++) { - // Control Property Exist - s.PropertyValue = value; - alreadyExist = true; - break; + Setting s = SettingList[n]; + if (controlName.Equals(s.ControlName) && propertyName.Equals(s.PropertyName)) + { + // Control Property Exist + s.PropertyValue = value; + alreadyExist = true; + break; + } } - } - if (!alreadyExist) - SettingList.Add(setting); + if (!alreadyExist) + SettingList.Add(setting); + } + catch (Exception ex) + { + Debug.WriteLine("Settings AddSettingToList: " + ex.Message); + } } private void AddSettingToXDoc(string controlName, string propertyName, object propertyValue) { - if (string.IsNullOrWhiteSpace(controlName)) return; - if (string.IsNullOrWhiteSpace(propertyName)) return; - if (propertyValue == null) return; + try + { + if (string.IsNullOrWhiteSpace(controlName)) return; + if (string.IsNullOrWhiteSpace(propertyName)) return; + if (propertyValue == null) return; - string? value = propertyValue.ToString(); - if (value == null) return; + string? value = propertyValue.ToString(); + if (value == null) return; - // Create Control Property - XElement xControlProperty = new(propertyName); - xControlProperty.Value = value; + // Create Control Property + XElement xControlProperty = new(propertyName); + xControlProperty.Value = value; - // Create Control Name - XElement xControl = new(controlName); - xControl.Add(xControlProperty); + // Create Control Name + XElement xControl = new(controlName); + xControl.Add(xControlProperty); - // Create Settings - XElement xSettings = new("Settings"); - xSettings.Add(xControl); + // Create Settings + XElement xSettings = new("Settings"); + xSettings.Add(xControl); - // Begin Check - var settings = XDoc.Elements("Settings"); - bool settingExist = settings.Any(); - if (settingExist) - { - // Top Exist - XElement? setting0 = XDoc.Element("Settings"); - if (setting0 == null) return; - - var controls = setting0.Elements(controlName); - bool controlExist = controls.Any(); - if (controlExist) + // Begin Check + var settings = XDoc.Elements("Settings"); + bool settingExist = settings.Any(); + if (settingExist) { - // Control Exist - XElement? control0 = setting0.Element(controlName); - if (control0 == null) return; + // Top Exist + XElement? setting0 = XDoc.Element("Settings"); + if (setting0 == null) return; - var controlProperties = control0.Elements(propertyName); - bool controlPropertyExist = controlProperties.Any(); - if (controlPropertyExist) + var controls = setting0.Elements(controlName); + bool controlExist = controls.Any(); + if (controlExist) { - // Control Property Exist - XElement? controlProperty0 = control0.Element(propertyName); - if (controlProperty0 == null) return; + // Control Exist + XElement? control0 = setting0.Element(controlName); + if (control0 == null) return; + + var controlProperties = control0.Elements(propertyName); + bool controlPropertyExist = controlProperties.Any(); + if (controlPropertyExist) + { + // Control Property Exist + XElement? controlProperty0 = control0.Element(propertyName); + if (controlProperty0 == null) return; - controlProperty0.Value = value; + controlProperty0.Value = value; + } + else + { + // Control Property Not Exist + control0.Add(xControlProperty); + } } else { - // Control Property Not Exist - control0.Add(xControlProperty); + // Control Not Exist + setting0.Add(xControl); } } else { - // Control Not Exist - setting0.Add(xControl); + // Setiings Not Exist + XDoc.Add(xSettings); } } - else + catch (Exception ex) { - // Setiings Not Exist - XDoc.Add(xSettings); + Debug.WriteLine("Settings AddSettingToXDoc: " + ex.Message); } } diff --git a/Notes.txt b/Notes.txt new file mode 100644 index 0000000..9dc5c41 --- /dev/null +++ b/Notes.txt @@ -0,0 +1 @@ +Microsoft.Diagnostics.Tracing.TraceEvent v3.1.9 Is Incompatible With WinForms Designer. \ No newline at end of file diff --git a/SDCAgnosticServer/ConsoleTools.cs b/SDCAgnosticServer/ConsoleTools.cs new file mode 100644 index 0000000..8588c39 --- /dev/null +++ b/SDCAgnosticServer/ConsoleTools.cs @@ -0,0 +1,449 @@ +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace SDCAgnosticServer; + +public static class ConsoleTools +{ + public static void AddOrUpdateCommand(this List list, string baseCmd, string cmd) + { + try + { + bool isExist = false; + for (int n = 0; n < list.Count; n++) + { + if (list[n].StartsWith(baseCmd)) + { + list[n] = cmd; + isExist = true; + } + } + if (!isExist) list.Add(cmd); + } + catch (Exception ex) + { + Debug.WriteLine("ConsoleTool AddOrUpdateCommand: " + ex.Message); + } + } + + public static string? GetCommandsPath() + { + try + { + using Process currentProcess = Process.GetCurrentProcess(); + string al = Path.GetFullPath(currentProcess.ProcessName); + string? n = Path.GetFileNameWithoutExtension(al); + return !string.IsNullOrEmpty(n) ? Path.GetFullPath(n + ".txt") : null; + } + catch (Exception ex) + { + Debug.WriteLine("ConsoleTool GetCommandsPath: " + ex.Message); + return null; + } + } + + public static async Task ReadValueAsync(string msg, object value, Type type) + { + try + { + await Task.Run(() => + { + if (!string.IsNullOrEmpty(msg)) Console.Out.WriteLine(msg); + if (type == typeof(bool)) + { + while (true) + { + string? cmd = Console.ReadLine(); + if (cmd == null) continue; + if (cmd == string.Empty) break; + + bool isBool = bool.TryParse(cmd, out bool result); + if (isBool) + { + value = result; + break; + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine("Wrong Value, Use True or False. Try Again."); + Console.ResetColor(); + } + } + } + else if (type == typeof(float)) + { + while (true) + { + string? cmd = Console.ReadLine(); + if (cmd == null) continue; + if (cmd == string.Empty) break; + + bool isFloat = float.TryParse(cmd, out float f); + if (isFloat) + { + value = f; + break; + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine("Wrong Number. Try Again."); + Console.ResetColor(); + } + } + } + else if (type == typeof(int)) + { + while (true) + { + string? cmd = Console.ReadLine(); + if (cmd == null) continue; + if (cmd == string.Empty) break; + + bool isInt = int.TryParse(cmd, out int n); + if (isInt) + { + value = n; + break; + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine("Wrong Number. Try Again."); + Console.ResetColor(); + } + } + } + else if (type == typeof(string)) + { + while (true) + { + string? cmd = Console.ReadLine(); + if (cmd == null) continue; + + if (!string.IsNullOrEmpty(cmd) || !string.IsNullOrWhiteSpace(cmd)) + value = cmd.Trim(); + break; + } + } + }); + } + catch (Exception ex) + { + Debug.WriteLine("ConsoleTool ReadValueAsync: " + ex.Message); + } + + return value; + } + + public static bool TryGetValueByKey(string input, string keyName, bool isKeyMandatory, bool requireDoubleQuotes, out string value) + { + try + { + value = string.Empty; + string origKeyName = keyName; + string key = $"-{keyName}="; + int startIndex = input.IndexOf(key, StringComparison.InvariantCultureIgnoreCase); + if (startIndex == -1) + { + if (isKeyMandatory) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"\"{origKeyName}\" Key Is Missing."); + Console.ResetColor(); + return false; + } + else return true; + } + + int skip = startIndex + key.Length; + input = input[skip..]; + + if (requireDoubleQuotes) + { + if (input.StartsWith('"')) + input = input[1..]; + else + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"The Value Of \"{origKeyName}\" Require Double Quotes."); + Console.ResetColor(); + return false; + } + } + + int endIndex; + if (requireDoubleQuotes) + { + endIndex = input.IndexOf("\"", StringComparison.InvariantCultureIgnoreCase); + if (endIndex == -1) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"The Value Of \"{origKeyName}\" Require Double Quotes."); + Console.ResetColor(); + return false; + } + } + else + { + endIndex = input.IndexOf(" -", StringComparison.InvariantCultureIgnoreCase); + } + + if (endIndex != -1) + { + input = input[..endIndex]; + } + + value = input.Trim(); + return true; + } + catch (Exception ex) + { + Debug.WriteLine("ConsoleTool TryGetValueByKey: " + ex.Message); + value = string.Empty; + return false; + } + } + + public static bool TryGetBool(string key, string value, bool isValueMandatory, out bool result) + { + try + { + result = false; + value = value.Trim(); + if (!isValueMandatory && string.IsNullOrEmpty(value)) return true; + + bool isBool = bool.TryParse(value, out bool b); + + if (!isBool) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"Value Of \"{key}\" Is Wrong, Use True or False."); + Console.ResetColor(); + return false; + } + + result = b; + return true; + } + catch (Exception ex) + { + Debug.WriteLine("ConsoleTool TryGetBool: " + ex.Message); + result = false; + return false; + } + } + + public static bool TryGetFloat(string key, string value, bool isValueMandatory, int min, int max, out float result) + { + try + { + result = Convert.ToSingle(min); + value = value.Trim(); + if (!isValueMandatory && string.IsNullOrEmpty(value)) return true; + + bool isFloat = float.TryParse(value, out float number); + + if (!isFloat) + { + Console.ForegroundColor = ConsoleColor.Red; + if (string.IsNullOrEmpty(value)) + Console.Out.WriteLine($"The Value Of \"{key}\" Is Empty."); + else + Console.Out.WriteLine($"\"{value}\" It's Not A Number."); + Console.ResetColor(); + return false; + } + + if (number < min || number > max) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"The Value of \"{key}\" Must Be Between {min} and {max}."); + Console.ResetColor(); + return false; + } + + result = number; + return true; + } + catch (Exception ex) + { + Debug.WriteLine("ConsoleTool TryGetFloat: " + ex.Message); + result = -1; + return false; + } + } + + public static bool TryGetInt(string key, string value, bool isValueMandatory, int min, int max, out int result) + { + try + { + result = min; + value = value.Trim(); + if (!isValueMandatory && string.IsNullOrEmpty(value)) return true; + + bool isInt = int.TryParse(value, out int number); + + if (!isInt) + { + Console.ForegroundColor = ConsoleColor.Red; + if (string.IsNullOrEmpty(value)) + Console.Out.WriteLine($"The Value Of \"{key}\" Is Empty."); + else + Console.Out.WriteLine($"\"{value}\" It's Not A Number."); + Console.ResetColor(); + return false; + } + + if (number < min || number > max) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"The Value of \"{key}\" Must Be Between {min} and {max}."); + Console.ResetColor(); + return false; + } + + result = number; + return true; + } + catch (Exception ex) + { + Debug.WriteLine("ConsoleTool TryGetInt: " + ex.Message); + result = -1; + return false; + } + } + + public static bool TryGetString(string key, string value, bool isValueMandatory, out string result) + { + try + { + result = string.Empty; + value = value.Trim(); + if (!isValueMandatory && string.IsNullOrEmpty(value)) return true; + + if (string.IsNullOrEmpty(value)) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"The Value of \"{key}\" Cannot Be Empty."); + Console.ResetColor(); + return false; + } + else + { + result = value; + return true; + } + } + catch (Exception ex) + { + Debug.WriteLine("ConsoleTool TryGetString: " + ex.Message); + result = string.Empty; + return false; + } + } + + public static bool TryGetString(string key, string value, bool isValueMandatory, KeyValues keyValues, out string result) + { + try + { + result = string.Empty; + value = value.Trim(); + if (!isValueMandatory && string.IsNullOrEmpty(value)) return true; + + List list = keyValues.Get(); + + for (int n = 0; n < list.Count; n++) + { + KeyValue kv = list[n]; + if (kv.Key.ToLower().Equals(value.ToLower())) + { + result = value; + return true; + } + } + + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"The Value of \"{key}\" Must Be One Of:\n"); + for (int n = 0; n < list.Count; n++) + { + KeyValue kv = list[n]; + Console.Out.WriteLine(kv.Key); + } + Console.ResetColor(); + return false; + } + catch (Exception ex) + { + Debug.WriteLine("ConsoleTool TryGetString: " + ex.Message); + result = string.Empty; + return false; + } + } + + //=============================================== Console Quick Editor + // Save Original on Startup + // GetConsoleMode(GetConsoleWindow(), ref saveConsoleMode); + // Restore at Exit + // SetConsoleMode(GetConsoleWindow(), saveConsoleMode); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern IntPtr GetConsoleWindow(); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool GetConsoleMode(IntPtr hConsoleHandle, out int lpMode); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool SetConsoleMode(IntPtr hConsoleHandle, int ioMode); + + /// + /// This flag enables the user to use the mouse to select and edit text. To enable + /// this option, you must also set the ExtendedFlags flag. + /// + const int QuickEditMode = 64; + + // ExtendedFlags must be combined with + // InsertMode and QuickEditMode when setting + /// + /// ExtendedFlags must be enabled in order to enable InsertMode or QuickEditMode. + /// + const int ExtendedFlags = 128; + + public static void DisableQuickEdit() + { + IntPtr conHandle = GetConsoleWindow(); + + if (!GetConsoleMode(conHandle, out int mode)) + { + // error getting the console mode. Exit. + return; + } + + mode &= ~(QuickEditMode | ExtendedFlags); + + if (!SetConsoleMode(conHandle, mode)) + { + // error setting console mode. + } + } + + public static void EnableQuickEdit() + { + IntPtr conHandle = GetConsoleWindow(); + + if (!GetConsoleMode(conHandle, out int mode)) + { + // error getting the console mode. Exit. + return; + } + + mode |= (QuickEditMode | ExtendedFlags); + + if (!SetConsoleMode(conHandle, mode)) + { + // error setting console mode. + } + } +} \ No newline at end of file diff --git a/SDCAgnosticServer/ExecuteCommands.cs b/SDCAgnosticServer/ExecuteCommands.cs new file mode 100644 index 0000000..c9b524b --- /dev/null +++ b/SDCAgnosticServer/ExecuteCommands.cs @@ -0,0 +1,1456 @@ +using MsmhToolsClass; +using MsmhToolsClass.MsmhAgnosticServer; +using System.Diagnostics; +using System.Net; + +namespace SDCAgnosticServer; + +public static partial class Program +{ + private static async Task ExecuteCommandsAsync(string? input) + { + try + { + await Task.Run(async () => + { + if (string.IsNullOrEmpty(input)) return; + if (string.IsNullOrWhiteSpace(input)) return; + input = input.Trim(); + + // Comment + if (input.ToLower().StartsWith('/')) return; + + // Clear Screen - Stop Writing + else if (input.ToLower().Equals(Key.Common.C.ToLower())) + { + WriteRequestsToLog = false; + WriteFragmentDetailsToLog = false; + try + { + Console.Clear(); + } + catch (Exception ex) + { + WriteToStdout($"Error Console: {ex.Message}"); + } + } + + // Clear Screen + else if (input.ToLower().Equals(Key.Common.CLS.ToLower())) + { + try + { + Console.Clear(); + } + catch (Exception ex) + { + WriteToStdout($"Error Console: {ex.Message}"); + } + } + + // Get Help + else if (input.ToLower().Equals("/?") || input.ToLower().Equals("/h") || input.ToLower().Equals("-h") || + input.ToLower().Equals("help") || input.ToLower().Equals("/help") || input.ToLower().Equals("-help")) + Help.GetHelp(); + + // Get Help Programs + else if (input.ToLower().StartsWith("help program")) + { + string prefix = "help program"; + if (input.ToLower().Equals($"{prefix} {Key.Programs.Fragment.Name.ToLower()}")) Help.GetHelpFragment(); + else if (input.ToLower().Equals($"{prefix} {Key.Programs.DnsRules.Name.ToLower()}")) Help.GetHelpDnsRules(); + else if (input.ToLower().Equals($"{prefix} {Key.Programs.ProxyRules.Name.ToLower()}")) Help.GetHelpProxyRules(); + else Help.GetHelpPrograms(); + } + + // Load Commands + else if (input.ToLower().Equals(Key.Common.Load.ToLower())) + await LoadCommandsFromFileAsync(); + + // Create Profile + else if (input.ToLower().StartsWith(Key.Common.Profile.ToLower())) + { + string[] split = input.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (split.Length > 1) + { + Profile = split[1]; + ShowProfileMsg(); + } + else WriteToStdout($"Wrong Profile Name", ConsoleColor.DarkRed); + } + + // Check Profile Name + else if (string.IsNullOrEmpty(Profile.Trim())) + { + WriteToStdout($"Set Profile Name First. Command: Profile ", ConsoleColor.Cyan); + return; + } + + // Get Status Machine Read + else if (input.ToLower().StartsWith(Key.Common.Out.ToLower())) + { + string[] split = input.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (split.Length > 1) + { + string profile = split[1]; + MainDetails(profile); + } + else WriteToStdout($"Wrong Profile Name", ConsoleColor.DarkRed); + } + + // Get Status Human Read + else if (input.ToLower().Equals(Key.Common.Status.ToLower())) + ShowStatus(); + + // Flush DNS + else if (input.ToLower().Equals(Key.Common.Flush.ToLower())) + FlushDns(); + + // Parent Process + else if (input.ToLower().StartsWith(Key.ParentProcess.Name.ToLower())) + { + // ParentProcess -PID=m + + string key = Key.ParentProcess.PID; + bool isValueOk = ConsoleTools.TryGetValueByKey(input, key, true, false, out string value); + if (!isValueOk) return; + isValueOk = ConsoleTools.TryGetInt(key, value, true, 0, int.MaxValue, out int pid); + if (!isValueOk) return; + + ParentPID = pid; + ShowParentProcessMsg(); + } + + // AgnosticSettings + else if (input.ToLower().StartsWith(Key.Setting.Name.ToLower())) + { + int port = DefaultPort; + AgnosticSettings.WorkingMode workingMode = DefaultWorkingMode; + int maxRequests = DefaultMaxRequests; + int dnsTimeoutSec = DefaultDnsTimeoutSec; + int proxyTimeoutSec = DefaultProxyTimeoutSec; + float killOnCpuUsage = DefaultKillOnCpuUsage; + bool blockPort80 = DefaultBlockPort80; + bool allowInsecure = DefaultAllowInsecure; + List dnss = new(); + string cloudflareCleanIP = string.Empty; + string bootstrapIp = DefaultBootstrapIp.ToString(); + int bootstrapPort = DefaultBootstrapPort; + string proxyScheme = string.Empty; + string proxyUser = string.Empty; + string proxyPass = string.Empty; + bool applyUpstreamOnlyToBlockedIps = DefaultApplyUpstreamOnlyToBlockedIps; + + // Interactive Mode + if (input.ToLower().Equals(Key.Setting.Name.ToLower())) + { + // Port + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Enter Port Number (Default: {DefaultPort})", port, typeof(int)); + bool isInt = int.TryParse(value.ToString(), out int n); + if (isInt && n >= DefaultPortMin && n <= DefaultPortMax) + { + // Check Port + bool isPortOpen = NetworkTool.IsPortOpen(n); + + if (isPortOpen) + { + WriteToStdout($"Port {n} Is Occupied, Choose Another."); + continue; + } + + port = n; + WriteToStdout($"Port Set To {port}", ConsoleColor.Green); + break; + } + else + { + if (isInt) + WriteToStdout($"Port Number Must Be Between {DefaultPortMin} and {DefaultPortMax}", ConsoleColor.Red); + else + WriteToStdout("Error Parsing The Int Number!", ConsoleColor.Red); + } + } + + // Working Mode + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Enter Working Mode (Default: {DefaultWorkingMode})", workingMode, typeof(string)); + string workingModeStr = value.ToString() ?? string.Empty; + workingModeStr = workingModeStr.ToLower().Trim(); + + if (workingModeStr.Equals(nameof(AgnosticSettings.WorkingMode.Dns).ToLower())) + workingMode = AgnosticSettings.WorkingMode.Dns; + else if (workingModeStr.Equals(nameof(AgnosticSettings.WorkingMode.DnsAndProxy).ToLower())) + workingMode = AgnosticSettings.WorkingMode.DnsAndProxy; + + WriteToStdout($"Working Mode Set to {workingMode}", ConsoleColor.Green); + break; + } + + // Max Requests + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Enter Maximum Requests To Handle Per Second (Default: {DefaultMaxRequests})", maxRequests, typeof(int)); + bool isInt = int.TryParse(value.ToString(), out int n); + if (isInt && n >= DefaultMaxRequestsMin && n <= DefaultMaxRequestsMax) + { + maxRequests = n; + WriteToStdout($"Maximum Requests Set to {maxRequests}", ConsoleColor.Green); + break; + } + else + { + if (isInt) + WriteToStdout($"Maximum Requests Must Be Between {DefaultMaxRequestsMin} and {DefaultMaxRequestsMax}", ConsoleColor.Red); + else + WriteToStdout("Error Parsing The Int Number!", ConsoleColor.Red); + } + } + + // Dns Timeout Sec + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Enter Dns Timeout in Sec (Default: {DefaultDnsTimeoutSec} Sec)", dnsTimeoutSec, typeof(int)); + bool isInt = int.TryParse(value.ToString(), out int n); + if (isInt && n >= DefaultDnsTimeoutSecMin && n <= DefaultDnsTimeoutSecMax) + { + dnsTimeoutSec = n; + WriteToStdout($"Dns Timeout Set to {dnsTimeoutSec} Seconds", ConsoleColor.Green); + break; + } + else + { + if (isInt) + WriteToStdout($"Dns Timeout Must Be Between {DefaultDnsTimeoutSecMin} and {DefaultDnsTimeoutSecMax}", ConsoleColor.Red); + else + WriteToStdout("Error Parsing The Int Number!", ConsoleColor.Red); + } + } + + if (workingMode == AgnosticSettings.WorkingMode.DnsAndProxy) + { + // Proxy Timeout Sec + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Enter Proxy Timeout in Sec (Default: {DefaultProxyTimeoutSec} Sec)", proxyTimeoutSec, typeof(int)); + bool isInt = int.TryParse(value.ToString(), out int n); + if (isInt && n >= DefaultProxyTimeoutSecMin && n <= DefaultProxyTimeoutSecMax) + { + proxyTimeoutSec = n; + WriteToStdout($"Proxy Timeout Set to {proxyTimeoutSec} Seconds", ConsoleColor.Green); + break; + } + else + { + if (isInt) + WriteToStdout($"Proxy Timeout Must Be Between {DefaultProxyTimeoutSecMin} and {DefaultProxyTimeoutSecMax}", ConsoleColor.Red); + else + WriteToStdout("Error Parsing The Int Number!", ConsoleColor.Red); + } + } + + // Kill On Cpu Usage + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Kill On Cpu Usage, Enter Percentage (Default: {DefaultKillOnCpuUsage}%)", killOnCpuUsage, typeof(float)); + bool isFloat = float.TryParse(value.ToString(), out float f); + if (isFloat && f >= DefaultKillOnCpuUsageMin && f <= DefaultKillOnCpuUsageMax) + { + killOnCpuUsage = f; + WriteToStdout($"Kill On Cpu Usage Set to {killOnCpuUsage}%", ConsoleColor.Green); + break; + } + else + { + if (isFloat) + WriteToStdout($"Percentage Must Be Between {DefaultKillOnCpuUsageMin} and {DefaultKillOnCpuUsageMax}", ConsoleColor.Red); + else + WriteToStdout("Error Parsing The Float Number!", ConsoleColor.Red); + } + } + + // Block Port 80 + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Block Port 80, Enter True/False (Default: {DefaultBlockPort80})", blockPort80, typeof(bool)); + bool b = Convert.ToBoolean(value); + + blockPort80 = b; + WriteToStdout($"Block Port 80: {blockPort80}", ConsoleColor.Green); + break; + } + } + + // Allow Insecure + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Allow Insecure, Enter True/False (Default: {DefaultAllowInsecure})", allowInsecure, typeof(bool)); + bool b = Convert.ToBoolean(value); + + allowInsecure = b; + WriteToStdout($"Allow Insecure: {allowInsecure}", ConsoleColor.Green); + break; + } + + // DNSs + while (true) + { + string dnssStr = string.Empty; + object value = await ConsoleTools.ReadValueAsync("Enter DNS Addresses (Comma Separate) (Default: Google, Quad9)", dnssStr, typeof(string)); + dnssStr = value.ToString() ?? string.Empty; + dnssStr = dnssStr.Trim(); + + if (!string.IsNullOrEmpty(dnssStr)) + { + string[] dnssArray = dnssStr.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + dnss.AddRange(dnssArray); + } + + WriteToStdout($"DNS Addresses Set. Count: {dnss.Count}", ConsoleColor.Green); + break; + } + + // Cloudflare Clean IP + while (true) + { + string cfCleanIp = string.Empty; + object value = await ConsoleTools.ReadValueAsync("Enter Cloudflare Clean IPv4 (Default: Empty)", cfCleanIp, typeof(string)); + cfCleanIp = value.ToString() ?? string.Empty; + cfCleanIp = cfCleanIp.Trim(); + + if (!string.IsNullOrEmpty(cfCleanIp)) + { + bool isIpv4Valid = NetworkTool.IsIPv4Valid(cfCleanIp, out _); + if (isIpv4Valid) cloudflareCleanIP = cfCleanIp; + } + + if (!string.IsNullOrEmpty(cloudflareCleanIP)) + WriteToStdout($"Cloudflare Clean IP Set To: {cloudflareCleanIP}", ConsoleColor.Green); + else + WriteToStdout($"Cloudflare Clean IP Disabled", ConsoleColor.Green); + break; + } + + // Bootstrap Ip + while (true) + { + string getBootstrapIp = string.Empty; + object value = await ConsoleTools.ReadValueAsync($"Enter Bootstrap Ip (Plain DNS) (Default: None)", bootstrapIp, typeof(string)); + getBootstrapIp = value.ToString() ?? string.Empty; + getBootstrapIp = getBootstrapIp.Trim(); + + if (!string.IsNullOrEmpty(getBootstrapIp)) + { + bool isIp = IPAddress.TryParse(getBootstrapIp, out _); + if (isIp) bootstrapIp = getBootstrapIp; + } + + WriteToStdout($"Bootstrap IP Set To: {bootstrapIp}", ConsoleColor.Green); + break; + } + + // Bootstrap Port + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Enter Bootstrap Port (Default: {DefaultBootstrapPort})", bootstrapPort, typeof(int)); + bool isInt = int.TryParse(value.ToString(), out int n); + if (isInt && n >= 1 && n <= 65535) + { + bootstrapPort = n; + WriteToStdout($"Bootstrap Port Set to {bootstrapPort}", ConsoleColor.Green); + break; + } + else + { + if (isInt) + WriteToStdout("Bootstrap Port Must Be Between 1 and 65535", ConsoleColor.Red); + else + WriteToStdout("Error Parsing The Int Number!", ConsoleColor.Red); + } + } + + // Upstream Proxy Scheme + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Enter Upstream Proxy Scheme (HTTP Or SOCKS5) (Default: Empty)", proxyScheme, typeof(string)); + proxyScheme = value.ToString() ?? string.Empty; + proxyScheme = proxyScheme.Trim(); + + if (!string.IsNullOrEmpty(proxyScheme)) + WriteToStdout($"Upstream Proxy Scheme Set To: {proxyScheme}", ConsoleColor.Green); + else + WriteToStdout("Upstream Proxy Scheme Disabled", ConsoleColor.Green); + break; + } + + if (!string.IsNullOrEmpty(proxyScheme)) + { + // Upstream Proxy Username + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Enter Upstream Proxy Username (Default: Empty)", proxyUser, typeof(string)); + proxyUser = value.ToString() ?? string.Empty; + proxyUser = proxyUser.Trim(); + + if (!string.IsNullOrEmpty(proxyUser)) + WriteToStdout($"Upstream Proxy Username Set To: {proxyUser}", ConsoleColor.Green); + else + WriteToStdout("Upstream Proxy Username Set To: None", ConsoleColor.Green); + break; + } + + // Upstream Proxy Password + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Enter Upstream Proxy Password (Default: Empty)", proxyPass, typeof(string)); + proxyPass = value.ToString() ?? string.Empty; + proxyPass = proxyPass.Trim(); + + if (!string.IsNullOrEmpty(proxyPass)) + WriteToStdout($"Upstream Proxy Password Set To: {proxyPass}", ConsoleColor.Green); + else + WriteToStdout("Upstream Proxy Password Set To: None", ConsoleColor.Green); + break; + } + + // Apply Upstream Only To Blocked IPs + while (true) + { + object value = await ConsoleTools.ReadValueAsync($"Apply Upstream Only To Blocked IPs, Enter True/False (Default: {DefaultApplyUpstreamOnlyToBlockedIps})", applyUpstreamOnlyToBlockedIps, typeof(bool)); + bool b = Convert.ToBoolean(value); + + applyUpstreamOnlyToBlockedIps = b; + WriteToStdout($"Apply Upstream Only To Blocked IPs: {applyUpstreamOnlyToBlockedIps}", ConsoleColor.Green); + break; + } + } + } + else // Command Mode + { + // setting -Port=m -WorkingMode= -MaxRequests= -DnsTimeoutSec= -ProxyTimeoutSec= -KillOnCpuUsage= -BlockPort80= + // -AllowInsecure= -DNSs= -CfCleanIP= -BootstrapIp= -BootstrapPort= + // -ProxyScheme= -ProxyUser= -ProxyPass= -OnlyBlockedIPs= + + KeyValues keyValues = new(); + keyValues.Add(Key.Setting.Port, true, false, typeof(int), DefaultPortMin, DefaultPortMax); + keyValues.Add(Key.Setting.WorkingMode, false, false, typeof(string)); + keyValues.Add(Key.Setting.MaxRequests, false, false, typeof(int), DefaultMaxRequestsMin, DefaultMaxRequestsMax); + keyValues.Add(Key.Setting.DnsTimeoutSec, false, false, typeof(int), DefaultDnsTimeoutSecMin, DefaultDnsTimeoutSecMax); + keyValues.Add(Key.Setting.ProxyTimeoutSec, false, false, typeof(int), DefaultProxyTimeoutSecMin, DefaultProxyTimeoutSecMax); + keyValues.Add(Key.Setting.KillOnCpuUsage, false, false, typeof(float), DefaultKillOnCpuUsageMin, DefaultKillOnCpuUsageMax); + keyValues.Add(Key.Setting.BlockPort80, false, false, typeof(bool)); + keyValues.Add(Key.Setting.AllowInsecure, false, false, typeof(bool)); + keyValues.Add(Key.Setting.DNSs, false, false, typeof(string)); + keyValues.Add(Key.Setting.CfCleanIP, false, false, typeof(string)); + keyValues.Add(Key.Setting.BootstrapIp, false, false, typeof(string)); + keyValues.Add(Key.Setting.BootstrapPort, false, false, typeof(int), 1, 65535); + keyValues.Add(Key.Setting.ProxyScheme, false, false, typeof(string)); + keyValues.Add(Key.Setting.ProxyUser, false, false, typeof(string)); + keyValues.Add(Key.Setting.ProxyPass, false, false, typeof(string)); + keyValues.Add(Key.Setting.OnlyBlockedIPs, false, false, typeof(bool)); + + bool isListOk = keyValues.TryGetValuesByKeys(input, out List list); + if (!isListOk) return; + for (int n = 0; n < list.Count; n++) + { + KeyValue kv = list[n]; + if (kv.Key.Equals(Key.Setting.Port)) port = kv.ValueInt; + if (kv.Key.Equals(Key.Setting.WorkingMode)) + { + if (kv.ValueString.Equals(nameof(AgnosticSettings.WorkingMode.Dns))) + workingMode = AgnosticSettings.WorkingMode.Dns; + else if (kv.ValueString.Equals(nameof(AgnosticSettings.WorkingMode.DnsAndProxy))) + workingMode = AgnosticSettings.WorkingMode.DnsAndProxy; + } + if (kv.Key.Equals(Key.Setting.MaxRequests)) maxRequests = kv.ValueInt; + if (kv.Key.Equals(Key.Setting.DnsTimeoutSec)) dnsTimeoutSec = kv.ValueInt; + if (kv.Key.Equals(Key.Setting.ProxyTimeoutSec)) proxyTimeoutSec = kv.ValueInt; + if (kv.Key.Equals(Key.Setting.KillOnCpuUsage)) killOnCpuUsage = kv.ValueFloat; + if (kv.Key.Equals(Key.Setting.BlockPort80)) blockPort80 = kv.ValueBool; + if (kv.Key.Equals(Key.Setting.AllowInsecure)) allowInsecure = kv.ValueBool; + if (kv.Key.Equals(Key.Setting.DNSs)) + { + if (!string.IsNullOrEmpty(kv.ValueString)) + { + string[] dnssArray = kv.ValueString.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + dnss.AddRange(dnssArray); + } + } + if (kv.Key.Equals(Key.Setting.CfCleanIP)) cloudflareCleanIP = kv.ValueString; + if (kv.Key.Equals(Key.Setting.BootstrapIp)) bootstrapIp = kv.ValueString; + if (kv.Key.Equals(Key.Setting.BootstrapPort)) bootstrapPort = kv.ValueInt; + if (kv.Key.Equals(Key.Setting.ProxyScheme)) proxyScheme = kv.ValueString; + if (kv.Key.Equals(Key.Setting.ProxyUser)) proxyUser = kv.ValueString; + if (kv.Key.Equals(Key.Setting.ProxyPass)) proxyPass = kv.ValueString; + if (kv.Key.Equals(Key.Setting.OnlyBlockedIPs)) applyUpstreamOnlyToBlockedIps = kv.ValueBool; + } + } + + AgnosticSettings settings = new() + { + ListenerPort = port, + Working_Mode = workingMode, + MaxRequests = maxRequests, + DnsTimeoutSec = dnsTimeoutSec, + ProxyTimeoutSec = proxyTimeoutSec, + KillOnCpuUsage = killOnCpuUsage, + BlockPort80 = blockPort80, + AllowInsecure = allowInsecure, + DNSs = dnss, + CloudflareCleanIP = cloudflareCleanIP + }; + bool isbootstrapIp = IPAddress.TryParse(bootstrapIp, out IPAddress? bootstrapIpOut); + if (isbootstrapIp && bootstrapIpOut != null) + settings.BootstrapIpAddress = bootstrapIpOut; + settings.BootstrapPort = bootstrapPort; + if (!string.IsNullOrEmpty(proxyScheme)) + { + settings.UpstreamProxyScheme = proxyScheme; + settings.UpstreamProxyUser = proxyUser; + settings.UpstreamProxyPass = proxyPass; + settings.ApplyUpstreamOnlyToBlockedIps = applyUpstreamOnlyToBlockedIps; + } + + ShowSettingsMsg(settings); + } + + // SSLSetting + else if (input.ToLower().StartsWith(Key.SSLSetting.Name.ToLower())) + { + bool enable = DefaultSSLEnable; + string? rootCA_Path = null; + string? rootCA_KeyPath = null; + string? cert_Path = null; + string? cert_KeyPath = null; + bool changeSni = false; + string defaultSni = string.Empty; + + // Interactive Mode + if (input.ToLower().Equals(Key.SSLSetting.Name.ToLower())) + { + // Enable + string msgRV = $"Enable SSL Decryption, Enter True/False (Default: {DefaultSSLEnable})"; + while (true) + { + object value = await ConsoleTools.ReadValueAsync(msgRV, enable, typeof(bool)); + enable = Convert.ToBoolean(value); + + WriteToStdout($"Enable Set to {enable}", ConsoleColor.Green); + break; + } + + if (enable) + { + // RootCA_Path + bool doesUserSetCAPath = false; + while (true) + { + msgRV = $"Enter The Path Of Root Certificate (Leave Empty To Generate)"; + object value = await ConsoleTools.ReadValueAsync(msgRV, string.Empty, typeof(string)); + string path = value.ToString() ?? string.Empty; + if (string.IsNullOrEmpty(path)) + { + rootCA_Path = null; + break; + } + else + { + path = Path.GetFullPath(path); + if (!File.Exists(path)) + { + string msgNotExist = $"{path}\nFile Not Exist."; + WriteToStdout(msgNotExist, ConsoleColor.Red); + continue; + } + else + { + rootCA_Path = path; + doesUserSetCAPath = true; + break; + } + } + } + + // RootCA_KeyPath + if (doesUserSetCAPath) + { + while (true) + { + msgRV = $"Enter The Path Of Root Private Key (Leave Empty If Certificate Contains Private Key)"; + object value = await ConsoleTools.ReadValueAsync(msgRV, string.Empty, typeof(string)); + string path = value.ToString() ?? string.Empty; + if (string.IsNullOrEmpty(path)) + { + rootCA_KeyPath = null; + break; + } + else + { + path = Path.GetFullPath(path); + if (!File.Exists(path)) + { + string msgNotExist = $"{path}\nFile Not Exist."; + WriteToStdout(msgNotExist, ConsoleColor.Red); + continue; + } + else + { + rootCA_KeyPath = path; + break; + } + } + } + } + + // Cert_Path + bool doesUserSetCertPath = false; + while (true) + { + msgRV = $"Enter The Path Of Certificate (Leave Empty To Generate)"; + object value = await ConsoleTools.ReadValueAsync(msgRV, string.Empty, typeof(string)); + string path = value.ToString() ?? string.Empty; + if (string.IsNullOrEmpty(path)) + { + cert_Path = null; + break; + } + else + { + path = Path.GetFullPath(path); + if (!File.Exists(path)) + { + string msgNotExist = $"{path}\nFile Not Exist."; + WriteToStdout(msgNotExist, ConsoleColor.Red); + continue; + } + else + { + cert_Path = path; + doesUserSetCertPath = true; + break; + } + } + } + + // Cert_KeyPath + if (doesUserSetCertPath) + { + while (true) + { + msgRV = $"Enter The Path Of Cert Private Key (Leave Empty If Certificate Contains Private Key)"; + object value = await ConsoleTools.ReadValueAsync(msgRV, string.Empty, typeof(string)); + string path = value.ToString() ?? string.Empty; + if (string.IsNullOrEmpty(path)) + { + cert_KeyPath = null; + break; + } + else + { + path = Path.GetFullPath(path); + if (!File.Exists(path)) + { + string msgNotExist = $"{path}\nFile Not Exist."; + WriteToStdout(msgNotExist, ConsoleColor.Red); + continue; + } + else + { + cert_KeyPath = path; + break; + } + } + } + } + + // Change SNI To IP + msgRV = $"Change SNI To Bypass DPI, Enter True/False (Default: {DefaultSSLChangeSni})"; + while (true) + { + object value = await ConsoleTools.ReadValueAsync(msgRV, changeSni, typeof(bool)); + changeSni = Convert.ToBoolean(value); + + WriteToStdout($"ChangeSni Set to {changeSni}", ConsoleColor.Green); + break; + } + + // Default SNI + if (changeSni) + { + msgRV = $"Set Default SNI, E.G. speedtest.net (Default: Empty)"; + while (true) + { + object value = await ConsoleTools.ReadValueAsync(msgRV, string.Empty, typeof(string)); + string defaultSniOut = value.ToString() ?? string.Empty; + if (!string.IsNullOrEmpty(defaultSniOut)) + { + defaultSni = defaultSniOut; + WriteToStdout($"DefaultSni Set to {defaultSni}", ConsoleColor.Green); + break; + } + + WriteToStdout($"DefaultSni Set to Empty", ConsoleColor.Green); + break; + } + } + } + } + else // Command Mode + { + // sslsetting -Enable=m -RootCA_Path="" -RootCA_KeyPath="" -Cert_Path="" -Cert_KeyPath="" -ChangeSni= -DefaultSni= + + KeyValues keyValues = new(); + keyValues.Add(Key.SSLSetting.Enable, true, false, typeof(bool)); + keyValues.Add(Key.SSLSetting.RootCA_Path, false, true, typeof(string)); + keyValues.Add(Key.SSLSetting.RootCA_KeyPath, false, true, typeof(string)); + keyValues.Add(Key.SSLSetting.Cert_Path, false, true, typeof(string)); + keyValues.Add(Key.SSLSetting.Cert_KeyPath, false, true, typeof(string)); + keyValues.Add(Key.SSLSetting.ChangeSni, false, false, typeof(bool)); + keyValues.Add(Key.SSLSetting.DefaultSni, false, false, typeof(string)); + + bool isListOk = keyValues.TryGetValuesByKeys(input, out List list); + if (!isListOk) return; + for (int n = 0; n < list.Count; n++) + { + KeyValue kv = list[n]; + if (kv.Key.Equals(Key.SSLSetting.Enable)) enable = kv.ValueBool; + if (kv.Key.Equals(Key.SSLSetting.RootCA_Path)) rootCA_Path = kv.ValueString; + if (kv.Key.Equals(Key.SSLSetting.RootCA_KeyPath)) rootCA_KeyPath = kv.ValueString; + if (kv.Key.Equals(Key.SSLSetting.Cert_Path)) cert_Path = kv.ValueString; + if (kv.Key.Equals(Key.SSLSetting.Cert_KeyPath)) cert_KeyPath = kv.ValueString; + if (kv.Key.Equals(Key.SSLSetting.ChangeSni)) changeSni = kv.ValueBool; + if (kv.Key.Equals(Key.SSLSetting.DefaultSni)) defaultSni = kv.ValueString; + } + } + + AgnosticSettingsSSL settingsSSL = new(enable) + { + EnableSSL = enable, + RootCA_Path = rootCA_Path, + RootCA_KeyPath = rootCA_KeyPath, + Cert_Path = cert_Path, + Cert_KeyPath = cert_KeyPath, + ChangeSni = changeSni, + DefaultSni = defaultSni + }; + + ShowSettingsSSLMsg(settingsSSL); + } + + // Programs + else if (input.ToLower().StartsWith(Key.Programs.Name.ToLower())) + { + string msg = "Available Programs:\n\n"; + msg += $"{Key.Programs.Fragment.Name}\n"; + msg += $"{Key.Programs.DnsRules.Name}\n"; + msg += $"{Key.Programs.ProxyRules.Name}\n"; + + // Interactive Mode + if (input.ToLower().Equals(Key.Programs.Name.ToLower())) + { + WriteToStdout(msg, ConsoleColor.Cyan); + + while (true) + { + string programName = string.Empty; + object valueP = await ConsoleTools.ReadValueAsync("Enter One Of Programs Name (Default: None):", programName, typeof(string)); + programName = valueP.ToString() ?? string.Empty; + if (string.IsNullOrEmpty(programName) || string.IsNullOrWhiteSpace(programName)) + { + WriteToStdout($"Exited From {Key.Programs.Name}."); + break; + } + + // DPI Bypass + else if (programName.ToLower().Equals(Key.Programs.Fragment.Name.ToLower())) + { + string msgAm = $"Available {Key.Programs.Fragment.Name} Modes:\n\n"; + msgAm += $"{Key.Programs.Fragment.Mode.Program.Name}\n"; + msgAm += $"{Key.Programs.Fragment.Mode.Disable}\n"; + + WriteToStdout(msgAm, ConsoleColor.Cyan); + + string modeStr = Key.Programs.Fragment.Mode.Disable; + AgnosticProgram.Fragment.Mode mode = AgnosticProgram.Fragment.Mode.Disable; + string msgRV = $"Enter One Of Modes (Default: {mode}):"; + while (true) + { + object value = await ConsoleTools.ReadValueAsync(msgRV, modeStr, typeof(string)); + modeStr = value.ToString() ?? string.Empty; + if (modeStr.ToLower().Equals(Key.Programs.Fragment.Mode.Program.Name.ToLower())) + mode = AgnosticProgram.Fragment.Mode.Program; + else if (modeStr.ToLower().Equals(Key.Programs.Fragment.Mode.Disable.ToLower())) + mode = AgnosticProgram.Fragment.Mode.Disable; + else + { + WriteToStdout("Wrong Mode.", ConsoleColor.Red); + continue; + } + break; + } + + int beforeSniChunks = DefaultFragmentBeforeSniChunks; + AgnosticProgram.Fragment.ChunkMode chunkMode = DefaultFragmentChunkMode; + int sniChunks = DefaultFragmentSniChunks; + int antiPatternOffset = DefaultFragmentAntiPatternOffset; + int fragmentDelay = DefaultFragmentFragmentDelay; + + if (mode == AgnosticProgram.Fragment.Mode.Program) + { + // Get Before Sni Chunks + msgRV = $"Enter Number Of Chunks Before SNI (Default: {DefaultFragmentBeforeSniChunks}):"; + while (true) + { + object value = await ConsoleTools.ReadValueAsync(msgRV, beforeSniChunks, typeof(int)); + int n = Convert.ToInt32(value); + if (n >= DefaultFragmentBeforeSniChunksMin && n <= DefaultFragmentBeforeSniChunksMax) + { + beforeSniChunks = n; + break; + } + else + { + WriteToStdout($"Chunks Number Must Be Between {DefaultFragmentBeforeSniChunksMin} and {DefaultFragmentBeforeSniChunksMax}", ConsoleColor.Red); + continue; + } + } + + // Get Chunk Mode + msgAm = $"Available {Key.Programs.Fragment.Mode.Program.ChunkMode.Name} Modes:\n\n"; + msgAm += $"{Key.Programs.Fragment.Mode.Program.ChunkMode.SNI}\n"; + msgAm += $"{Key.Programs.Fragment.Mode.Program.ChunkMode.SniExtension}\n"; + msgAm += $"{Key.Programs.Fragment.Mode.Program.ChunkMode.AllExtensions}\n"; + + WriteToStdout(msgAm, ConsoleColor.Cyan); + + modeStr = Key.Programs.Fragment.Mode.Program.ChunkMode.SNI; + msgRV = $"Enter One Of Modes (Default: {chunkMode}):"; + while (true) + { + object value = await ConsoleTools.ReadValueAsync(msgRV, modeStr, typeof(string)); + modeStr = value.ToString() ?? string.Empty; + if (modeStr.ToLower().Equals(Key.Programs.Fragment.Mode.Program.ChunkMode.SNI.ToLower())) + chunkMode = AgnosticProgram.Fragment.ChunkMode.SNI; + else if (modeStr.ToLower().Equals(Key.Programs.Fragment.Mode.Program.ChunkMode.SniExtension.ToLower())) + chunkMode = AgnosticProgram.Fragment.ChunkMode.SniExtension; + else if (modeStr.ToLower().Equals(Key.Programs.Fragment.Mode.Program.ChunkMode.AllExtensions.ToLower())) + chunkMode = AgnosticProgram.Fragment.ChunkMode.AllExtensions; + else + { + WriteToStdout("Wrong Mode.", ConsoleColor.Red); + continue; + } + break; + } + + // Get Sni Chunks + msgRV = $"Enter Number Of \"{chunkMode}\" Chunks (Default: {DefaultFragmentSniChunks}):"; + while (true) + { + object value = await ConsoleTools.ReadValueAsync(msgRV, sniChunks, typeof(int)); + int n = Convert.ToInt32(value); + if (n >= DefaultFragmentSniChunksMin && n <= DefaultFragmentSniChunksMax) + { + sniChunks = n; + break; + } + else + { + WriteToStdout($"Chunks Number Must Be Between {DefaultFragmentSniChunksMin} and {DefaultFragmentSniChunksMax}", ConsoleColor.Red); + continue; + } + } + + // Get Anti-Pattern Offset + msgRV = $"Enter Number Of Anti-Pattern Offset (Default: {DefaultFragmentAntiPatternOffset}):"; + while (true) + { + object value = await ConsoleTools.ReadValueAsync(msgRV, antiPatternOffset, typeof(int)); + int n = Convert.ToInt32(value); + if (n >= DefaultFragmentAntiPatternOffsetMin && n <= DefaultFragmentAntiPatternOffsetMax) + { + antiPatternOffset = n; + break; + } + else + { + WriteToStdout($"Chunks Number Must Be Between {DefaultFragmentAntiPatternOffsetMin} and {DefaultFragmentAntiPatternOffsetMin}", ConsoleColor.Red); + continue; + } + } + + // Get Fragment Delay + msgRV = $"Enter Milliseconds Of Fragment Delay (Default: {DefaultFragmentFragmentDelay}):"; + while (true) + { + object value = await ConsoleTools.ReadValueAsync(msgRV, fragmentDelay, typeof(int)); + int n = Convert.ToInt32(value); + if (n >= DefaultFragmentFragmentDelayMin && n <= DefaultFragmentFragmentDelayMax) + { + fragmentDelay = n; + break; + } + else + { + WriteToStdout($"Fragment Delay Must Be Between {DefaultFragmentFragmentDelayMin} and {DefaultFragmentFragmentDelayMax} Milliseconds", ConsoleColor.Red); + continue; + } + } + } + + AgnosticProgram.Fragment FragmentStaticProgram = new(); + FragmentStaticProgram.Set(mode, beforeSniChunks, chunkMode, sniChunks, antiPatternOffset, fragmentDelay); + + ShowFragmentMsg(FragmentStaticProgram); + } + + // Dns Rules + else if (programName.ToLower().Equals(Key.Programs.DnsRules.Name.ToLower())) + { + string msgAm = $"Available {Key.Programs.DnsRules.Name} Modes:\n\n"; + msgAm += $"{Key.Programs.DnsRules.Mode.File}\n"; + msgAm += $"{Key.Programs.DnsRules.Mode.Text}\n"; + msgAm += $"{Key.Programs.DnsRules.Mode.Disable}\n"; + + WriteToStdout(msgAm, ConsoleColor.Cyan); + + string modeStr = Key.Programs.DnsRules.Mode.Disable; + AgnosticProgram.DnsRules.Mode mode = AgnosticProgram.DnsRules.Mode.Disable; + string msgRV = $"Enter One Of Modes (Default: {mode}):"; + while (true) + { + object value = await ConsoleTools.ReadValueAsync(msgRV, modeStr, typeof(string)); + modeStr = value.ToString() ?? string.Empty; + if (modeStr.ToLower().Equals(Key.Programs.DnsRules.Mode.File.ToLower())) + mode = AgnosticProgram.DnsRules.Mode.File; + else if (modeStr.ToLower().Equals(Key.Programs.DnsRules.Mode.Text.ToLower())) + mode = AgnosticProgram.DnsRules.Mode.Text; + else if (modeStr.ToLower().Equals(Key.Programs.DnsRules.Mode.Disable.ToLower())) + mode = AgnosticProgram.DnsRules.Mode.Disable; + else + { + WriteToStdout("Wrong Mode.", ConsoleColor.Red); + continue; + } + break; + } + + string filePathOrText = string.Empty; + + if (mode == AgnosticProgram.DnsRules.Mode.File) + { + while (true) + { + msgRV = $"Enter The Path Of {mode} (Default: Cancel):"; + object valuePath = await ConsoleTools.ReadValueAsync(msgRV, string.Empty, typeof(string)); + filePathOrText = valuePath.ToString() ?? string.Empty; + if (string.IsNullOrEmpty(filePathOrText)) + { + mode = AgnosticProgram.DnsRules.Mode.Disable; + break; + } + else + { + filePathOrText = Path.GetFullPath(filePathOrText); + if (!File.Exists(filePathOrText)) + { + string msgNotExist = $"{filePathOrText}\nFile Not Exist."; + WriteToStdout(msgNotExist, ConsoleColor.Red); + continue; + } + else break; + } + } + } + + if (mode == AgnosticProgram.DnsRules.Mode.Text) + { + msgRV = $"Enter {Key.Programs.DnsRules.Name} As Text (Default: Cancel):"; + msgRV += "\n e.g. Google.com|8.8.8.8;\\nCloudflare.com|dns:tcp://8.8.8.8;"; + object valueText = await ConsoleTools.ReadValueAsync(msgRV, string.Empty, typeof(string)); + filePathOrText = valueText.ToString() ?? string.Empty; + if (string.IsNullOrEmpty(filePathOrText)) + { + mode = AgnosticProgram.DnsRules.Mode.Disable; + } + else + { + filePathOrText = filePathOrText.ToLower().Replace(@"\n", Environment.NewLine); + } + } + + AgnosticProgram.DnsRules dnsRulesProgram = new(); + dnsRulesProgram.Set(mode, filePathOrText); + + ShowDnsRulesMsg(dnsRulesProgram); + } + + // ProxyRules + else if (programName.ToLower().Equals(Key.Programs.ProxyRules.Name.ToLower())) + { + string msgAm = $"Available {Key.Programs.ProxyRules.Name} Modes:\n\n"; + msgAm += $"{Key.Programs.ProxyRules.Mode.File}\n"; + msgAm += $"{Key.Programs.ProxyRules.Mode.Text}\n"; + msgAm += $"{Key.Programs.ProxyRules.Mode.Disable}\n"; + + WriteToStdout(msgAm, ConsoleColor.Cyan); + + string modeStr = Key.Programs.ProxyRules.Mode.Disable; + AgnosticProgram.ProxyRules.Mode mode = AgnosticProgram.ProxyRules.Mode.Disable; + string msgRV = $"Enter One Of Modes (Default: {mode}):"; + while (true) + { + object value = await ConsoleTools.ReadValueAsync(msgRV, modeStr, typeof(string)); + modeStr = value.ToString() ?? string.Empty; + if (modeStr.ToLower().Equals(Key.Programs.ProxyRules.Mode.File.ToLower())) + mode = AgnosticProgram.ProxyRules.Mode.File; + else if (modeStr.ToLower().Equals(Key.Programs.ProxyRules.Mode.Text.ToLower())) + mode = AgnosticProgram.ProxyRules.Mode.Text; + else if (modeStr.ToLower().Equals(Key.Programs.ProxyRules.Mode.Disable.ToLower())) + mode = AgnosticProgram.ProxyRules.Mode.Disable; + else + { + WriteToStdout("Wrong Mode.", ConsoleColor.Red); + continue; + } + break; + } + + string filePathOrText = string.Empty; + + if (mode == AgnosticProgram.ProxyRules.Mode.File) + { + while (true) + { + msgRV = $"Enter The Path Of {mode} (Default: Cancel):"; + object valuePath = await ConsoleTools.ReadValueAsync(msgRV, string.Empty, typeof(string)); + filePathOrText = valuePath.ToString() ?? string.Empty; + if (string.IsNullOrEmpty(filePathOrText)) + { + mode = AgnosticProgram.ProxyRules.Mode.Disable; + break; + } + else + { + filePathOrText = Path.GetFullPath(filePathOrText); + if (!File.Exists(filePathOrText)) + { + string msgNotExist = $"{filePathOrText}\nFile Not Exist."; + WriteToStdout(msgNotExist, ConsoleColor.Red); + continue; + } + else break; + } + } + } + + if (mode == AgnosticProgram.ProxyRules.Mode.Text) + { + msgRV = $"Enter {Key.Programs.ProxyRules.Name} As Text (Default: Cancel):"; + msgRV += "\n e.g. Google.com|8.8.8.8;\\nCloudflare.com|dns:tcp://8.8.8.8;"; + object valueText = await ConsoleTools.ReadValueAsync(msgRV, string.Empty, typeof(string)); + filePathOrText = valueText.ToString() ?? string.Empty; + if (string.IsNullOrEmpty(filePathOrText)) + { + mode = AgnosticProgram.ProxyRules.Mode.Disable; + } + else + { + filePathOrText = filePathOrText.ToLower().Replace(@"\n", Environment.NewLine); + } + } + + AgnosticProgram.ProxyRules proxyRulesProgram = new(); + proxyRulesProgram.Set(mode, filePathOrText); + + ShowProxyRulesMsg(proxyRulesProgram); + } + + else + { + WriteToStdout("Wrong Program Name.", ConsoleColor.Red); + continue; + } + } + } + else // Command Mode + { + bool isValueOk = false; + + // Fragment + if (input.ToLower().StartsWith($"{Key.Programs.Name.ToLower()} {Key.Programs.Fragment.Name.ToLower()}")) + { + // Programs Fragment -Mode=m -BeforeSniChunks=m -ChunkMode=m -SniChunks=m -AntiPatternOffset=m -FragmentDelay=m + + // Get ModeStr + string modeStr = Key.Programs.Fragment.Mode.Disable; + string key = Key.Programs.Fragment.Mode.Name; + isValueOk = ConsoleTools.TryGetValueByKey(input, key, true, false, out string value); + if (!isValueOk) return; + + KeyValues modes = new(); + modes.Add(Key.Programs.Fragment.Mode.Program.Name, true, false, typeof(string)); + modes.Add(Key.Programs.Fragment.Mode.Disable, true, false, typeof(string)); + + isValueOk = ConsoleTools.TryGetString(key, value, true, modes, out value); + if (!isValueOk) return; + modeStr = value; + + // Get -Mode + AgnosticProgram.Fragment.Mode mode = AgnosticProgram.Fragment.Mode.Disable; + if (modeStr.ToLower().Equals(Key.Programs.Fragment.Mode.Program.Name.ToLower())) + mode = AgnosticProgram.Fragment.Mode.Program; + else if (modeStr.ToLower().Equals(Key.Programs.Fragment.Mode.Disable.ToLower())) + mode = AgnosticProgram.Fragment.Mode.Disable; + + int beforeSniChunks = DefaultFragmentBeforeSniChunks; + string chunkModeStr = DefaultFragmentChunkModeStr; + int sniChunks = DefaultFragmentSniChunks; + int antiPatternOffset = DefaultFragmentAntiPatternOffset; + int fragmentDelay = DefaultFragmentFragmentDelay; + + // Get The Rest + if (mode == AgnosticProgram.Fragment.Mode.Program) + { + KeyValues keyValues = new(); + keyValues.Add(Key.Programs.Fragment.Mode.Program.BeforeSniChunks, true, false, typeof(int), DefaultFragmentBeforeSniChunksMin, DefaultFragmentBeforeSniChunksMax); + keyValues.Add(Key.Programs.Fragment.Mode.Program.ChunkMode.Name, true, false, typeof(string)); + keyValues.Add(Key.Programs.Fragment.Mode.Program.SniChunks, true, false, typeof(int), DefaultFragmentSniChunksMin, DefaultFragmentSniChunksMax); + keyValues.Add(Key.Programs.Fragment.Mode.Program.AntiPatternOffset, true, false, typeof(int), DefaultFragmentAntiPatternOffsetMin, DefaultFragmentAntiPatternOffsetMax); + keyValues.Add(Key.Programs.Fragment.Mode.Program.FragmentDelay, true, false, typeof(int), DefaultFragmentFragmentDelayMin, DefaultFragmentFragmentDelayMax); + + bool isListOk = keyValues.TryGetValuesByKeys(input, out List list); + + bool isChunkModeOk = false; + + for (int n = 0; n < list.Count; n++) + { + KeyValue kv = list[n]; + if (kv.Key.Equals(Key.Programs.Fragment.Mode.Program.BeforeSniChunks)) beforeSniChunks = kv.ValueInt; + if (kv.Key.Equals(Key.Programs.Fragment.Mode.Program.ChunkMode.Name)) + { + chunkModeStr = kv.ValueString; + + KeyValues chunkModes = new(); + chunkModes.Add(Key.Programs.Fragment.Mode.Program.ChunkMode.SNI, true, false, typeof(string)); + chunkModes.Add(Key.Programs.Fragment.Mode.Program.ChunkMode.SniExtension, true, false, typeof(string)); + chunkModes.Add(Key.Programs.Fragment.Mode.Program.ChunkMode.AllExtensions, true, false, typeof(string)); + + string chunkKey = Key.Programs.Fragment.Mode.Program.ChunkMode.Name; + isChunkModeOk = ConsoleTools.TryGetString(chunkKey, chunkModeStr, true, chunkModes, out chunkModeStr); + if (!isChunkModeOk) break; + } + if (kv.Key.Equals(Key.Programs.Fragment.Mode.Program.SniChunks)) sniChunks = kv.ValueInt; + if (kv.Key.Equals(Key.Programs.Fragment.Mode.Program.AntiPatternOffset)) antiPatternOffset = kv.ValueInt; + if (kv.Key.Equals(Key.Programs.Fragment.Mode.Program.FragmentDelay)) fragmentDelay = kv.ValueInt; + } + + if (!isChunkModeOk) return; + if (!isListOk) return; + } + + // Get Chunk Mode + AgnosticProgram.Fragment.ChunkMode chunkMode = AgnosticProgram.Fragment.ChunkMode.SNI; + if (chunkModeStr.ToLower().Equals(Key.Programs.Fragment.Mode.Program.ChunkMode.SNI.ToLower())) + chunkMode = AgnosticProgram.Fragment.ChunkMode.SNI; + else if (chunkModeStr.ToLower().Equals(Key.Programs.Fragment.Mode.Program.ChunkMode.SniExtension.ToLower())) + chunkMode = AgnosticProgram.Fragment.ChunkMode.SniExtension; + else if (chunkModeStr.ToLower().Equals(Key.Programs.Fragment.Mode.Program.ChunkMode.AllExtensions.ToLower())) + chunkMode = AgnosticProgram.Fragment.ChunkMode.AllExtensions; + + AgnosticProgram.Fragment fragmentStaticProgram = new(); + fragmentStaticProgram.Set(mode, beforeSniChunks, chunkMode, sniChunks, antiPatternOffset, fragmentDelay); + + ShowFragmentMsg(fragmentStaticProgram); + } + + // DnsRules + if (input.ToLower().StartsWith($"{Key.Programs.Name.ToLower()} {Key.Programs.DnsRules.Name.ToLower()}")) + { + // Programs DnsRules -Mode=m -PathOrText="m" + + // Get ModeStr + string modeStr = Key.Programs.DnsRules.Mode.Disable; + string key = Key.Programs.DnsRules.Mode.Name; + isValueOk = ConsoleTools.TryGetValueByKey(input, key, true, false, out string value); + if (!isValueOk) return; + + KeyValues modes = new(); + modes.Add(Key.Programs.DnsRules.Mode.File, true, false, typeof(string)); + modes.Add(Key.Programs.DnsRules.Mode.Text, true, false, typeof(string)); + modes.Add(Key.Programs.DnsRules.Mode.Disable, true, false, typeof(string)); + + isValueOk = ConsoleTools.TryGetString(key, value, true, modes, out value); + if (!isValueOk) return; + modeStr = value; + + // Get -Mode + AgnosticProgram.DnsRules.Mode mode = AgnosticProgram.DnsRules.Mode.Disable; + if (modeStr.ToLower().Equals(Key.Programs.DnsRules.Mode.File.ToLower())) + mode = AgnosticProgram.DnsRules.Mode.File; + else if (modeStr.ToLower().Equals(Key.Programs.DnsRules.Mode.Text.ToLower())) + mode = AgnosticProgram.DnsRules.Mode.Text; + else if (modeStr.ToLower().Equals(Key.Programs.DnsRules.Mode.Disable.ToLower())) + mode = AgnosticProgram.DnsRules.Mode.Disable; + + // Get -PathOrText + string pathOrText = string.Empty; + key = Key.Programs.DnsRules.PathOrText; + if (mode != AgnosticProgram.DnsRules.Mode.Disable) + { + isValueOk = ConsoleTools.TryGetValueByKey(input, key, true, true, out value); + if (!isValueOk) return; + isValueOk = ConsoleTools.TryGetString(key, value, true, out value); + if (!isValueOk) return; + pathOrText = value; + } + + if (mode == AgnosticProgram.DnsRules.Mode.File) + { + pathOrText = Path.GetFullPath(pathOrText); + if (!File.Exists(pathOrText)) + { + string msgNotExist = $"{pathOrText}\nFile Not Exist."; + WriteToStdout(msgNotExist, ConsoleColor.Red); + return; + } + } + + if (mode == AgnosticProgram.DnsRules.Mode.Text) + { + pathOrText = pathOrText.ToLower().Replace(@"\n", Environment.NewLine); + } + + AgnosticProgram.DnsRules dnsRulesProgram = new(); + dnsRulesProgram.Set(mode, pathOrText); + + ShowDnsRulesMsg(dnsRulesProgram); + } + + // ProxyRules + if (input.ToLower().StartsWith($"{Key.Programs.Name.ToLower()} {Key.Programs.ProxyRules.Name.ToLower()}")) + { + // Programs ProxyRules -Mode=m -PathOrText="m" + + // Get ModeStr + string modeStr = Key.Programs.ProxyRules.Mode.Disable; + string key = Key.Programs.ProxyRules.Mode.Name; + isValueOk = ConsoleTools.TryGetValueByKey(input, key, true, false, out string value); + if (!isValueOk) return; + + KeyValues modes = new(); + modes.Add(Key.Programs.ProxyRules.Mode.File, true, false, typeof(string)); + modes.Add(Key.Programs.ProxyRules.Mode.Text, true, false, typeof(string)); + modes.Add(Key.Programs.ProxyRules.Mode.Disable, true, false, typeof(string)); + + isValueOk = ConsoleTools.TryGetString(key, value, true, modes, out value); + if (!isValueOk) return; + modeStr = value; + + // Get -Mode + AgnosticProgram.ProxyRules.Mode mode = AgnosticProgram.ProxyRules.Mode.Disable; + if (modeStr.ToLower().Equals(Key.Programs.ProxyRules.Mode.File.ToLower())) + mode = AgnosticProgram.ProxyRules.Mode.File; + else if (modeStr.ToLower().Equals(Key.Programs.ProxyRules.Mode.Text.ToLower())) + mode = AgnosticProgram.ProxyRules.Mode.Text; + else if (modeStr.ToLower().Equals(Key.Programs.ProxyRules.Mode.Disable.ToLower())) + mode = AgnosticProgram.ProxyRules.Mode.Disable; + + // Get -PathOrText + string pathOrText = string.Empty; + key = Key.Programs.ProxyRules.PathOrText; + if (mode != AgnosticProgram.ProxyRules.Mode.Disable) + { + isValueOk = ConsoleTools.TryGetValueByKey(input, key, true, true, out value); + if (!isValueOk) return; + isValueOk = ConsoleTools.TryGetString(key, value, true, out value); + if (!isValueOk) return; + pathOrText = value; + } + + if (mode == AgnosticProgram.ProxyRules.Mode.File) + { + pathOrText = Path.GetFullPath(pathOrText); + if (!File.Exists(pathOrText)) + { + string msgNotExist = $"{pathOrText}\nFile Not Exist."; + WriteToStdout(msgNotExist, ConsoleColor.Red); + return; + } + } + + if (mode == AgnosticProgram.ProxyRules.Mode.Text) + { + pathOrText = pathOrText.ToLower().Replace(@"\n", Environment.NewLine); + } + + AgnosticProgram.ProxyRules proxyRulesProgram = new(); + proxyRulesProgram.Set(mode, pathOrText); + + ShowProxyRulesMsg(proxyRulesProgram); + } + + } + } + + // Kill All Requests + else if (input.ToLower().Equals(Key.Common.KillAll.ToLower())) + { + // KillAll + bool killed = false; + foreach (ServerProfile sf in ServerProfiles) + { + if (sf.AgnosticServer != null && sf.Settings != null && !string.IsNullOrEmpty(sf.Name)) + { + if (sf.AgnosticServer.IsRunning && sf.Settings.Working_Mode == AgnosticSettings.WorkingMode.DnsAndProxy) + { + sf.AgnosticServer.KillAll(); + WriteToStdout($"Done. Killed All Requests Of {sf.Name}.", ConsoleColor.Green); + killed = true; + } + } + } + + if (!killed) WriteToStdout($"There Is No Running Server.", ConsoleColor.Green); + } + + // Write Requests To Log: True + else if (input.ToLower().Equals($"{Key.Common.Requests.ToLower()} true")) + { + // WriteRequests True + WriteRequestsToLog = true; + WriteToStdout($"WriteRequestsToLog: True", ConsoleColor.Green); + + // Save Command To List + string baseCmd = Key.Common.Requests; + string cmd = $"{baseCmd} True"; + LoadCommands.AddOrUpdateCommand(baseCmd, cmd); + } + + // Write Requests To Log: False + else if (input.ToLower().Equals($"{Key.Common.Requests.ToLower()} false")) + { + // WriteRequests False + WriteRequestsToLog = false; + WriteToStdout($"WriteRequestsToLog: False", ConsoleColor.Green); + + // Save Command To List + string baseCmd = Key.Common.Requests; + string cmd = $"{baseCmd} False"; + LoadCommands.AddOrUpdateCommand(baseCmd, cmd); + } + + // Write Chunk Details To Log: True + else if (input.ToLower().Equals($"{Key.Common.FragmentDetails.ToLower()} true")) + { + // ChunkDetails True + WriteFragmentDetailsToLog = true; + WriteToStdout($"WriteFragmentDetailsToLog: True", ConsoleColor.Green); + + // Save Command To List + string baseCmd = Key.Common.FragmentDetails; + string cmd = $"{baseCmd} True"; + LoadCommands.AddOrUpdateCommand(baseCmd, cmd); + } + + // Write Chunk Details To Log: False + else if (input.ToLower().Equals($"{Key.Common.FragmentDetails.ToLower()} false")) + { + // ChunkDetails False + WriteFragmentDetailsToLog = false; + WriteToStdout($"WriteFragmentDetailsToLog: False", ConsoleColor.Green); + + // Save Command To List + string baseCmd = Key.Common.FragmentDetails; + string cmd = $"{baseCmd} False"; + LoadCommands.AddOrUpdateCommand(baseCmd, cmd); + } + + // Start Proxy Server + else if (input.ToLower().Equals(Key.Common.Start.ToLower())) + { + // Start + bool flushNeeded = false; + foreach (ServerProfile sf in ServerProfiles) + { + if (sf.AgnosticServer != null && sf.Settings != null && !string.IsNullOrEmpty(sf.Name)) + { + WriteToStdout(string.Empty); + if (!sf.AgnosticServer.IsRunning) + { + WriteToStdout($"Starting {sf.Name}...", ConsoleColor.Cyan); + if (sf.SettingsSSL != null) await sf.AgnosticServer.EnableSSL(sf.SettingsSSL); + if (sf.Fragment != null) sf.AgnosticServer.EnableFragment(sf.Fragment); + if (sf.DnsRules != null) sf.AgnosticServer.EnableDnsRules(sf.DnsRules); + if (sf.ProxyRules != null) sf.AgnosticServer.EnableProxyRules(sf.ProxyRules); + sf.AgnosticServer.Start(sf.Settings); + await Task.Delay(50); + if (sf.AgnosticServer.IsRunning) + { + sf.AgnosticServer.OnRequestReceived -= ProxyServer_OnRequestReceived; + sf.AgnosticServer.OnRequestReceived += ProxyServer_OnRequestReceived; + if (sf.Fragment != null) + { + sf.Fragment.OnChunkDetailsReceived -= FragmentStaticProgram_OnChunkDetailsReceived; + sf.Fragment.OnChunkDetailsReceived += FragmentStaticProgram_OnChunkDetailsReceived; + } + WriteToStdout($"{sf.Name} Started", ConsoleColor.Green); + flushNeeded = true; + } + else + WriteToStdout($"Couldn't Start {sf.Name}", ConsoleColor.Red); + } + else WriteToStdout($"{sf.Name} Already Started", ConsoleColor.Gray); + } + } + + if (flushNeeded) + { + WriteToStdout(string.Empty); + WriteToStdout("Flushing System DNS...", ConsoleColor.Cyan); + ProcessManager.ExecuteOnly("ipconfig", null, "/flushdns", true, true); + WriteToStdout("System DNS Flushed", ConsoleColor.Green); + } + } + + // Stop Proxy Server + else if (input.ToLower().Equals(Key.Common.Stop.ToLower())) + { + // Stop + foreach (ServerProfile sf in ServerProfiles) + { + if (sf.AgnosticServer != null && sf.Settings != null && !string.IsNullOrEmpty(sf.Name)) + { + WriteToStdout(string.Empty); + if (sf.AgnosticServer.IsRunning) + { + WriteToStdout($"Stopping {sf.Name}...", ConsoleColor.Cyan); + sf.AgnosticServer.Stop(); + await Task.Delay(50); + if (!sf.AgnosticServer.IsRunning) + WriteToStdout($"{sf.Name} Stopped", ConsoleColor.Green); + else + WriteToStdout($"Couldn't Stop {sf.Name}", ConsoleColor.Red); + } + else WriteToStdout($"{sf.Name} Already Stopped", ConsoleColor.Gray); + } + } + } + + // Save Commands + else if (input.ToLower().Equals(Key.Common.Save.ToLower())) + SaveCommandsToFile(); + + // Wrong Command + else if (input.Length > 0) + WriteToStdout("Wrong Command. Type \"Help\" To Get More Info.", ConsoleColor.Red); + }); + } + catch (Exception ex) + { + Debug.WriteLine("ExecuteCommandsAsync: " + ex.Message); + } + } +} \ No newline at end of file diff --git a/SDCProxyServer/Help.cs b/SDCAgnosticServer/Help.cs similarity index 62% rename from SDCProxyServer/Help.cs rename to SDCAgnosticServer/Help.cs index 04fc9b8..040fe9c 100644 --- a/SDCProxyServer/Help.cs +++ b/SDCAgnosticServer/Help.cs @@ -1,6 +1,7 @@ -using System.Reflection; +using System.Diagnostics; +using System.Reflection; -namespace SDCProxyServer; +namespace SDCAgnosticServer; public class Help { @@ -9,8 +10,11 @@ public static void GetHelp() string help; // Title - help = $"\nMsmh Proxy Server v{Assembly.GetExecutingAssembly().GetName().Version}, Author: msasanmh@gmail.com"; - help += "\nSupports:"; + help = $"\nMsmh Agnostic Server v{Assembly.GetExecutingAssembly().GetName().Version}, Author: msasanmh@gmail.com"; + help += "\nServer Supports:"; + help += "\n UDP Plain DNS"; + help += "\n TCP Plain DNS"; + help += "\n DNS Over HTTPS (DoH)"; help += "\n HTTP (Domain, IPv4, IPv6) (Get, Post, etc)"; help += "\n HTTPS (Domain, IPv4, IPv6) (Post, etc)"; help += "\n SOCKS4 (IPv4) (Connect, Bind)"; @@ -23,39 +27,45 @@ public static void GetHelp() WriteToStdout("\nCommands:"); // C - help = "\nc"; + help = $"\n{Key.Common.C}"; WriteToStdout(help, ConsoleColor.Blue); help = $" Clear Screen And Stop Writing."; WriteToStdout(help); // CLS - help = "\ncls"; + help = $"\n{Key.Common.CLS}"; WriteToStdout(help, ConsoleColor.Blue); help = $" Clear Screen."; WriteToStdout(help); // OUT - help = "\nout"; + help = $"\n{Key.Common.Out} "; WriteToStdout(help, ConsoleColor.Blue); help = $" Get Status In Machine Read."; WriteToStdout(help); // STATUS - help = "\nStatus"; + help = $"\n{Key.Common.Status}"; WriteToStdout(help, ConsoleColor.Blue); help = $" Get Status In Human Read."; WriteToStdout(help); + // Flush DNS + help = $"\n{Key.Common.Flush}"; + WriteToStdout(help, ConsoleColor.Blue); + help = $" Flush DNS Cache."; + WriteToStdout(help); + // Start - help = "\nStart"; + help = $"\n{Key.Common.Start}"; WriteToStdout(help, ConsoleColor.Blue); - help = $" Start Proxy Server."; + help = $" Start Server."; WriteToStdout(help); // Stop - help = "\nStop"; + help = $"\n{Key.Common.Stop}"; WriteToStdout(help, ConsoleColor.Blue); - help = $" Stop Proxy Server."; + help = $" Stop Server."; WriteToStdout(help); // Parent Process ID @@ -76,22 +86,25 @@ public static void GetHelp() // Setting Interactive Mode help = $"\n{Key.Setting.Name}"; WriteToStdout(help, ConsoleColor.Blue); - help = $" Modify Proxy Settings In Interactive Mode."; + help = $" Modify Server Settings In Interactive Mode."; WriteToStdout(help); - // Setting Command Mode - setting -Port=m -MaxThreads=m -RequestTimeoutSec=m -KillOnCpuUsage=m -BlockPort80=m + // Setting Command Mode + // setting -Port=m -WorkingMode= -MaxRequests= -DnsTimeoutSec= -ProxyTimeoutSec= -KillOnCpuUsage= -BlockPort80= + // -AllowInsecure= -DNSs= -CfCleanIP= -BootstrapIp= -BootstrapPort= + // -ProxyScheme= -ProxyUser= -ProxyPass= -OnlyBlockedIPs= help = $"\n{Key.Setting.Name}