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