Skip to content

Commit 88c16b5

Browse files
Copilotmonosans
andcommitted
Add CIDR range expansion support for proxy scanning
Co-authored-by: monosans <76561516+monosans@users.noreply.github.com>
1 parent 39e556f commit 88c16b5

5 files changed

Lines changed: 244 additions & 3 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ foldhash = "=0.2.0"
1616
futures = { version = "=0.3.31", optional = true }
1717
hickory-resolver = "=0.25.2"
1818
httpdate = "=1.0.3"
19+
ipnetwork = "=0.21.1"
1920
itertools = "=0.14"
2021
maxminddb = { version = "=0.26.0", features = ["mmap"] }
2122
parking_lot = "=0.12.4"

src/parsers.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::sync::LazyLock;
22

3+
use ipnetwork::IpNetwork;
4+
35
pub static PROXY_REGEX: LazyLock<fancy_regex::Regex> = LazyLock::new(|| {
46
let pattern = r"(?:^|[^0-9A-Za-z])(?:(?P<protocol>https?|socks[45]):\/\/)?(?:(?P<username>[0-9A-Za-z]{1,64}):(?P<password>[0-9A-Za-z]{1,64})@)?(?P<host>[A-Za-z][\-\.A-Za-z]{0,251}[A-Za-z]|[A-Za-z]|(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}):(?P<port>[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?=[^0-9A-Za-z]|$)";
57
fancy_regex::RegexBuilder::new(pattern)
@@ -13,10 +15,100 @@ static IPV4_REGEX: LazyLock<fancy_regex::Regex> = LazyLock::new(|| {
1315
fancy_regex::Regex::new(pattern).unwrap()
1416
});
1517

18+
static CIDR_REGEX: LazyLock<fancy_regex::Regex> = LazyLock::new(|| {
19+
let pattern = r"^\s*(?P<network>(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3})/(?P<prefix>[0-9]|[12][0-9]|3[0-2]):(?P<port>[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])\s*$";
20+
fancy_regex::Regex::new(pattern).unwrap()
21+
});
22+
1623
pub fn parse_ipv4(s: &str) -> Option<String> {
1724
if let Ok(Some(captures)) = IPV4_REGEX.captures(s) {
1825
captures.name("host").map(|capture| capture.as_str().to_owned())
1926
} else {
2027
None
2128
}
2229
}
30+
31+
/// Expands CIDR ranges in text into individual IP:port entries
32+
/// Supports format like "192.168.1.0/24:8080" which expands to all IPs in the range
33+
pub fn expand_cidr_ranges(text: &str) -> String {
34+
let mut result = String::new();
35+
36+
for line in text.lines() {
37+
let line = line.trim();
38+
if let Ok(Some(captures)) = CIDR_REGEX.captures(line) {
39+
// Extract CIDR range and port
40+
if let (Some(network), Some(port)) = (captures.name("network"), captures.name("port")) {
41+
let cidr_str = format!("{}/{}",
42+
network.as_str(),
43+
captures.name("prefix").unwrap().as_str()
44+
);
45+
46+
match cidr_str.parse::<IpNetwork>() {
47+
Ok(network) => {
48+
// Expand the network to individual IPs
49+
for ip in network.iter() {
50+
if ip.is_ipv4() {
51+
result.push_str(&format!("{}:{}\n", ip, port.as_str()));
52+
}
53+
}
54+
}
55+
Err(_) => {
56+
// If parsing fails, keep the original line
57+
result.push_str(line);
58+
result.push('\n');
59+
}
60+
}
61+
} else {
62+
// If regex matches but capture groups are missing, keep the original line
63+
result.push_str(line);
64+
result.push('\n');
65+
}
66+
} else {
67+
// Not a CIDR range, keep the original line
68+
result.push_str(line);
69+
result.push('\n');
70+
}
71+
}
72+
73+
result
74+
}
75+
76+
#[cfg(test)]
77+
mod tests {
78+
use super::*;
79+
80+
#[test]
81+
fn test_cidr_expansion() {
82+
// Test basic CIDR expansion
83+
let input = "192.168.1.0/30:8080";
84+
let result = expand_cidr_ranges(input);
85+
let lines: Vec<&str> = result.trim().split('\n').collect();
86+
87+
assert_eq!(lines.len(), 4);
88+
assert!(lines.contains(&"192.168.1.0:8080"));
89+
assert!(lines.contains(&"192.168.1.1:8080"));
90+
assert!(lines.contains(&"192.168.1.2:8080"));
91+
assert!(lines.contains(&"192.168.1.3:8080"));
92+
}
93+
94+
#[test]
95+
fn test_mixed_input() {
96+
let input = "192.168.1.0/31:8080\n127.0.0.1:9090\ninvalid-line";
97+
let result = expand_cidr_ranges(input);
98+
let lines: Vec<&str> = result.trim().split('\n').collect();
99+
100+
// Should have 2 CIDR-expanded IPs + 1 regular IP + 1 invalid line
101+
assert_eq!(lines.len(), 4);
102+
assert!(lines.contains(&"192.168.1.0:8080"));
103+
assert!(lines.contains(&"192.168.1.1:8080"));
104+
assert!(lines.contains(&"127.0.0.1:9090"));
105+
assert!(lines.contains(&"invalid-line"));
106+
}
107+
108+
#[test]
109+
fn test_single_ip_cidr() {
110+
let input = "10.0.0.1/32:3128";
111+
let result = expand_cidr_ranges(input);
112+
assert_eq!(result.trim(), "10.0.0.1:3128");
113+
}
114+
}

src/scraper.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
HashSet,
1010
config::{Config, Source},
1111
http,
12-
parsers::PROXY_REGEX,
12+
parsers::{PROXY_REGEX, expand_cidr_ranges},
1313
proxy::{Proxy, ProxyType},
1414
utils::pretty_error,
1515
};
@@ -58,8 +58,11 @@ async fn scrape_one(
5858
}
5959
};
6060

