From bca17b51d06e696b91126115a3aac802922404f3 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 01:56:40 +0000 Subject: [PATCH 1/2] feat: Add advanced proxy checking features This commit adds a number of advanced features to the proxy checker, including: - Speed testing (latency and download speed) - SOCKS4 protocol support - Saving results to a file (CSV or JSON) - Proxy scoring - Blacklist checking The code has also been improved to address feedback from a code review, including fixing a bug in the SOCKS4 check, removing dead code, and improving exception handling. --- CheckProxy.csproj | 1 + Program.cs | 236 +++++++++++++------------------- ProxyChecker.cs | 340 ++++++++++++++++++++++++++++++++++++++++++++++ ProxyInfo.cs | 65 +++++++++ 4 files changed, 500 insertions(+), 142 deletions(-) create mode 100644 ProxyChecker.cs create mode 100644 ProxyInfo.cs diff --git a/CheckProxy.csproj b/CheckProxy.csproj index 9a3020c..c775d0e 100644 --- a/CheckProxy.csproj +++ b/CheckProxy.csproj @@ -15,6 +15,7 @@ + diff --git a/Program.cs b/Program.cs index dda05cf..611b29f 100644 --- a/Program.cs +++ b/Program.cs @@ -1,7 +1,7 @@ +using Newtonsoft.Json; using Spectre.Console; using System.Net; -using System.Net.NetworkInformation; -using System.Net.Sockets; +using System.Text; using System.CommandLine; /// @@ -19,29 +19,31 @@ public static async Task Main(string[] args) var proxyArgument = new Argument("proxy", "A single proxy address (e.g., 1.2.3.4:8080)."); var fileOption = new Option("--file", "A file containing a list of proxies, one per line."); var timeoutOption = new Option("--timeout", () => 5000, "Timeout in milliseconds for each check."); + var outputOption = new Option("--output", "The path to the output file (e.g., proxies.csv or proxies.json)."); var rootCommand = new RootCommand("CheckProxy - A tool to check the validity of proxy servers.") { proxyArgument, fileOption, - timeoutOption + timeoutOption, + outputOption }; - rootCommand.SetHandler(async (proxy, file, timeout) => + rootCommand.SetHandler(async (proxy, file, timeout, output) => { if (proxy != null) { - await CheckSingleProxy(proxy, timeout); + await CheckSingleProxy(proxy, timeout, output); } else if (file != null) { - await CheckProxiesFromFile(file, timeout); + await CheckProxiesFromFile(file, timeout, output); } else { AnsiConsole.MarkupLine("[red]Error: You must provide a proxy address or a file.[/]"); } - }, proxyArgument, fileOption, timeoutOption); + }, proxyArgument, fileOption, timeoutOption, outputOption); return await rootCommand.InvokeAsync(args); } @@ -51,23 +53,38 @@ public static async Task Main(string[] args) /// /// The proxy address string (e.g., "1.2.3.4:8080"). /// The timeout in milliseconds for each check. - static async Task CheckSingleProxy(string proxyAddress, int timeout) + /// The path to the output file. + static async Task CheckSingleProxy(string proxyAddress, int timeout, string? output) { - if (IPEndPoint.TryParse(proxyAddress, out var proxyEndPoint)) + if (IPEndPoint.TryParse(proxyAddress, out _)) { var webProxy = new WebProxy(proxyAddress); - AnsiConsole.MarkupLine("Target: [bold]" + proxyAddress + "[/]"); - - var columns = new List + var checker = new ProxyChecker(webProxy); + var proxyInfo = await checker.CheckProxyAsync(); + + var table = new Table(); + table.AddColumn("Property"); + table.AddColumn("Value"); + + table.AddRow("Address", proxyInfo.Address ?? "N/A"); + table.AddRow("Type", proxyInfo.Type ?? "N/A"); + table.AddRow("Anonymity", proxyInfo.Anonymity ?? "N/A"); + table.AddRow("Country", proxyInfo.Country ?? "N/A"); + table.AddRow("ASN", proxyInfo.Asn ?? "N/A"); + table.AddRow("Outgoing IP", proxyInfo.OutgoingIp ?? "N/A"); + table.AddRow("Alive", proxyInfo.IsAlive ? "[green]Yes[/]" : "[red]No[/]"); + table.AddRow("Additional Headers", proxyInfo.AdditionalHeaders ?? "N/A"); + table.AddRow("Latency", proxyInfo.Latency == -1 ? "N/A" : $"{proxyInfo.Latency} ms"); + table.AddRow("Download Speed", proxyInfo.DownloadSpeed == -1 ? "N/A" : $"{proxyInfo.DownloadSpeed:F2} KB/s"); + table.AddRow("Score", $"{proxyInfo.Score}/100"); + table.AddRow("Blacklisted", proxyInfo.IsBlacklisted ? "[red]Yes[/]" : "[green]No[/]"); + + AnsiConsole.Write(table); + + if (output != null) { - new Markup("[bold]Ping Check[/]"), - new Markup("[bold]TCP Connection[/]"), - new Markup("[bold]HTTP Proxy Check[/]"), - }; - AnsiConsole.Write(new Columns(columns)); - - var results = await RunChecksInParallel(webProxy, proxyEndPoint, timeout); - AnsiConsole.Write(new Columns(results.Select(r => new Markup(r.ToString())))); + await WriteResultsToFile(new List { proxyInfo }, output); + } } else { @@ -80,7 +97,8 @@ static async Task CheckSingleProxy(string proxyAddress, int timeout) /// /// The file containing the list of proxies. /// The timeout in milliseconds for each check. - static async Task CheckProxiesFromFile(FileInfo file, int timeout) + /// The path to the output file. + static async Task CheckProxiesFromFile(FileInfo file, int timeout, string? output) { if (!file.Exists) { @@ -91,15 +109,20 @@ static async Task CheckProxiesFromFile(FileInfo file, int timeout) var proxies = await File.ReadAllLinesAsync(file.FullName); AnsiConsole.MarkupLine($"[yellow]Found {proxies.Length} proxies in {file.Name}. Starting checks...[/]"); - var columns = new List - { - new Markup("[bold]Proxy[/]"), - new Markup("[bold]Ping[/]"), - new Markup("[bold]TCP[/]"), - new Markup("[bold]HTTP[/]"), - }; - AnsiConsole.Write(new Columns(columns)); - + var table = new Table(); + table.AddColumn("Address"); + table.AddColumn("Type"); + table.AddColumn("Anonymity"); + table.AddColumn("Country"); + table.AddColumn("ASN"); + table.AddColumn("Outgoing IP"); + table.AddColumn("Alive"); + table.AddColumn("Latency"); + table.AddColumn("Download Speed"); + table.AddColumn("Score"); + table.AddColumn("Blacklisted"); + + var proxyInfos = new List(); var tasks = new List(); var semaphore = new SemaphoreSlim(10); // Limit to 10 concurrent checks @@ -111,25 +134,30 @@ static async Task CheckProxiesFromFile(FileInfo file, int timeout) { try { - if (IPEndPoint.TryParse(proxyAddress, out var proxyEndPoint)) + if (IPEndPoint.TryParse(proxyAddress, out _)) { var webProxy = new WebProxy(proxyAddress); - var results = await RunChecksInParallel(webProxy, proxyEndPoint, timeout); - AnsiConsole.Write(new Columns( - new Markup(proxyAddress), - new Markup(results[0].ToString()), - new Markup(results[1].ToString()), - new Markup(results[2].ToString()) - )); + var checker = new ProxyChecker(webProxy); + var proxyInfo = await checker.CheckProxyAsync(); + proxyInfos.Add(proxyInfo); + + table.AddRow( + proxyInfo.Address ?? "N/A", + proxyInfo.Type ?? "N/A", + proxyInfo.Anonymity ?? "N/A", + proxyInfo.Country ?? "N/A", + proxyInfo.Asn ?? "N/A", + proxyInfo.OutgoingIp ?? "N/A", + proxyInfo.IsAlive ? "[green]Yes[/]" : "[red]No[/]", + proxyInfo.Latency == -1 ? "N/A" : $"{proxyInfo.Latency} ms", + proxyInfo.DownloadSpeed == -1 ? "N/A" : $"{proxyInfo.DownloadSpeed:F2} KB/s", + $"{proxyInfo.Score}/100", + proxyInfo.IsBlacklisted ? "[red]Yes[/]" : "[green]No[/]" + ); } else { - AnsiConsole.Write(new Columns( - new Markup(proxyAddress), - new Markup("[red]Invalid[/]"), - new Markup("[red]Invalid[/]"), - new Markup("[red]Invalid[/]") - )); + table.AddRow(proxyAddress, "[red]Invalid[/]", "[red]Invalid[/]", "[red]Invalid[/]", "[red]Invalid[/]", "[red]Invalid[/]", "[red]No[/]", "N/A", "N/A", "0/100", "N/A"); } } finally @@ -140,118 +168,42 @@ static async Task CheckProxiesFromFile(FileInfo file, int timeout) } await Task.WhenAll(tasks); - AnsiConsole.MarkupLine("[green]All proxy checks complete.[/]"); - } - - /// - /// Runs a series of checks for a given proxy in parallel. - /// - /// The WebProxy object for the proxy. - /// The IPEndPoint for the proxy. - /// The timeout in milliseconds for each check. - /// A boolean array indicating the result of each check. - static async Task RunChecksInParallel(WebProxy wp, IPEndPoint proxyEp, int timeout) - { - var pingCheckTask = PingCheckAsync(proxyEp.Address.ToString(), timeout); - var tcpConnectionTask = TcpConnectionCheckAsync(wp.Address.Host, wp.Address.Port, timeout); - var httpProxyCheckTask = HttpProxyCheckAsync(wp, timeout); - - await Task.WhenAll( - pingCheckTask, - tcpConnectionTask, - httpProxyCheckTask); - - return new[] - { - pingCheckTask.Result, - tcpConnectionTask.Result, - httpProxyCheckTask.Result, - }; - } - - /// - /// Performs an HTTP GET request through the proxy to check its functionality. - /// - /// The WebProxy to use for the request. - /// The timeout in milliseconds for the request. - /// True if the request is successful; otherwise, false. - static async Task HttpProxyCheckAsync(WebProxy wp, int timeout) - { - var handler = new HttpClientHandler - { - Proxy = wp, - UseProxy = true, - }; - - using var client = new HttpClient(handler); - client.Timeout = TimeSpan.FromMilliseconds(timeout); + AnsiConsole.Write(table); - try + if (output != null) { - using var response = await client.GetAsync("http://www.google.com/generate_204"); - return response.IsSuccessStatusCode; - } - catch - { - return false; + await WriteResultsToFile(proxyInfos, output); } + + AnsiConsole.MarkupLine("[green]All proxy checks complete.[/]"); } /// - /// Pings the specified address to check for reachability. + /// Writes the results to a file. /// - /// The IP address or hostname to ping. - /// The timeout in milliseconds for the ping. - /// True if the ping is successful; otherwise, false. - static async Task PingCheckAsync(string address, int timeout) + /// The list of proxy info objects to write. + /// The path to the output file. + static async Task WriteResultsToFile(IEnumerable proxyInfos, string output) { - using var ping = new Ping(); - try - { - var reply = await ping.SendPingAsync(address, timeout); - return reply?.Status == IPStatus.Success; - } - catch (PingException) + var extension = Path.GetExtension(output).ToLower(); + if (extension == ".json") { - return false; + var json = JsonConvert.SerializeObject(proxyInfos, Formatting.Indented); + await File.WriteAllTextAsync(output, json); } - } - - /// - /// Attempts to open a TCP socket to the specified host and port. - /// - /// The target host. - /// The target port. - /// The timeout in milliseconds for the connection attempt. - /// True if the connection is successful; otherwise, false. - static async Task TcpConnectionCheckAsync(string host, int port, int timeout) - { - using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - try + else if (extension == ".csv") { - var connectTask = socket.ConnectAsync(host, port); - var timeoutTask = Task.Delay(TimeSpan.FromMilliseconds(timeout)); - - var completedTask = await Task.WhenAny(connectTask, timeoutTask); - - if (completedTask == timeoutTask) + var csv = new StringBuilder(); + csv.AppendLine("Address,Type,Anonymity,Country,ASN,Outgoing IP,Alive,Latency,Download Speed"); + foreach (var proxyInfo in proxyInfos) { - return false; + csv.AppendLine($"{proxyInfo.Address},{proxyInfo.Type},{proxyInfo.Anonymity},{proxyInfo.Country},{proxyInfo.Asn},{proxyInfo.OutgoingIp},{proxyInfo.IsAlive},{proxyInfo.Latency},{proxyInfo.DownloadSpeed}"); } - - await connectTask; - return socket.Connected; + await File.WriteAllTextAsync(output, csv.ToString()); } - catch - { - return false; - } - finally + else { - if (socket.Connected) - { - socket.Disconnect(false); - } + AnsiConsole.MarkupLine("[red]Error:[/] Invalid output file format. Please use .csv or .json.[/]"); } } -} \ No newline at end of file +} diff --git a/ProxyChecker.cs b/ProxyChecker.cs new file mode 100644 index 0000000..6aa1947 --- /dev/null +++ b/ProxyChecker.cs @@ -0,0 +1,340 @@ +using Newtonsoft.Json; +using System.Diagnostics; +using System.Net; +using System.Net.Http.Headers; + +/// +/// A class to check the validity of a proxy server. +/// +public class ProxyChecker +{ + private readonly HttpClient _httpClient; + private readonly WebProxy _proxy; + + /// + /// Initializes a new instance of the class. + /// + /// The proxy to use for the checks. + public ProxyChecker(WebProxy proxy) + { + _proxy = proxy; + var handler = new HttpClientHandler + { + Proxy = proxy, + UseProxy = true, + }; + + _httpClient = new HttpClient(handler); + _httpClient.Timeout = TimeSpan.FromMilliseconds(5000); + } + + /// + /// Checks the proxy and returns a object with the results. + /// + /// A object with the results of the check. + public async Task CheckProxyAsync() + { + var proxyInfo = new ProxyInfo + { + Address = _proxy.Address.ToString(), + IsAlive = false, + }; + + try + { + var response = await _httpClient.GetStringAsync("http://httpbin.org/headers"); + var headers = JsonConvert.DeserializeObject(response); + + var publicIpAddress = headers.Origin; + var headerDictionary = headers.Headers; + + proxyInfo.IsAlive = true; + proxyInfo.OutgoingIp = publicIpAddress; + + var ipInfo = await GetIpInfoAsync(publicIpAddress); + if (ipInfo != null) + { + proxyInfo.Country = ipInfo.Country; + proxyInfo.Asn = ipInfo.Asn; + } + + proxyInfo.Type = await GetProxyTypeAsync(); + proxyInfo.Anonymity = GetAnonymity(publicIpAddress, headerDictionary); + proxyInfo.AdditionalHeaders = string.Join(", ", headerDictionary.Keys); + + await TestSpeedAsync(proxyInfo); + + proxyInfo.Score = CalculateScore(proxyInfo); + proxyInfo.IsBlacklisted = await CheckBlacklistAsync(proxyInfo.OutgoingIp); + } + catch + { + // Ignore exceptions + } + + return proxyInfo; + } + + /// + /// Calculates the score of the proxy. + /// + /// The proxy info object. + /// The score of the proxy. + private int CalculateScore(ProxyInfo proxyInfo) + { + if (!proxyInfo.IsAlive) + { + return 0; + } + + var score = 0; + + if (proxyInfo.Anonymity == "Elite") + { + score += 40; + } + else if (proxyInfo.Anonymity == "Anonymous") + { + score += 20; + } + + if (proxyInfo.Latency > 0 && proxyInfo.Latency < 1000) + { + score += (int)(20 * (1 - proxyInfo.Latency / 1000.0)); + } + + if (proxyInfo.DownloadSpeed > 0) + { + score += (int)(20 * (1 - Math.Exp(-proxyInfo.DownloadSpeed / 1000.0))); + } + + if (proxyInfo.Type == "HTTPS" || proxyInfo.Type == "SOCKS5") + { + score += 20; + } + + return Math.Min(100, score); + } + + /// + /// Checks if the proxy is blacklisted. + /// + /// The IP address to check. + /// True if the proxy is blacklisted; otherwise, false. + private async Task CheckBlacklistAsync(string ipAddress) + { + try + { + var reversedIp = string.Join(".", ipAddress.Split('.').Reverse()); + var host = $"{reversedIp}.zen.spamhaus.org"; + var addresses = await Dns.GetHostAddressesAsync(host); + return addresses.Length > 0; + } + catch (Exception ex) + { + Console.WriteLine($"Blacklist check failed: {ex.Message}"); + return false; + } + } + + /// + /// Tests the speed of the proxy. + /// + /// The proxy info object to update. + private async Task TestSpeedAsync(ProxyInfo proxyInfo) + { + try + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + var response = await _httpClient.GetAsync("http://speedtest.tele2.net/1MB.zip"); + var content = await response.Content.ReadAsByteArrayAsync(); + + stopwatch.Stop(); + + proxyInfo.Latency = stopwatch.ElapsedMilliseconds; + proxyInfo.DownloadSpeed = content.Length / 1024.0 / (stopwatch.ElapsedMilliseconds / 1000.0); + } + catch (Exception ex) + { + Console.WriteLine($"Speed test failed: {ex.Message}"); + proxyInfo.Latency = -1; + proxyInfo.DownloadSpeed = -1; + } + } + + /// + /// Gets the geolocation and ASN information for the specified IP address. + /// + /// The IP address to get the information for. + /// A object with the geolocation and ASN information. + private async Task GetIpInfoAsync(string ipAddress) + { + try + { + var response = await new HttpClient().GetStringAsync($"http://ip-api.com/json/{ipAddress}"); + return JsonConvert.DeserializeObject(response); + } + catch (Exception ex) + { + Console.WriteLine($"IP info check failed: {ex.Message}"); + return null; + } + } + + /// + /// Gets the proxy type. + /// + /// The proxy type. + private async Task GetProxyTypeAsync() + { + if (await IsSocks5ProxyAsync()) + { + return "SOCKS5"; + } + + if (await IsSocks4ProxyAsync()) + { + return "SOCKS4"; + } + + if (await IsHttpsProxyAsync()) + { + return "HTTPS"; + } + + return "HTTP"; + } + + /// + /// Checks if the proxy is a SOCKS5 proxy. + /// + /// True if the proxy is a SOCKS5 proxy; otherwise, false. + private async Task IsSocks5ProxyAsync() + { + try + { + var socket = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); + await socket.ConnectAsync(_proxy.Address.Host, _proxy.Address.Port); + var buffer = new byte[] { 5, 1, 0 }; + await socket.SendAsync(buffer, System.Net.Sockets.SocketFlags.None); + var response = new byte[2]; + await socket.ReceiveAsync(response, System.Net.Sockets.SocketFlags.None); + return response[0] == 5 && response[1] == 0; + } + catch (Exception ex) + { + Console.WriteLine($"SOCKS5 check failed: {ex.Message}"); + return false; + } + } + + /// + /// Checks if the proxy is a SOCKS4 proxy. + /// + /// True if the proxy is a SOCKS4 proxy; otherwise, false. + private async Task IsSocks4ProxyAsync() + { + try + { + var socket = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); + await socket.ConnectAsync(_proxy.Address.Host, _proxy.Address.Port); + var buffer = new byte[9]; + buffer[0] = 4; + buffer[1] = 1; + var portBytes = BitConverter.GetBytes((ushort)80); + buffer[2] = portBytes[1]; + buffer[3] = portBytes[0]; + var ipBytes = IPAddress.Parse("8.8.8.8").GetAddressBytes(); + buffer[4] = ipBytes[0]; + buffer[5] = ipBytes[1]; + buffer[6] = ipBytes[2]; + buffer[7] = ipBytes[3]; + buffer[8] = 0; + await socket.SendAsync(buffer, System.Net.Sockets.SocketFlags.None); + var response = new byte[8]; + await socket.ReceiveAsync(response, System.Net.Sockets.SocketFlags.None); + return response[1] == 90; + } + catch (Exception ex) + { + Console.WriteLine($"SOCKS4 check failed: {ex.Message}"); + return false; + } + } + + /// + /// Checks if the proxy is an HTTPS proxy. + /// + /// True if the proxy is an HTTPS proxy; otherwise, false. + private async Task IsHttpsProxyAsync() + { + try + { + var response = await _httpClient.GetAsync("https://www.google.com"); + return response.IsSuccessStatusCode; + } + catch + { + return false; + } + } + + /// + /// Gets the anonymity level of the proxy. + /// + /// The public IP address of the system. + /// The headers from the proxy. + /// The anonymity level of the proxy. + private string GetAnonymity(string publicIpAddress, Dictionary headers) + { + if (headers.ContainsKey("X-Forwarded-For") || headers.ContainsKey("Via")) + { + var forwardedFor = headers.ContainsKey("X-Forwarded-For") ? headers["X-Forwarded-For"] : ""; + if (forwardedFor.Contains(publicIpAddress)) + { + return "Transparent"; + } + return "Anonymous"; + } + + return "Elite"; + } +} + +/// +/// Represents the headers returned by httpbin.org/headers. +/// +public class HttpBinHeaders +{ + /// + /// Gets or sets the origin IP address. + /// + [JsonProperty("origin")] + public string Origin { get; set; } + + /// + /// Gets or sets the headers. + /// + [JsonProperty("headers")] + public Dictionary Headers { get; set; } +} + +/// +/// Represents the geolocation and ASN information for an IP address. +/// +public class IpInfo +{ + /// + /// Gets or sets the country of the IP address. + /// + [JsonProperty("country")] + public string Country { get; set; } + + /// + /// Gets or sets the Autonomous System Number (ASN) of the IP address. + /// + [JsonProperty("as")] + public string Asn { get; set; } +} diff --git a/ProxyInfo.cs b/ProxyInfo.cs new file mode 100644 index 0000000..0fcc938 --- /dev/null +++ b/ProxyInfo.cs @@ -0,0 +1,65 @@ +/// +/// Represents the information about a proxy server. +/// +public class ProxyInfo +{ + /// + /// Gets or sets the proxy address. + /// + public string Address { get; set; } + + /// + /// Gets or sets the proxy type (e.g., HTTP, HTTPS, SOCKS5). + /// + public string Type { get; set; } + + /// + /// Gets or sets the anonymity level (e.g., Elite, Anonymous, Transparent). + /// + public string Anonymity { get; set; } + + /// + /// Gets or sets the country of the proxy. + /// + public string Country { get; set; } + + /// + /// Gets or sets the Autonomous System Number (ASN) of the proxy. + /// + public string Asn { get; set; } + + /// + /// Gets or sets the outgoing IP address of the proxy. + /// + public string OutgoingIp { get; set; } + + /// + /// Gets or sets a value indicating whether the proxy is alive. + /// + public bool IsAlive { get; set; } + + /// + /// Gets or sets any additional headers added by the proxy. + /// + public string AdditionalHeaders { get; set; } + + /// + /// Gets or sets the latency of the proxy in milliseconds. + /// + public long Latency { get; set; } + + /// + /// Gets or sets the download speed of the proxy in KB/s. + /// + public double DownloadSpeed { get; set; } + + /// + /// Gets or sets the score of the proxy. + /// + public int Score { get; set; } + + /// + /// Gets or sets a value indicating whether the proxy is blacklisted. + /// + public bool IsBlacklisted { get; set; } +} From 931e0b554fb69a337f9470a87dbfb1ac84a6222e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 07:58:08 +0000 Subject: [PATCH 2/2] feat: Add advanced proxy checking and scanning features This commit introduces a number of advanced features to the proxy checker, including: - Geolocation filtering - Customizable test targets - Historical reliability tracking (uptime) - Interactive mode with live updates - Port scanning for discovering new proxies The code has also been improved to address feedback from a code review, including fixing a bug in the anonymity check. --- CheckProxy.csproj | 2 + Program.cs | 164 +++++++++++++++++++++++++++++++++------------- ProxyChecker.cs | 58 ++++++++++++++-- ProxyDbContext.cs | 19 ++++++ ProxyInfo.cs | 5 ++ 5 files changed, 197 insertions(+), 51 deletions(-) create mode 100644 ProxyDbContext.cs diff --git a/CheckProxy.csproj b/CheckProxy.csproj index c775d0e..bde93a4 100644 --- a/CheckProxy.csproj +++ b/CheckProxy.csproj @@ -16,6 +16,8 @@ + + diff --git a/Program.cs b/Program.cs index 611b29f..720b311 100644 --- a/Program.cs +++ b/Program.cs @@ -1,6 +1,8 @@ +using IPNetwork2; using Newtonsoft.Json; using Spectre.Console; using System.Net; +using System.Net.Sockets; using System.Text; using System.CommandLine; @@ -20,30 +22,43 @@ public static async Task Main(string[] args) var fileOption = new Option("--file", "A file containing a list of proxies, one per line."); var timeoutOption = new Option("--timeout", () => 5000, "Timeout in milliseconds for each check."); var outputOption = new Option("--output", "The path to the output file (e.g., proxies.csv or proxies.json)."); + var countryOption = new Option("--country", "The country to filter proxies by."); + var targetUrlOption = new Option("--target-url", "The URL to test the proxy against."); var rootCommand = new RootCommand("CheckProxy - A tool to check the validity of proxy servers.") { proxyArgument, fileOption, timeoutOption, - outputOption + outputOption, + countryOption, + targetUrlOption }; - rootCommand.SetHandler(async (proxy, file, timeout, output) => + var scanCommand = new Command("scan", "Scan a range of IP addresses for open proxy ports."); + var ipRangeOption = new Option("--ip-range", "The IP range to scan (e.g., 1.2.3.0/24)."); + scanCommand.AddOption(ipRangeOption); + scanCommand.SetHandler(async (ipRange) => + { + await ScanProxies(ipRange); + }, ipRangeOption); + rootCommand.AddCommand(scanCommand); + + rootCommand.SetHandler(async (proxy, file, timeout, output, country, targetUrl) => { if (proxy != null) { - await CheckSingleProxy(proxy, timeout, output); + await CheckSingleProxy(proxy, timeout, output, country, targetUrl); } else if (file != null) { - await CheckProxiesFromFile(file, timeout, output); + await CheckProxiesFromFile(file, timeout, output, country, targetUrl); } else { AnsiConsole.MarkupLine("[red]Error: You must provide a proxy address or a file.[/]"); } - }, proxyArgument, fileOption, timeoutOption, outputOption); + }, proxyArgument, fileOption, timeoutOption, outputOption, countryOption, targetUrlOption); return await rootCommand.InvokeAsync(args); } @@ -54,14 +69,21 @@ public static async Task Main(string[] args) /// The proxy address string (e.g., "1.2.3.4:8080"). /// The timeout in milliseconds for each check. /// The path to the output file. - static async Task CheckSingleProxy(string proxyAddress, int timeout, string? output) + /// The country to filter proxies by. + /// The URL to test the proxy against. + static async Task CheckSingleProxy(string proxyAddress, int timeout, string? output, string? country, string? targetUrl) { if (IPEndPoint.TryParse(proxyAddress, out _)) { var webProxy = new WebProxy(proxyAddress); - var checker = new ProxyChecker(webProxy); + var checker = new ProxyChecker(webProxy, targetUrl); var proxyInfo = await checker.CheckProxyAsync(); + if (country != null && proxyInfo.Country != country) + { + return; + } + var table = new Table(); table.AddColumn("Property"); table.AddColumn("Value"); @@ -78,6 +100,7 @@ static async Task CheckSingleProxy(string proxyAddress, int timeout, string? out table.AddRow("Download Speed", proxyInfo.DownloadSpeed == -1 ? "N/A" : $"{proxyInfo.DownloadSpeed:F2} KB/s"); table.AddRow("Score", $"{proxyInfo.Score}/100"); table.AddRow("Blacklisted", proxyInfo.IsBlacklisted ? "[red]Yes[/]" : "[green]No[/]"); + table.AddRow("Uptime", $"{proxyInfo.UptimePercentage:F2}%"); AnsiConsole.Write(table); @@ -98,7 +121,9 @@ static async Task CheckSingleProxy(string proxyAddress, int timeout, string? out /// The file containing the list of proxies. /// The timeout in milliseconds for each check. /// The path to the output file. - static async Task CheckProxiesFromFile(FileInfo file, int timeout, string? output) + /// The country to filter proxies by. + /// The URL to test the proxy against. + static async Task CheckProxiesFromFile(FileInfo file, int timeout, string? output, string? country, string? targetUrl) { if (!file.Exists) { @@ -121,54 +146,60 @@ static async Task CheckProxiesFromFile(FileInfo file, int timeout, string? outpu table.AddColumn("Download Speed"); table.AddColumn("Score"); table.AddColumn("Blacklisted"); + table.AddColumn("Uptime"); var proxyInfos = new List(); - var tasks = new List(); var semaphore = new SemaphoreSlim(10); // Limit to 10 concurrent checks - foreach (var proxyAddress in proxies) - { - await semaphore.WaitAsync(); - - tasks.Add(Task.Run(async () => + await AnsiConsole.Live(table) + .StartAsync(async ctx => { - try + var tasks = proxies.Select(async proxyAddress => { - if (IPEndPoint.TryParse(proxyAddress, out _)) + await semaphore.WaitAsync(); + try { - var webProxy = new WebProxy(proxyAddress); - var checker = new ProxyChecker(webProxy); - var proxyInfo = await checker.CheckProxyAsync(); - proxyInfos.Add(proxyInfo); - - table.AddRow( - proxyInfo.Address ?? "N/A", - proxyInfo.Type ?? "N/A", - proxyInfo.Anonymity ?? "N/A", - proxyInfo.Country ?? "N/A", - proxyInfo.Asn ?? "N/A", - proxyInfo.OutgoingIp ?? "N/A", - proxyInfo.IsAlive ? "[green]Yes[/]" : "[red]No[/]", - proxyInfo.Latency == -1 ? "N/A" : $"{proxyInfo.Latency} ms", - proxyInfo.DownloadSpeed == -1 ? "N/A" : $"{proxyInfo.DownloadSpeed:F2} KB/s", - $"{proxyInfo.Score}/100", - proxyInfo.IsBlacklisted ? "[red]Yes[/]" : "[green]No[/]" - ); + if (IPEndPoint.TryParse(proxyAddress, out _)) + { + var webProxy = new WebProxy(proxyAddress); + var checker = new ProxyChecker(webProxy, targetUrl); + var proxyInfo = await checker.CheckProxyAsync(); + + if (country != null && proxyInfo.Country != country) + { + return; + } + + proxyInfos.Add(proxyInfo); + + table.AddRow( + proxyInfo.Address ?? "N/A", + proxyInfo.Type ?? "N/A", + proxyInfo.Anonymity ?? "N/A", + proxyInfo.Country ?? "N/A", + proxyInfo.Asn ?? "N/A", + proxyInfo.OutgoingIp ?? "N/A", + proxyInfo.IsAlive ? "[green]Yes[/]" : "[red]No[/]", + proxyInfo.Latency == -1 ? "N/A" : $"{proxyInfo.Latency} ms", + proxyInfo.DownloadSpeed == -1 ? "N/A" : $"{proxyInfo.DownloadSpeed:F2} KB/s", + $"{proxyInfo.Score}/100", + proxyInfo.IsBlacklisted ? "[red]Yes[/]" : "[green]No[/]", + $"{proxyInfo.UptimePercentage:F2}%" + ); + } + else + { + table.AddRow(proxyAddress, "[red]Invalid[/]", "[red]Invalid[/]", "[red]Invalid[/]", "[red]Invalid[/]", "[red]Invalid[/]", "[red]No[/]", "N/A", "N/A", "0/100", "N/A", "N/A"); + } } - else + finally { - table.AddRow(proxyAddress, "[red]Invalid[/]", "[red]Invalid[/]", "[red]Invalid[/]", "[red]Invalid[/]", "[red]Invalid[/]", "[red]No[/]", "N/A", "N/A", "0/100", "N/A"); + semaphore.Release(); + ctx.Refresh(); } - } - finally - { - semaphore.Release(); - } - })); - } - - await Task.WhenAll(tasks); - AnsiConsole.Write(table); + }); + await Task.WhenAll(tasks); + }); if (output != null) { @@ -206,4 +237,45 @@ static async Task WriteResultsToFile(IEnumerable proxyInfos, string o AnsiConsole.MarkupLine("[red]Error:[/] Invalid output file format. Please use .csv or .json.[/]"); } } + + /// + /// Scans a range of IP addresses for open proxy ports. + /// + /// The IP range to scan. + static async Task ScanProxies(string ipRange) + { + var commonPorts = new[] { 80, 8080, 1080, 3128, 8888 }; + var ips = IPNetwork.Parse(ipRange).ListIPAddress(); + + var table = new Table(); + table.AddColumn("Address"); + table.AddColumn("Port"); + table.AddColumn("Status"); + + await AnsiConsole.Live(table) + .StartAsync(async ctx => + { + var tasks = ips.SelectMany(ip => commonPorts.Select(async port => + { + using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + try + { + await socket.ConnectAsync(ip, port); + if (socket.Connected) + { + table.AddRow(ip.ToString(), port.ToString(), "[green]Open[/]"); + } + } + catch + { + // Ignore exceptions + } + finally + { + ctx.Refresh(); + } + })); + await Task.WhenAll(tasks); + }); + } } diff --git a/ProxyChecker.cs b/ProxyChecker.cs index 6aa1947..08629c7 100644 --- a/ProxyChecker.cs +++ b/ProxyChecker.cs @@ -1,3 +1,4 @@ +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using System.Diagnostics; using System.Net; @@ -10,14 +11,18 @@ public class ProxyChecker { private readonly HttpClient _httpClient; private readonly WebProxy _proxy; + private readonly string _targetUrl; + private string _originalIp; /// /// Initializes a new instance of the class. /// /// The proxy to use for the checks. - public ProxyChecker(WebProxy proxy) + /// The URL to test the proxy against. + public ProxyChecker(WebProxy proxy, string? targetUrl) { _proxy = proxy; + _targetUrl = targetUrl ?? "http://httpbin.org/headers"; var handler = new HttpClientHandler { Proxy = proxy, @@ -26,6 +31,8 @@ public ProxyChecker(WebProxy proxy) _httpClient = new HttpClient(handler); _httpClient.Timeout = TimeSpan.FromMilliseconds(5000); + + _originalIp = GetPublicIpAddressAsync().Result; } /// @@ -42,7 +49,7 @@ public async Task CheckProxyAsync() try { - var response = await _httpClient.GetStringAsync("http://httpbin.org/headers"); + var response = await _httpClient.GetStringAsync(_targetUrl); var headers = JsonConvert.DeserializeObject(response); var publicIpAddress = headers.Origin; @@ -66,6 +73,8 @@ public async Task CheckProxyAsync() proxyInfo.Score = CalculateScore(proxyInfo); proxyInfo.IsBlacklisted = await CheckBlacklistAsync(proxyInfo.OutgoingIp); + + await UpdateUptimeAsync(proxyInfo); } catch { @@ -75,6 +84,30 @@ public async Task CheckProxyAsync() return proxyInfo; } + /// + /// Updates the uptime for the proxy. + /// + /// The proxy info object. + private async Task UpdateUptimeAsync(ProxyInfo proxyInfo) + { + using var db = new ProxyDbContext(); + await db.Database.EnsureCreatedAsync(); + + var check = new ProxyCheck + { + Address = proxyInfo.Address, + IsAlive = proxyInfo.IsAlive, + Timestamp = DateTime.UtcNow + }; + db.ProxyChecks.Add(check); + await db.SaveChangesAsync(); + + var checks = await db.ProxyChecks.Where(c => c.Address == proxyInfo.Address).ToListAsync(); + var totalChecks = checks.Count; + var aliveChecks = checks.Count(c => c.IsAlive); + proxyInfo.UptimePercentage = totalChecks > 0 ? (double)aliveChecks / totalChecks * 100 : 0; + } + /// /// Calculates the score of the proxy. /// @@ -284,15 +317,15 @@ private async Task IsHttpsProxyAsync() /// /// Gets the anonymity level of the proxy. /// - /// The public IP address of the system. + /// The public IP address of the proxy. /// The headers from the proxy. /// The anonymity level of the proxy. - private string GetAnonymity(string publicIpAddress, Dictionary headers) + private string GetAnonymity(string proxyIpAddress, Dictionary headers) { if (headers.ContainsKey("X-Forwarded-For") || headers.ContainsKey("Via")) { var forwardedFor = headers.ContainsKey("X-Forwarded-For") ? headers["X-Forwarded-For"] : ""; - if (forwardedFor.Contains(publicIpAddress)) + if (forwardedFor.Contains(_originalIp)) { return "Transparent"; } @@ -301,6 +334,21 @@ private string GetAnonymity(string publicIpAddress, Dictionary h return "Elite"; } + + private async Task GetPublicIpAddressAsync() + { + try + { + var httpClient = new HttpClient(); + var response = await httpClient.GetStringAsync("https://api.ipify.org"); + return response.Trim(); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to get public IP address: {ex.Message}"); + return string.Empty; + } + } } /// diff --git a/ProxyDbContext.cs b/ProxyDbContext.cs new file mode 100644 index 0000000..0fce8d7 --- /dev/null +++ b/ProxyDbContext.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; + +public class ProxyDbContext : DbContext +{ + public DbSet ProxyChecks { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlite("Data Source=proxy_checks.db"); + } +} + +public class ProxyCheck +{ + public int Id { get; set; } + public string Address { get; set; } + public bool IsAlive { get; set; } + public DateTime Timestamp { get; set; } +} diff --git a/ProxyInfo.cs b/ProxyInfo.cs index 0fcc938..0aac560 100644 --- a/ProxyInfo.cs +++ b/ProxyInfo.cs @@ -62,4 +62,9 @@ public class ProxyInfo /// Gets or sets a value indicating whether the proxy is blacklisted. /// public bool IsBlacklisted { get; set; } + + /// + /// Gets or sets the uptime percentage of the proxy. + /// + public double UptimePercentage { get; set; } }