File size: 5,619 Bytes
d123f27
da9899a
 
 
 
 
 
 
 
32ea1ed
da9899a
d123f27
 
32ea1ed
 
d123f27
 
da9899a
32ea1ed
 
da9899a
32ea1ed
da9899a
 
 
 
 
 
 
 
 
 
d123f27
da9899a
 
32ea1ed
da9899a
 
32ea1ed
da9899a
 
 
32ea1ed
da9899a
 
d123f27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da9899a
 
32ea1ed
da9899a
 
d123f27
 
da9899a
 
32ea1ed
d123f27
da9899a
 
 
 
 
 
 
 
 
d123f27
 
 
 
 
da9899a
 
 
 
d123f27
da9899a
 
 
 
 
32ea1ed
da9899a
d123f27
 
 
da9899a
32ea1ed
d123f27
 
 
 
da9899a
 
 
 
32ea1ed
da9899a
 
d123f27
da9899a
32ea1ed
d123f27
da9899a
d123f27
da9899a
d123f27
da9899a
32ea1ed
 
 
d123f27
32ea1ed
 
 
 
 
 
 
d123f27
 
 
32ea1ed
d123f27
32ea1ed
 
 
 
 
 
 
d123f27
32ea1ed
d123f27
0e93030
d123f27
32ea1ed
 
da9899a
32ea1ed
d123f27
 
 
32ea1ed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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

# --- Config ---
MASSCAN_RATE = 10000  # Reduced to avoid ISP blocks
TARGET_PORT = 11434   # Ollama default port
TARGET_RANGE = "0.0.0.0/0"  # Scan everything
NUM_CHECKERS = 50     # Workers
PROCESSED_IPS_LIMIT = 1000000

# --- DB Config ---
DB_URL = "postgresql://neondb_owner:npg_r7oFwW5XsmtG@ep-patient-lake-agwy3kca-pooler.c-2.eu-central-1.aws.neon.tech/ollama?sslmode=require"

# --- Global State ---
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 Connection Pool ---
db_pool = psycopg2.pool.SimpleConnectionPool(
    minconn=1,
    maxconn=10,
    dsn=DB_URL
)

# --- Flask App ---
app = Flask(__name__)

# --- Homepage (Real-Time Stats) ---
@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"]))

# --- Stats API ---
@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"])
    })

# --- API Endpoint (Save IPs) ---
@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)

# --- Verify Ollama Instance ---
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

# --- Worker (Process IPs) ---
def worker():
    while True:
        if not ip_queue.empty():
            ip = ip_queue.get()
            verify_and_send_ip(ip)
        time.sleep(0.01)

# --- Masscan Runner (Now Works) ---
def run_masscan():
    print("=== NIANSUH MASSCAN STARTED ===")
    print(f"Scanning {TARGET_RANGE} at {MASSCAN_RATE} pps...")

    # Run masscan with sudo (required for raw sockets)
    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):  # Match IPs
            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()

# --- Start Everything ---
if __name__ == '__main__':
    # Start workers
    for _ in range(NUM_CHECKERS):
        threading.Thread(target=worker, daemon=True).start()

    # Start masscan in a separate thread
    threading.Thread(target=run_masscan, daemon=True).start()

    # Start Flask
    app.run(host='0.0.0.0', port=5000, threaded=True)