|
|
from flask import Flask, jsonify, render_template_string |
|
|
import subprocess |
|
|
import threading |
|
|
import queue |
|
|
import requests |
|
|
import psycopg2 |
|
|
from psycopg2 import pool |
|
|
import time |
|
|
from datetime import datetime |
|
|
import re |
|
|
|
|
|
|
|
|
MASSCAN_RATE = 10000 |
|
|
TARGET_PORT = 11434 |
|
|
TARGET_RANGE = "0.0.0.0/0" |
|
|
NUM_CHECKERS = 50 |
|
|
PROCESSED_IPS_LIMIT = 1000000 |
|
|
|
|
|
|
|
|
DB_URL = "postgresql://neondb_owner:npg_r7oFwW5XsmtG@ep-patient-lake-agwy3kca-pooler.c-2.eu-central-1.aws.neon.tech/ollama?sslmode=require" |
|
|
|
|
|
|
|
|
processed_ips = set() |
|
|
ip_queue = queue.Queue() |
|
|
active_checks = 0 |
|
|
stats = { |
|
|
"found": 0, |
|
|
"verified_models": 0, |
|
|
"verification_failed": 0, |
|
|
"api_success": 0, |
|
|
"api_failed": 0, |
|
|
"cache_clears": 0, |
|
|
"last_scan_time": time.time(), |
|
|
} |
|
|
|
|
|
|
|
|
db_pool = psycopg2.pool.SimpleConnectionPool( |
|
|
minconn=1, |
|
|
maxconn=10, |
|
|
dsn=DB_URL |
|
|
) |
|
|
|
|
|
|
|
|
app = Flask(__name__) |
|
|
|
|
|
|
|
|
@app.route('/') |
|
|
def home(): |
|
|
return render_template_string(''' |
|
|
<!DOCTYPE html> |
|
|
<html> |
|
|
<head> |
|
|
<title>Niansuh Masscan - Live Stats</title> |
|
|
<meta http-equiv="refresh" content="2"> |
|
|
<style> |
|
|
body { font-family: monospace; background: #000; color: #0f0; } |
|
|
.stats { font-size: 1.2em; } |
|
|
.scanned { color: #ff0; } |
|
|
.verified { color: #0ff; } |
|
|
.queue { color: #f0f; } |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<h1>NIANSUH MASSCAN - LIVE STATS</h1> |
|
|
<div class="stats"> |
|
|
<p>π <span class="scanned">Scanned IPs: {{ stats.found }}</span></p> |
|
|
<p>β
<span class="verified">Verified Ollama: {{ stats.verified_models }}</span></p> |
|
|
<p>π <span class="queue">Queue: {{ queue_size }}</span></p> |
|
|
<p>π Active Checks: {{ active_checks }}</p> |
|
|
<p>ποΈ In Memory: {{ in_memory }}</p> |
|
|
<p>β±οΈ Uptime: {{ uptime }} seconds</p> |
|
|
</div> |
|
|
</body> |
|
|
</html> |
|
|
''', stats=stats, queue_size=ip_queue.qsize(), active_checks=active_checks, in_memory=len(processed_ips), uptime=int(time.time() - stats["last_scan_time"])) |
|
|
|
|
|
|
|
|
@app.route('/v1/stats') |
|
|
def get_stats(): |
|
|
return jsonify({ |
|
|
**stats, |
|
|
"queue_size": ip_queue.qsize(), |
|
|
"active_checks": active_checks, |
|
|
"in_memory": len(processed_ips), |
|
|
"uptime": int(time.time() - stats["last_scan_time"]) |
|
|
}) |
|
|
|
|
|
|
|
|
@app.route('/add-provider', methods=['POST']) |
|
|
def add_provider(): |
|
|
ip = request.json.get('ip') |
|
|
if not ip: |
|
|
stats["api_failed"] += 1 |
|
|
return jsonify({"error": "No IP provided"}), 400 |
|
|
|
|
|
conn = db_pool.getconn() |
|
|
try: |
|
|
with conn.cursor() as cur: |
|
|
cur.execute(""" |
|
|
INSERT INTO providers (ip, port, first_seen, last_seen) |
|
|
VALUES (%s, %s, %s, %s) |
|
|
ON CONFLICT (ip) DO UPDATE SET last_seen = %s |
|
|
""", (ip, TARGET_PORT, datetime.utcnow(), datetime.utcnow(), datetime.utcnow())) |
|
|
conn.commit() |
|
|
stats["api_success"] += 1 |
|
|
return jsonify({"status": "success"}), 200 |
|
|
except Exception as e: |
|
|
print(f"DB Error: {e}") |
|
|
stats["api_failed"] += 1 |
|
|
return jsonify({"error": str(e)}), 500 |
|
|
finally: |
|
|
db_pool.putconn(conn) |
|
|
|
|
|
|
|
|
def verify_and_send_ip(ip): |
|
|
global active_checks |
|
|
active_checks += 1 |
|
|
url = f"http://{ip}:{TARGET_PORT}/api/tags" |
|
|
try: |
|
|
response = requests.get(url, timeout=3) |
|
|
if response.status_code == 200 and "models" in response.json(): |
|
|
stats["verified_models"] += 1 |
|
|
requests.post("http://127.0.0.1:5000/add-provider", json={"ip": ip}, timeout=2) |
|
|
except: |
|
|
stats["verification_failed"] += 1 |
|
|
finally: |
|
|
active_checks -= 1 |
|
|
|
|
|
|
|
|
def worker(): |
|
|
while True: |
|
|
if not ip_queue.empty(): |
|
|
ip = ip_queue.get() |
|
|
verify_and_send_ip(ip) |
|
|
time.sleep(0.01) |
|
|
|
|
|
|
|
|
def run_masscan(): |
|
|
print("=== NIANSUH MASSCAN STARTED ===") |
|
|
print(f"Scanning {TARGET_RANGE} at {MASSCAN_RATE} pps...") |
|
|
|
|
|
|
|
|
process = subprocess.Popen( |
|
|
["sudo", "masscan", TARGET_RANGE, "-p", str(TARGET_PORT), "--rate", str(MASSCAN_RATE), "--output-format", "list"], |
|
|
stdout=subprocess.PIPE, |
|
|
stderr=subprocess.PIPE, |
|
|
universal_newlines=True |
|
|
) |
|
|
|
|
|
for line in process.stdout: |
|
|
line = line.strip() |
|
|
if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", line): |
|
|
if line not in processed_ips: |
|
|
processed_ips.add(line) |
|
|
stats["found"] += 1 |
|
|
ip_queue.put(line) |
|
|
|
|
|
if len(processed_ips) >= PROCESSED_IPS_LIMIT: |
|
|
processed_ips.clear() |
|
|
stats["cache_clears"] += 1 |
|
|
|
|
|
print("Masscan stopped. Restarting in 5 sec...") |
|
|
time.sleep(5) |
|
|
run_masscan() |
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
|
|
for _ in range(NUM_CHECKERS): |
|
|
threading.Thread(target=worker, daemon=True).start() |
|
|
|
|
|
|
|
|
threading.Thread(target=run_masscan, daemon=True).start() |
|
|
|
|
|
|
|
|
app.run(host='0.0.0.0', port=5000, threaded=True) |
|
|
|