Skip to content

Commit 47991c0

Browse files
authored
Add files via upload
1 parent 6c7eff9 commit 47991c0

3 files changed

Lines changed: 1033 additions & 0 deletions

File tree

cdn_path_finder.py

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
cdn_path_finder.py — لایه ۲: یافتن IP های باز CDN برای عبور از فیلتر
5+
6+
این ماژول IP های CDN هایی را که دولت‌ها باز نگه می‌دارند
7+
اسکن می‌کند تا مسیری برای relay کردن ترافیک پیدا کند.
8+
9+
استفاده:
10+
python3 cdn_path_finder.py --scan-cloudflare --target your-server.com
11+
python3 cdn_path_finder.py --scan-all --target your-server.com
12+
"""
13+
14+
import ipaddress
15+
import json
16+
import random
17+
import socket
18+
import ssl
19+
import struct
20+
import sys
21+
import threading
22+
import time
23+
import urllib.request
24+
from concurrent.futures import ThreadPoolExecutor, as_completed
25+
from typing import Dict, List, Optional, Tuple
26+
27+
CONNECT_TIMEOUT = 3.0
28+
HTTP_TIMEOUT = 5.0
29+
MAX_WORKERS = 64
30+
RESULTS_FILE = "cdn_open_ips.json"
31+
32+
# ─────────────────────────────────────────────────────────────
33+
# رنج‌های IP عمومی CDN های اصلی
34+
# منبع: https://www.cloudflare.com/ips/
35+
# https://api.fastly.com/public-ip-list
36+
# https://github.com/nicholasMeadows/AkamaiIPRanges
37+
# ─────────────────────────────────────────────────────────────
38+
CDN_RANGES: Dict[str, List[str]] = {
39+
"cloudflare": [
40+
"103.21.244.0/22", "103.22.200.0/22", "103.31.4.0/22",
41+
"104.16.0.0/13", "104.24.0.0/14", "108.162.192.0/18",
42+
"131.0.72.0/22", "141.101.64.0/18", "162.158.0.0/15",
43+
"172.64.0.0/13", "173.245.48.0/20", "188.114.96.0/20",
44+
"190.93.240.0/20", "197.234.240.0/22", "198.41.128.0/17",
45+
],
46+
"fastly": [
47+
"23.235.32.0/20", "43.249.72.0/22", "103.244.50.0/24",
48+
"103.245.222.0/23","103.245.224.0/24","104.156.80.0/20",
49+
"140.248.64.0/18", "140.248.128.0/17","146.75.0.0/17",
50+
"151.101.0.0/16", "157.52.192.0/18", "167.82.0.0/17",
51+
"167.82.128.0/20", "167.82.160.0/20", "167.82.224.0/20",
52+
"172.111.64.0/18", "185.31.16.0/22", "199.27.72.0/21",
53+
"199.232.0.0/16",
54+
],
55+
"akamai": [
56+
"2.16.0.0/13", "23.0.0.0/12", "23.192.0.0/11",
57+
"69.192.0.0/16", "72.246.0.0/15", "88.221.0.0/16",
58+
"92.122.0.0/15", "96.6.0.0/15", "184.24.0.0/13",
59+
"184.50.0.0/15", "184.84.0.0/14",
60+
],
61+
"bunny": [
62+
"185.181.48.0/22", "192.189.0.0/16",
63+
],
64+
}
65+
66+
# ─────────────────────────────────────────────────────────────
67+
# رنگ‌بندی ترمینال
68+
# ─────────────────────────────────────────────────────────────
69+
def _c(text: str, code: str) -> str:
70+
if not sys.stdout.isatty():
71+
return text
72+
return f"\033[{code}m{text}\033[0m"
73+
74+
ok = lambda t: _c(t, "92")
75+
deny = lambda t: _c(t, "91")
76+
warn = lambda t: _c(t, "93")
77+
info = lambda t: _c(t, "96")
78+
79+
80+
# ─────────────────────────────────────────────────────────────
81+
# یافتن IP های باز
82+
# ─────────────────────────────────────────────────────────────
83+
def tcp_open(ip: str, port: int = 443) -> bool:
84+
"""بررسی اینکه پورت TCP روی این IP باز است."""
85+
try:
86+
with socket.create_connection((ip, port), timeout=CONNECT_TIMEOUT):
87+
return True
88+
except Exception:
89+
return False
90+
91+
92+
def https_reachable(ip: str, host_header: str = "", path: str = "/") -> Tuple[bool, int, float]:
93+
"""
94+
یک درخواست HTTPS به IP می‌فرستد و کد پاسخ + تأخیر را برمی‌گرداند.
95+
host_header می‌تواند برای domain fronting استفاده شود.
96+
"""
97+
ctx = ssl.create_default_context()
98+
ctx.check_hostname = False
99+
ctx.verify_mode = ssl.CERT_NONE
100+
t0 = time.monotonic()
101+
try:
102+
conn = socket.create_connection((ip, 443), timeout=HTTP_TIMEOUT)
103+
tls = ctx.wrap_socket(conn, server_hostname=host_header or ip)
104+
header = host_header or ip
105+
req = (
106+
f"GET {path} HTTP/1.1\r\n"
107+
f"Host: {header}\r\n"
108+
"User-Agent: curl/7.88.1\r\n"
109+
"Accept: */*\r\n"
110+
"Connection: close\r\n\r\n"
111+
).encode()
112+
tls.sendall(req)
113+
resp = tls.recv(4096).decode(errors="ignore")
114+
tls.close()
115+
latency = (time.monotonic() - t0) * 1000
116+
code = 0
117+
first = resp.split("\r\n", 1)[0]
118+
if "HTTP/" in first:
119+
try:
120+
code = int(first.split()[1])
121+
except Exception:
122+
pass
123+
return True, code, latency
124+
except Exception:
125+
return False, 0, (time.monotonic() - t0) * 1000
126+
127+
128+
def expand_range(cidr: str, max_ips: int = 256) -> List[str]:
129+
"""یک رنج CIDR را به لیست IP تبدیل می‌کند (با shuffle برای تنوع)."""
130+
net = ipaddress.ip_network(cidr, strict=False)
131+
ips = [str(h) for h in net.hosts()]
132+
if len(ips) > max_ips:
133+
random.shuffle(ips)
134+
ips = ips[:max_ips]
135+
return ips
136+
137+
138+
def scan_cdn(
139+
cdn_name: str,
140+
ranges: List[str],
141+
port: int = 443,
142+
host_header: str = "",
143+
max_ips: int = 50,
144+
workers: int = MAX_WORKERS,
145+
) -> List[Dict]:
146+
"""
147+
IP های یک CDN را اسکن می‌کند و نتایج را بر اساس تأخیر مرتب می‌کند.
148+
"""
149+
all_ips: List[str] = []
150+
for cidr in ranges:
151+
all_ips.extend(expand_range(cidr, max_ips // len(ranges) + 1))
152+
random.shuffle(all_ips)
153+
all_ips = all_ips[:max_ips]
154+
155+
results = []
156+
lock = threading.Lock()
157+
158+
def probe(ip: str):
159+
reachable, code, latency = https_reachable(ip, host_header, "/")
160+
if reachable:
161+
entry = {"ip": ip, "cdn": cdn_name, "code": code, "latency_ms": round(latency, 1)}
162+
with lock:
163+
results.append(entry)
164+
print(f" {ok('✓')} {ip:>16} HTTP {code} {latency:6.0f} ms")
165+
166+
print(info(f"\n[{cdn_name}] اسکن {len(all_ips)} IP روی پورت {port} ..."))
167+
with ThreadPoolExecutor(max_workers=workers) as ex:
168+
futures = {ex.submit(probe, ip): ip for ip in all_ips}
169+
for f in as_completed(futures):
170+
_ = f # خطاها داخل probe مدیریت می‌شوند
171+
172+
results.sort(key=lambda r: r["latency_ms"])
173+
return results
174+
175+
176+
# ─────────────────────────────────────────────────────────────
177+
# Domain Fronting — پوشش ترافیک با یک دامنه مجاز
178+
# ─────────────────────────────────────────────────────────────
179+
def domain_front_test(
180+
cdn_ip: str,
181+
front_domain: str,
182+
real_host: str,
183+
path: str = "/",
184+
) -> Tuple[bool, int, str]:
185+
"""
186+
Domain Fronting:
187+
- SNI و Host را front_domain می‌گذارد (دامنه‌ای که فیلتر نیست)
188+
- اما درخواست به real_host هدایت می‌شود (اگر هر دو روی یک CDN باشند)
189+
190+
این تکنیک روی CDN هایی که allow می‌کنند کار می‌کند.
191+
"""
192+
ctx = ssl.create_default_context()
193+
ctx.check_hostname = False
194+
ctx.verify_mode = ssl.CERT_NONE
195+
try:
196+
conn = socket.create_connection((cdn_ip, 443), timeout=HTTP_TIMEOUT)
197+
# SNI = front_domain (دامنه غیر فیلتر)
198+
tls = ctx.wrap_socket(conn, server_hostname=front_domain)
199+
# Host header = real_host (سرور واقعی شما)
200+
req = (
201+
f"GET {path} HTTP/1.1\r\n"
202+
f"Host: {real_host}\r\n"
203+
"User-Agent: curl/7.88.1\r\n"
204+
"Connection: close\r\n\r\n"
205+
).encode()
206+
tls.sendall(req)
207+
resp = tls.recv(4096).decode(errors="ignore")
208+
tls.close()
209+
code = 0
210+
first = resp.split("\r\n", 1)[0]
211+
if "HTTP/" in first:
212+
try:
213+
code = int(first.split()[1])
214+
except Exception:
215+
pass
216+
success = code in (200, 301, 302, 204)
217+
return success, code, resp.split("\r\n")[0]
218+
except Exception as e:
219+
return False, 0, str(e)
220+
221+
222+
# ─────────────────────────────────────────────────────────────
223+
# Cloudflare Worker به عنوان relay پروکسی
224+
# ─────────────────────────────────────────────────────────────
225+
WORKER_SCRIPT_TEMPLATE = """\
226+
// Cloudflare Worker — relay proxy
227+
// در داشبورد Cloudflare Workers ایجاد کنید
228+
// این worker درخواست را به سرور شما forward می‌کند
229+
230+
const TARGET = "https://{target_host}";
231+
232+
export default {{
233+
async fetch(request, env) {{
234+
const url = new URL(request.url);
235+
const targetUrl = TARGET + url.pathname + url.search;
236+
237+
const newRequest = new Request(targetUrl, {{
238+
method: request.method,
239+
headers: request.headers,
240+
body: request.body,
241+
}});
242+
243+
return fetch(newRequest);
244+
}},
245+
}};
246+
"""
247+
248+
def generate_worker_script(target_host: str) -> str:
249+
return WORKER_SCRIPT_TEMPLATE.format(target_host=target_host)
250+
251+
252+
# ─────────────────────────────────────────────────────────────
253+
# ذخیره و بارگذاری نتایج
254+
# ─────────────────────────────────────────────────────────────
255+
def save_results(results: List[Dict], path: str = RESULTS_FILE) -> None:
256+
with open(path, "w", encoding="utf-8") as f:
257+
json.dump(results, f, indent=2, ensure_ascii=False)
258+
print(info(f"\n[ذخیره] {len(results)} IP در {path}"))
259+
260+
261+
def load_results(path: str = RESULTS_FILE) -> List[Dict]:
262+
try:
263+
with open(path, encoding="utf-8") as f:
264+
return json.load(f)
265+
except Exception:
266+
return []
267+
268+
269+
def best_ips(results: List[Dict], top_n: int = 5) -> List[Dict]:
270+
"""سریع‌ترین IP های پیدا شده را برمی‌گرداند."""
271+
return sorted(results, key=lambda r: r["latency_ms"])[:top_n]
272+
273+
274+
# ─────────────────────────────────────────────────────────────
275+
# رابط خط فرمان
276+
# ─────────────────────────────────────────────────────────────
277+
def main() -> int:
278+
import argparse
279+
p = argparse.ArgumentParser(description="یافتن IP های باز CDN برای عبور از فیلتر")
280+
p.add_argument("--scan-cloudflare", action="store_true", help="اسکن IP های Cloudflare")
281+
p.add_argument("--scan-fastly", action="store_true", help="اسکن IP های Fastly")
282+
p.add_argument("--scan-akamai", action="store_true", help="اسکن IP های Akamai")
283+
p.add_argument("--scan-all", action="store_true", help="اسکن همه CDN ها")
284+
p.add_argument("--target", default="", help="دامنه سرور شما")
285+
p.add_argument("--front", default="", help="دامنه front برای domain fronting")
286+
p.add_argument("--max-ips", type=int, default=60,help="حداکثر IP برای اسکن هر CDN")
287+
p.add_argument("--show-best", action="store_true", help="نمایش بهترین IP های ذخیره‌شده")
288+
p.add_argument("--gen-worker", action="store_true", help="تولید اسکریپت Cloudflare Worker")
289+
args = p.parse_args()
290+
291+
if args.show_best:
292+
results = load_results()
293+
top = best_ips(results, 10)
294+
print(info(f"\n{'IP':>18} {'CDN':<12} {'Code':>5} {'Latency':>10}"))
295+
print("-" * 56)
296+
for r in top:
297+
print(f" {r['ip']:>16} {r['cdn']:<12} {r['code']:>5} {r['latency_ms']:>8.0f} ms")
298+
return 0
299+
300+
if args.gen_worker:
301+
if not args.target:
302+
print(deny("[خطا] --target لازم است"))
303+
return 1
304+
script = generate_worker_script(args.target)
305+
fname = "worker_relay.js"
306+
with open(fname, "w") as f:
307+
f.write(script)
308+
print(ok(f"[Worker] اسکریپت در {fname} ذخیره شد"))
309+
print(info("در داشبورد Cloudflare Workers → Create Worker بارگذاری کنید"))
310+
return 0
311+
312+
to_scan = []
313+
if args.scan_all or args.scan_cloudflare:
314+
to_scan.append(("cloudflare", CDN_RANGES["cloudflare"]))
315+
if args.scan_all or args.scan_fastly:
316+
to_scan.append(("fastly", CDN_RANGES["fastly"]))
317+
if args.scan_all or args.scan_akamai:
318+
to_scan.append(("akamai", CDN_RANGES["akamai"]))
319+
320+
if not to_scan:
321+
p.print_help()
322+
return 1
323+
324+
all_results = []
325+
for cdn_name, ranges in to_scan:
326+
found = scan_cdn(
327+
cdn_name, ranges,
328+
host_header=args.front or args.target,
329+
max_ips=args.max_ips,
330+
)
331+
all_results.extend(found)
332+
print(info(f"[{cdn_name}] {len(found)} IP باز پیدا شد"))
333+
334+
if all_results:
335+
save_results(all_results)
336+
print(info("\nبهترین IP ها:"))
337+
for r in best_ips(all_results, 5):
338+
print(f" {ok('✓')} {r['ip']:>16} {r['cdn']:<12} {r['latency_ms']:.0f} ms")
339+
340+
# تست domain fronting اگر target و front داده شده
341+
if args.target and args.front and all_results:
342+
best = all_results[0]["ip"]
343+
print(info(f"\n[Domain Fronting] تست با {best} ..."))
344+
success, code, first_line = domain_front_test(
345+
best, args.front, args.target
346+
)
347+
if success:
348+
print(ok(f" ✓ Domain fronting کار کرد! HTTP {code}"))
349+
else:
350+
print(warn(f" ⚠ Domain fronting کار نکرد: {first_line}"))
351+
352+
return 0
353+
354+
355+
if __name__ == "__main__":
356+
raise SystemExit(main())

0 commit comments

Comments
 (0)