Session de développement et debugging avec Cline (Claude Sonnet).
| Fichier | Action |
|---|---|
main.py |
Bug fix saisie pays + cache SQLite + détachement serveur + options CLI |
proxy_scraper.py |
Barre de progression géolocalisation |
proxy_chain.py |
Fix SOCKS5 + écoute 0.0.0.0 |
proxy_cache.py |
Nouveau — Cache SQLite + gestion PID |
Entrez le code pays (ex: FR, US, DE): ❌ Code pays '' non disponible.
Prompt.ask() de Rich ne captait pas l'entrée clavier dans certains terminaux, retournant une chaîne vide ''.
# AVANT (bugué)
choice = Prompt.ask("[bold cyan]Entrez le code pays[/bold cyan]...").strip().upper()
# APRÈS (corrigé)
console.print("[bold cyan]Entrez le code pays[/bold cyan] (...): ", end="")
choice = input().strip().upper()Améliorations supplémentaires :
- Gestion
EOFError/KeyboardInterrupt→sys.exit(0) - Message explicite si saisie vide (au lieu de boucler silencieusement)
À chaque lancement, resolve_countries_batch interrogeait ip-api.com pour tous les proxies, ce qui prenait plusieurs minutes.
- Base :
~/.torproxy-chain/proxy_cache.db - TTL : 24 heures
- Schema :
CREATE TABLE proxies ( host TEXT NOT NULL, port INTEGER NOT NULL, proto TEXT DEFAULT 'socks5', country TEXT DEFAULT '', country_name TEXT DEFAULT '', cached_at REAL NOT NULL, PRIMARY KEY (host, port) )
load_cached_proxies(ttl=86400) # Charge les proxies valides du cache
save_proxies_to_cache(proxies) # Upsert dans SQLite
count_cached_proxies() # Nombre de proxies en cache
cache_age_hours() # Âge du cache en heures (None si expiré)
clear_cache() # Vide tout le cache1. Charger le cache SQLite
2. Appliquer les pays connus aux proxies scrapés (instantané)
3. Résoudre via ip-api.com UNIQUEMENT les proxies inconnus
4. Sauvegarder les nouvelles résolutions
→ Au 2ème lancement : géolocalisation quasi-instantanée.
python main.py --clear-cache # Forcer une re-géolocalisation complèteUtilise rich.progress pour afficher :
🌍 Géolocalisation 3/12 batches... ████████░░░░ 25% 287 résolus
Composants :
SpinnerColumn()— animationTextColumnavec description dynamiquebatch X/YBarColumn()— barre de progressionTaskProgressColumn()— pourcentage- Champ custom
resolved— compteur de proxies géolocalisés
La barre n'apparaît que si des proxies sont à résoudre (proxies déjà en cache = pas de barre).
Dans la boucle interactive, [d] quitte le menu sans arrêter le serveur SOCKS5 :
╭─ ⛓️ Détaché ───────────────────────────────────╮
│ Le serveur tourne en arrière-plan. │
│ │
│ 🔌 Proxy SOCKS5 actif : socks5://0.0.0.0:10800 │
│ 🆔 PID : 12345 │
│ │
│ Pour arrêter plus tard : │
│ python main.py --kill │
│ ou : kill 12345 │
╰───────────────────────────────────────────────────╯
Après détachement, le processus continue en boucle silencieuse (time.sleep(60)).
- Chemin :
~/.torproxy-chain/torproxy.pid - Contenu :
12345 ← PID 10800 ← port local FR ← pays sélectionné
write_pid(pid, local_port, country) # Écrit le PID au démarrage du serveur
read_pid() # Lit + vérifie que le processus existe encore
clear_pid() # Supprime le fichierpython main.py --kill # Envoie SIGTERM au serveur détaché + nettoie le PIDcurl: (97) Can't complete SOCKS5 connection to ipconfig.io. (4)
Code SOCKS5 4 = Host unreachable.
Bug 1 — sock.recv(N) non fiable
TCP peut fragmenter les données → recv(10) peut retourner 3 octets.
Le handshake SOCKS5 était corrompu de façon intermittente.
Bug 2 — Parsing réponse CONNECT incorrect
# ANCIEN CODE (bugué)
resp = sock.recv(10) # suppose toujours IPv4 + 10 octets
if resp[3] == 0x03: # domain name
sock.recv(domain_len + 2) # ← lit trop d'octets (déjà consommés !)La réponse CONNECT SOCKS5 a une longueur variable selon ATYP :
0x01(IPv4) : 4 header + 4 addr + 2 port = 10 octets0x03(domain) : 4 header + 1+N addr + 2 port = variable0x04(IPv6) : 4 header + 16 addr + 2 port = 22 octets
Ajout de _recvall(sock, n) :
def _recvall(sock, n):
data = b""
while len(data) < n:
chunk = sock.recv(n - len(data))
if not chunk:
raise ConnectionError(f"Socket closed after {len(data)}/{n} bytes")
data += chunk
return data_socks5_handshake (serveur local) — utilise _recvall partout.
_socks5_connect (client vers exit proxy) — parsing correct :
header = _recvall(sock, 4) # VER, REP, RSV, ATYP
if header[1] != 0x00: raise ConnectionError()
atyp = header[3]
if atyp == 0x01: _recvall(sock, 4 + 2) # IPv4
elif atyp == 0x03: _recvall(sock, _recvall(sock, 1)[0] + 2) # domain
elif atyp == 0x04: _recvall(sock, 16 + 2) # IPv6_socks4_connect — _recvall(sock, 8) au lieu de recv(8).
Réponse : OUI, aucune fuite.
| Nœud | DNS | TCP |
|---|---|---|
| Connexion locale → Tor | Aucun (127.0.0.1) | Local |
| Tor → Exit proxy | Aucun (IP numérique du scraper) | Via Tor ✅ |
| Exit proxy → Internet | Par le proxy exit (ATYP=0x03) | Via Tor ✅ |
Points clés :
rdns=Truesur PySocks → Tor gère le DNS si nécessaire_socks5_connectenvoie toujoursATYP=0x03(domain name) → pas de résolution localesocks5h://dansrequests→ même garantie pourresolve_countries_batch
# proxy_chain.py
LOCAL_BIND = "0.0.0.0" # était "127.0.0.1"Le serveur SOCKS5 est désormais accessible depuis le réseau local.
Utilisation depuis un autre appareil :
socks5://<IP-machine>:10800
get_chained_ip() utilise toujours 127.0.0.1 en interne (on ne peut pas connecter à 0.0.0.0).
torproxy-chain/
├── main.py ← CLI + logique principale (cache + détachement)
├── proxy_scraper.py ← Scraping + géolocalisation (avec barre de progression)
├── proxy_chain.py ← Serveur SOCKS5 local (fix handshake, 0.0.0.0)
├── proxy_cache.py ← NOUVEAU : cache SQLite + gestion PID
├── tor_manager.py ← Gestion de Tor
├── README.md
├── requirements.txt
└── SESSION_LOG.md ← Ce fichier
Cache & PID :
~/.torproxy-chain/
├── proxy_cache.db ← SQLite (proxies géolocalisés, TTL 24h)
└── torproxy.pid ← PID du serveur détaché (optionnel)
# Lancer
python main.py # Mode interactif
python main.py --country FR # Pays direct
python main.py --list-countries # Lister les pays
# Options
python main.py --skip-verify # Ne pas vérifier les proxies
python main.py --local-port 1080 # Changer le port local
python main.py --verbose # Mode verbeux
# Gestion
python main.py --kill # Arrêter le serveur détaché
python main.py --clear-cache # Vider le cache SQLite
# Tester le proxy
curl --proxy socks5h://127.0.0.1:10800 https://ipinfo.io
curl --proxy socks5h://<IP>:10800 https://ipinfo.io # depuis un autre appareil| Touche | Action |
|---|---|
r |
Rotation du proxy de sortie |
n |
Nouveau circuit Tor |
i |
Vérifier l'IP actuelle |
d |
Détacher (quitter sans arrêter le serveur) |
q |
Quitter et arrêter tout |