61+
// Expand CIDR ranges to individual IP:port entries
62+
let expanded_text = expand_cidr_ranges(&text);
63+
6164
let mut matches = Vec::new();
62-
for (i, maybe_capture) in PROXY_REGEX.captures_iter(&text).enumerate() {
65+
for (i, maybe_capture) in PROXY_REGEX.captures_iter(&expanded_text).enumerate() {
6366
if config.scraping.max_proxies_per_source != 0
6467
&& i >= config.scraping.max_proxies_per_source
6568
{
@@ -116,7 +119,7 @@ async fn scrape_one(
116119
}
117120

118121
drop(config);
119-
drop(text);
122+
drop(expanded_text);
120123

121124
#[cfg(feature = "tui")]
122125
for proto in seen_protocols {

test_config.toml

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# Enable debug logging (shows detailed checking process)
2+
# Warning: Produces very verbose output
3+
debug = false
4+
5+
6+
[scraping]
7+
# Maximum proxies to collect per source (0 = unlimited)
8+
# Helps skip unreliable sources with too many proxies
9+
max_proxies_per_source = 100000
10+
11+
# Request timeout for fetching proxy sources (seconds)
12+
# Higher values may find more sources but take longer
13+
timeout = 60.0
14+
connect_timeout = 5.0
15+
16+
# HTTP(S),SOCKS4 or SOCKS5 proxy used for fetching sources (e.g., "socks5://user:pass@host:port"). Leave empty to disable.
17+
proxy = ""
18+
19+
# User-Agent header for scraping requests
20+
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
21+
22+
23+
[checking]
24+
# URL for checking proxy functionality
25+
# httpbin-compatible: Returns JSON with IP info for ASN/geo data
26+
# plain-text: Returns just IP address for basic connectivity
27+
# Examples:
28+
# "https://httpbin.org/ip" - JSON with "origin" key. Full featured checking.
29+
# "https://api.ipify.org" - Simple IP return. Full featured checking.
30+
# "https://google.com" - Basic connect/read check
31+
# "" - Skip checking (scrape only)
32+
check_url = "https://api.ipify.org"
33+
34+
# Concurrent proxy checks (adjust based on RAM/network capacity)
35+
# Start low and increase gradually if system handles it well
36+
max_concurrent_checks = 1024
37+
38+
# Proxy response timeout (seconds)
39+
# Higher = more working proxies found, slower checking
40+
# Lower = faster checking, may miss slower proxies
41+
timeout = 60.0
42+
connect_timeout = 5.0
43+
44+
# User-Agent header for proxy check requests
45+
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
46+
47+
48+
[output]
49+
# Output directory (Docker ignores this setting)
50+
path = "./out"
51+
52+
# Sort by response time (true) or IP address (false)
53+
sort_by_speed = true
54+
55+
56+
# Plain text output (.txt files)
57+
[output.txt]
58+
enabled = false
59+
60+
61+
# JSON output with metadata (.json files)
62+
[output.json]
63+
enabled = false
64+
65+
# Add ASN (network provider) info to JSON output
66+
# Uses offline MaxMind database
67+
include_asn = true
68+
69+
# Add geolocation (country/city) info to JSON output
70+
# Uses offline MaxMind database
71+
include_geolocation = true
72+
73+
74+
# Proxy sources configuration
75+
# Add local files: ["./my_proxies.txt"] or URLs
76+
# Sources are fetched in parallel for speed
77+
78+
enabled = true
79+
urls = ["file:///tmp/test_cidr.txt"]
80+
# Local file examples:
81+
# "./my_http_proxies.txt",
82+
# "/home/user/my_http_proxies.txt",
83+
# "C:/Users/user/Desktop/my_http_proxies.txt",
84+
# "file:///home/user/my_http_proxies.txt",
85+
86+
# Advanced URL configuration examples (with basic auth or custom headers):
87+
# HTTP Basic Auth example:
88+
# { url = "https://some.api/endpoint", basic_auth = { username = "user", password = "password123" } },
89+
# Custom headers example:
90+
# { url = "https://some.api/endpoint", headers = { Authorization = "Bearer YOUR_API_KEY" } },
91+
92+
"https://api.proxyscrape.com/v3/free-proxy-list/get?request=getproxies&protocol=http",
93+
"https://api.proxyscrape.com/v3/free-proxy-list/get?request=getproxies&protocol=https",
94+
"https://raw.githubusercontent.com/proxifly/free-proxy-list/refs/heads/main/proxies/protocols/http/data.txt",
95+
"https://raw.githubusercontent.com/proxifly/free-proxy-list/refs/heads/main/proxies/protocols/https/data.txt",
96+
"https://raw.githubusercontent.com/roosterkid/openproxylist/refs/heads/main/HTTPS_RAW.txt",
97+
"https://raw.githubusercontent.com/sunny9577/proxy-scraper/refs/heads/master/generated/http_proxies.txt",
98+
"https://raw.githubusercontent.com/TheSpeedX/PROXY-List/refs/heads/master/http.txt",
99+
]
100+
101+
[scraping.socks4]
102+
enabled = false
103+
urls = [
104+
# Local file examples:
105+
# "./my_socks4_proxies.txt",
106+
# "/home/user/my_socks4_proxies.txt",
107+
# "C:/Users/user/Desktop/my_socks4_proxies.txt",
108+
# "file:///home/user/my_socks4_proxies.txt",
109+
110+
# Advanced URL configuration examples (with basic auth or custom headers):
111+
# HTTP Basic Auth example:
112+
# { url = "https://some.api/endpoint", basic_auth = { username = "user", password = "password123" } },
113+
# Custom headers example:
114+
# { url = "https://some.api/endpoint", headers = { Authorization = "Bearer YOUR_API_KEY" } },
115+
116+
"https://api.proxyscrape.com/v3/free-proxy-list/get?request=getproxies&protocol=socks4",
117+
"https://raw.githubusercontent.com/proxifly/free-proxy-list/refs/heads/main/proxies/protocols/socks4/data.txt",
118+
"https://raw.githubusercontent.com/roosterkid/openproxylist/refs/heads/main/SOCKS4_RAW.txt",
119+
"https://raw.githubusercontent.com/sunny9577/proxy-scraper/refs/heads/master/generated/socks4_proxies.txt",
120+
"https://raw.githubusercontent.com/TheSpeedX/PROXY-List/refs/heads/master/socks4.txt",
121+
]
122+
123+
[scraping.socks5]
124+
enabled = false
125+
urls = [
126+
# Local file examples:
127+
# "./my_socks5_proxies.txt",
128+
# "/home/user/my_socks5_proxies.txt",
129+
# "C:/Users/user/Desktop/my_socks5_proxies.txt",
130+
# "file:///home/user/my_socks5_proxies.txt",
131+
132+
# Advanced URL configuration examples (with basic auth or custom headers):
133+
# HTTP Basic Auth example:
134+
# { url = "https://some.api/endpoint", basic_auth = { username = "user", password = "password123" } },
135+
# Custom headers example:
136+
# { url = "https://some.api/endpoint", headers = { Authorization = "Bearer YOUR_API_KEY" } },
137+
138+
"https://api.proxyscrape.com/v3/free-proxy-list/get?request=getproxies&protocol=socks5",
139+
"https://raw.githubusercontent.com/hookzof/socks5_list/refs/heads/master/proxy.txt",
140+
"https://raw.githubusercontent.com/proxifly/free-proxy-list/refs/heads/main/proxies/protocols/socks5/data.txt",
141+
"https://raw.githubusercontent.com/roosterkid/openproxylist/refs/heads/main/SOCKS5_RAW.txt",
142+
"https://raw.githubusercontent.com/sunny9577/proxy-scraper/refs/heads/master/generated/socks5_proxies.txt",
143+
"https://raw.githubusercontent.com/TheSpeedX/PROXY-List/refs/heads/master/socks5.txt",
144+
]

0 commit comments

Comments
 (0)