Files
ad-infr-control/scoreboard_injector/main.py

605 lines
26 KiB
Python
Raw Normal View History

2025-12-02 14:01:34 +03:00
"""
Scoreboard Injector for ForcAD
2025-12-03 11:03:37 +03:00
Monitors Socket.IO events for attacks and alerts on critical situations
2025-12-02 14:01:34 +03:00
"""
import os
import asyncio
2025-12-04 13:26:39 +03:00
import aiohttp
2025-12-02 14:01:34 +03:00
from datetime import datetime, timedelta
2025-12-04 14:11:04 +03:00
from typing import Optional
2025-12-03 10:56:33 +03:00
import socketio
2025-12-02 14:01:34 +03:00
from fastapi import FastAPI, HTTPException, Depends, Header
import asyncpg
from contextlib import asynccontextmanager
# Configuration
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://adctrl:adctrl@postgres:5432/adctrl")
SECRET_TOKEN = os.getenv("SECRET_TOKEN", "change-me-in-production")
2025-12-03 11:03:37 +03:00
SCOREBOARD_URL = os.getenv("SCOREBOARD_URL", "http://10.60.0.1:8080")
2025-12-02 14:01:34 +03:00
OUR_TEAM_ID = int(os.getenv("OUR_TEAM_ID", "1"))
2025-12-03 15:36:41 +03:00
ALERT_THRESHOLD_POINTS = float(os.getenv("ALERT_THRESHOLD_POINTS", "5"))
2025-12-02 14:01:34 +03:00
TELEGRAM_API_URL = os.getenv("TELEGRAM_API_URL", "http://tg-bot:8003/send")
# Database pool
db_pool = None
ws_task = None
# Auth dependency
async def verify_token(authorization: str = Header(None)):
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing or invalid authorization header")
token = authorization.replace("Bearer ", "")
if token != SECRET_TOKEN:
raise HTTPException(status_code=403, detail="Invalid token")
return token
2025-12-04 14:12:27 +03:00
# Database functions
async def get_db():
return await db_pool.acquire()
async def release_db(conn):
await db_pool.release(conn)
2025-12-02 14:01:34 +03:00
2025-12-04 13:26:39 +03:00
async def send_telegram_alert(message: str, service_id: int = None, service_name: str = None):
2025-12-02 14:01:34 +03:00
"""Send alert to telegram bot"""
try:
async with aiohttp.ClientSession() as session:
2025-12-04 13:26:39 +03:00
payload = {"message": message}
if service_id:
payload["service_id"] = service_id
if service_name:
payload["service_name"] = service_name
2025-12-02 14:01:34 +03:00
async with session.post(
TELEGRAM_API_URL,
2025-12-04 13:26:39 +03:00
json=payload,
2025-12-02 14:01:34 +03:00
headers={"Authorization": f"Bearer {SECRET_TOKEN}"}
) as resp:
if resp.status != 200:
2025-12-04 13:26:39 +03:00
print(f"Failed to send telegram alert: Status {resp.status}")
2025-12-02 14:01:34 +03:00
except Exception as e:
2025-12-04 13:26:39 +03:00
print(f"Error sending telegram alert: {e}")
2025-12-02 14:01:34 +03:00
2025-12-03 15:28:48 +03:00
async def fetch_task_names():
"""Fetch task names from scoreboard API"""
try:
async with aiohttp.ClientSession() as session:
async with session.get(f"{SCOREBOARD_URL}/api/client/tasks/") as resp:
if resp.status == 200:
tasks = await resp.json()
return {task['id']: task['name'] for task in tasks}
2025-12-04 13:26:39 +03:00
return {}
2025-12-03 15:28:48 +03:00
except Exception as e:
print(f"Error fetching task names: {e}")
return {}
2025-12-03 10:56:33 +03:00
async def socketio_listener():
"""Listen to ForcAD scoreboard using Socket.IO"""
sio = socketio.AsyncClient(logger=False, engineio_logger=False)
2025-12-03 13:52:28 +03:00
# Cache for task and team names
2025-12-03 13:17:56 +03:00
task_names = {}
2025-12-03 13:52:28 +03:00
team_names = {}
2025-12-03 13:17:56 +03:00
2025-12-03 15:28:48 +03:00
# Fetch task names on startup
task_names.update(await fetch_task_names())
2025-12-03 15:15:56 +03:00
@sio.on('*', namespace='/live_events')
async def catch_all(event, data):
"""Catch all events from live_events namespace"""
if isinstance(data, list) and len(data) >= 2:
event_type = data[0]
event_data = data[1].get('data', {}) if isinstance(data[1], dict) else {}
if event_type == 'flag_stolen':
await process_flag_stolen(event_data)
2025-12-04 13:26:39 +03:00
elif isinstance(data, dict) and 'data' in data:
await process_flag_stolen(data['data'])
2025-12-03 15:15:56 +03:00
async def process_flag_stolen(event_data):
"""Process flag_stolen event"""
try:
attacker_id = event_data.get('attacker_id')
victim_id = event_data.get('victim_id')
task_id = event_data.get('task_id')
attacker_delta = event_data.get('attacker_delta', 0)
2025-12-04 13:26:39 +03:00
2025-12-03 15:15:56 +03:00
if attacker_id is None or victim_id is None:
return
2025-12-04 13:26:39 +03:00
2025-12-03 15:15:56 +03:00
service_name = task_names.get(task_id, f"task_{task_id}")
timestamp = datetime.utcnow()
is_our_attack = attacker_id == OUR_TEAM_ID
is_attack_to_us = victim_id == OUR_TEAM_ID
2025-12-04 13:26:39 +03:00
2025-12-04 14:02:13 +03:00
print(f"Flag event: attacker={attacker_id}, victim={victim_id}, service={service_name}, points={attacker_delta:.2f}")
print(f" Our team: {OUR_TEAM_ID}, is_our_attack={is_our_attack}, is_attack_to_us={is_attack_to_us}")
2025-12-03 15:15:56 +03:00
if is_our_attack or is_attack_to_us:
conn = await db_pool.acquire()
try:
attack_id = f"flag_{attacker_id}_{victim_id}_{task_id}_{int(timestamp.timestamp())}"
await conn.execute("""
INSERT INTO attacks (attack_id, attacker_team_id, victim_team_id, service_name, timestamp, points, is_our_attack, is_attack_to_us)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (attack_id) DO NOTHING
""", attack_id, attacker_id, victim_id, service_name, timestamp, float(attacker_delta), is_our_attack, is_attack_to_us)
2025-12-04 13:26:39 +03:00
if is_attack_to_us and attacker_delta >= ALERT_THRESHOLD_POINTS:
2025-12-04 14:02:13 +03:00
print(f" Sending alert: points {attacker_delta:.2f} >= threshold {ALERT_THRESHOLD_POINTS}")
2025-12-04 13:26:39 +03:00
alert_message = f"🚨 ATTACK DETECTED!\nTeam {attacker_id} stole flag from {service_name}\nPoints lost: {attacker_delta:.2f} FP"
2025-12-04 14:36:23 +03:00
# Get service_id from controller by checking name first, then alias
2025-12-04 13:26:39 +03:00
service_id = None
try:
2025-12-04 14:36:23 +03:00
# Try to find service by exact name match
2025-12-04 13:26:39 +03:00
service_row = await conn.fetchrow(
"SELECT id FROM services WHERE name = $1 LIMIT 1",
service_name
)
2025-12-04 14:36:23 +03:00
if not service_row:
# Try to find service by alias
service_row = await conn.fetchrow(
"SELECT id FROM services WHERE alias = $1 LIMIT 1",
service_name
)
2025-12-04 13:26:39 +03:00
if service_row:
service_id = service_row['id']
2025-12-04 14:19:15 +03:00
print(f" Found service_id: {service_id} for service: {service_name}")
else:
2025-12-04 14:36:23 +03:00
print(f" Service {service_name} not found by name or alias in services table, buttons will use service_name only")
2025-12-04 14:19:15 +03:00
except Exception as e:
print(f" Error looking up service_id: {e}")
2025-12-04 13:26:39 +03:00
alert_id = await conn.fetchval("""
INSERT INTO attack_alerts (attack_id, alert_type, severity, message)
VALUES (
(SELECT id FROM attacks WHERE attack_id = $1),
'flag_stolen',
'high',
$2
)
RETURNING id
""", attack_id, alert_message)
2025-12-04 14:19:15 +03:00
print(f" Calling send_telegram_alert with service_id={service_id}, service_name={service_name}")
2025-12-04 13:26:39 +03:00
await send_telegram_alert(alert_message, service_id=service_id, service_name=service_name)
await conn.execute("UPDATE attack_alerts SET notified = true WHERE id = $1", alert_id)
2025-12-04 14:02:13 +03:00
print(f" Alert sent successfully")
else:
if is_our_attack:
print(f" Our successful attack - no alert needed")
else:
print(f" Attack to us but below threshold: {attacker_delta:.2f} < {ALERT_THRESHOLD_POINTS}")
2025-12-03 15:15:56 +03:00
finally:
await db_pool.release(conn)
except Exception as e:
print(f"Error processing flag_stolen event: {e}")
@sio.event(namespace='/live_events')
2025-12-03 10:56:33 +03:00
async def update_scoreboard(data):
2025-12-03 13:46:00 +03:00
"""Handle scoreboard update - compare with previous state to detect NEW attacks"""
2025-12-03 10:56:33 +03:00
try:
event_data = data.get('data', {})
round_num = event_data.get('round', 0)
2025-12-03 13:17:56 +03:00
round_start = event_data.get('round_start', 0)
2025-12-03 10:56:33 +03:00
team_tasks = event_data.get('team_tasks', [])
2025-12-03 13:17:56 +03:00
conn = await db_pool.acquire()
try:
2025-12-03 14:04:41 +03:00
# Store team scores from team_tasks (score field = FP for this service)
# Aggregate scores per team
team_fp_totals = {}
for team_task in team_tasks:
team_id = team_task.get('team_id')
fp_score = team_task.get('score', 0)
if team_id not in team_fp_totals:
team_fp_totals[team_id] = 0
team_fp_totals[team_id] += fp_score
# Store aggregated scores
for team_id, total_fp in team_fp_totals.items():
2025-12-03 13:52:28 +03:00
await conn.execute("""
INSERT INTO team_scores (team_id, team_name, total_score, flag_points, round, timestamp)
VALUES ($1, $2, $3, $4, $5, NOW())
""", team_id, team_names.get(team_id, f'Team {team_id}'),
2025-12-03 14:04:41 +03:00
total_fp, total_fp, round_num)
# Process each team_task for attack detection
2025-12-03 14:46:06 +03:00
# Group by service to match stolen/lost pairs
service_data = {}
2025-12-03 13:17:56 +03:00
for team_task in team_tasks:
task_id = team_task.get('task_id')
2025-12-03 13:46:00 +03:00
service_name = task_names.get(task_id, f"task_{task_id}")
2025-12-03 14:46:06 +03:00
if service_name not in service_data:
service_data[service_name] = []
service_data[service_name].append(team_task)
# Process each service
for service_name, tasks in service_data.items():
# Track state for each team in this service
for team_task in tasks:
team_id = team_task.get('team_id')
task_id = team_task.get('task_id')
current_stolen = team_task.get('stolen', 0)
current_lost = team_task.get('lost', 0)
current_fp_score = team_task.get('score', 0)
2025-12-03 13:46:00 +03:00
2025-12-03 14:46:06 +03:00
# Get previous state from database
prev_state = await conn.fetchrow(
"SELECT stolen_flags, lost_flags, fp_score FROM scoreboard_state WHERE team_id = $1 AND service_name = $2",
team_id, service_name
)
2025-12-03 14:28:45 +03:00
2025-12-03 14:46:06 +03:00
prev_stolen = prev_state['stolen_flags'] if prev_state else 0
prev_lost = prev_state['lost_flags'] if prev_state else 0
prev_fp_score = prev_state['fp_score'] if prev_state else 0
2025-12-03 13:17:56 +03:00
2025-12-03 14:46:06 +03:00
# Calculate NEW flags and FP changes
new_stolen = current_stolen - prev_stolen
new_lost = current_lost - prev_lost
fp_change = current_fp_score - prev_fp_score
2025-12-03 13:46:00 +03:00
2025-12-03 14:46:06 +03:00
is_first_update = prev_state is None
2025-12-03 14:28:45 +03:00
2025-12-03 14:46:06 +03:00
# Update current state in database
2025-12-03 13:46:00 +03:00
await conn.execute("""
2025-12-03 14:46:06 +03:00
INSERT INTO scoreboard_state (team_id, service_name, stolen_flags, lost_flags, fp_score, last_updated)
VALUES ($1, $2, $3, $4, $5, NOW())
ON CONFLICT (team_id, service_name)
DO UPDATE SET stolen_flags = $3, lost_flags = $4, fp_score = $5, last_updated = NOW()
""", team_id, service_name, current_stolen, current_lost, current_fp_score)
2025-12-03 13:17:56 +03:00
2025-12-03 14:46:06 +03:00
# Create single attack record when flags change (not first update)
2025-12-03 15:15:56 +03:00
# Only track attacks involving our team to avoid duplicates
2025-12-03 14:46:06 +03:00
if not is_first_update and (new_stolen > 0 or new_lost > 0):
timestamp = datetime.utcnow()
is_our_attack = (new_stolen > 0 and team_id == OUR_TEAM_ID)
is_attack_to_us = (new_lost > 0 and team_id == OUR_TEAM_ID)
2025-12-03 15:15:56 +03:00
# Only create records for attacks involving OUR team
should_record = is_our_attack or is_attack_to_us
2025-12-03 13:17:56 +03:00
2025-12-03 15:15:56 +03:00
if should_record:
# Determine attacker/victim and FP
if new_stolen > 0:
# This team stole flags (attacker)
attacker_id = team_id
victim_id = None # We don't know exact victim
fp_value = max(0, fp_change)
attack_type = "stolen"
else:
# This team lost flags (victim)
attacker_id = None # We don't know exact attacker
victim_id = team_id
fp_value = abs(min(0, fp_change))
attack_type = "lost"
attack_id = f"r{round_num}_{attack_type}_team{team_id}_{service_name}_{int(timestamp.timestamp())}"
await conn.execute("""
INSERT INTO attacks (attack_id, attacker_team_id, victim_team_id, service_name, timestamp, points, is_our_attack, is_attack_to_us)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (attack_id) DO NOTHING
""", attack_id, attacker_id, victim_id, service_name, timestamp, float(fp_value), is_our_attack, is_attack_to_us)
if is_our_attack:
2025-12-04 13:26:39 +03:00
pass
2025-12-03 15:15:56 +03:00
elif is_attack_to_us:
if fp_value >= ALERT_THRESHOLD_POINTS:
await check_and_create_alerts(conn, 0, service_name)
2025-12-04 14:09:19 +03:00
finally:
2025-12-03 13:17:56 +03:00
await db_pool.release(conn)
2025-12-03 10:56:33 +03:00
except Exception as e:
print(f"Error processing update_scoreboard: {e}")
2025-12-03 15:15:56 +03:00
@sio.event(namespace='/live_events')
2025-12-03 10:56:33 +03:00
async def init_scoreboard(data):
"""Handle initial scoreboard data"""
try:
event_data = data.get('data', {})
teams = event_data.get('teams', [])
tasks = event_data.get('tasks', [])
2025-12-03 13:17:56 +03:00
for task in tasks:
task_names[task.get('id')] = task.get('name')
2025-12-03 13:52:28 +03:00
for team in teams:
team_names[team.get('id')] = team.get('name')
2025-12-03 10:56:33 +03:00
except Exception as e:
print(f"Error processing init_scoreboard: {e}")
@sio.event
async def connect():
2025-12-04 14:02:13 +03:00
print(f"✅ Connected to scoreboard at {SCOREBOARD_URL}")
2025-12-03 10:56:33 +03:00
@sio.event
async def disconnect():
2025-12-04 14:02:13 +03:00
print(f"❌ Disconnected from scoreboard")
2025-12-03 10:56:33 +03:00
while True:
try:
2025-12-04 14:02:13 +03:00
print(f"Connecting to {SCOREBOARD_URL}...")
2025-12-03 10:56:33 +03:00
await sio.connect(
SCOREBOARD_URL,
2025-12-03 15:15:56 +03:00
namespaces=['/live_events'],
2025-12-03 10:56:33 +03:00
transports=['websocket']
)
await sio.wait()
except Exception as e:
2025-12-04 14:02:13 +03:00
print(f"Connection error: {e}")
2025-12-03 10:56:33 +03:00
await asyncio.sleep(5)
2025-12-02 14:01:34 +03:00
# Lifespan context
@asynccontextmanager
async def lifespan(app: FastAPI):
global db_pool, ws_task
db_pool = await asyncpg.create_pool(DATABASE_URL, min_size=2, max_size=10)
2025-12-03 11:03:37 +03:00
ws_task = asyncio.create_task(socketio_listener())
2025-12-02 14:01:34 +03:00
yield
if ws_task:
ws_task.cancel()
try:
await ws_task
except asyncio.CancelledError:
pass
await db_pool.close()
app = FastAPI(title="Scoreboard Injector", lifespan=lifespan)
# API Endpoints
@app.get("/health")
async def health_check():
2025-12-03 10:31:37 +03:00
return {
"status": "ok",
"timestamp": datetime.utcnow().isoformat(),
"team_id": OUR_TEAM_ID,
2025-12-03 11:03:37 +03:00
"mode": "socketio",
"scoreboard_url": SCOREBOARD_URL
2025-12-03 10:31:37 +03:00
}
2025-12-02 14:01:34 +03:00
@app.get("/stats", dependencies=[Depends(verify_token)])
async def get_stats():
"""Get attack statistics"""
conn = await get_db()
try:
total = await conn.fetchval("SELECT COUNT(*) FROM attacks")
attacks_by_us = await conn.fetchval("SELECT COUNT(*) FROM attacks WHERE is_our_attack = true")
attacks_to_us = await conn.fetchval("SELECT COUNT(*) FROM attacks WHERE is_attack_to_us = true")
threshold_time = datetime.utcnow() - timedelta(minutes=5)
recent = await conn.fetchval("SELECT COUNT(*) FROM attacks WHERE timestamp > $1", threshold_time)
critical_alerts = await conn.fetchval(
"SELECT COUNT(*) FROM attack_alerts WHERE severity = 'critical' AND created_at > $1",
threshold_time
)
return {
"total_attacks": total,
"attacks_by_us": attacks_by_us,
"attacks_to_us": attacks_to_us,
"recent_attacks_5min": recent,
"critical_alerts_5min": critical_alerts
}
finally:
await release_db(conn)
@app.get("/attacks", dependencies=[Depends(verify_token)])
async def get_attacks(limit: int = 100, our_attacks: Optional[bool] = None, attacks_to_us: Optional[bool] = None):
2025-12-03 13:52:28 +03:00
"""Get recent attacks with team names"""
2025-12-02 14:01:34 +03:00
conn = await get_db()
try:
2025-12-03 13:52:28 +03:00
query = """
SELECT
a.*,
ts_attacker.team_name as attacker_team_name,
ts_victim.team_name as victim_team_name
FROM attacks a
LEFT JOIN (
SELECT DISTINCT ON (team_id) team_id, team_name
FROM team_scores
ORDER BY team_id, timestamp DESC
) ts_attacker ON a.attacker_team_id = ts_attacker.team_id
LEFT JOIN (
SELECT DISTINCT ON (team_id) team_id, team_name
FROM team_scores
ORDER BY team_id, timestamp DESC
) ts_victim ON a.victim_team_id = ts_victim.team_id
WHERE 1=1
"""
2025-12-02 14:01:34 +03:00
params = []
param_count = 0
if our_attacks is not None:
param_count += 1
2025-12-03 13:52:28 +03:00
query += f" AND a.is_our_attack = ${param_count}"
2025-12-02 14:01:34 +03:00
params.append(our_attacks)
if attacks_to_us is not None:
param_count += 1
2025-12-03 13:52:28 +03:00
query += f" AND a.is_attack_to_us = ${param_count}"
2025-12-02 14:01:34 +03:00
params.append(attacks_to_us)
param_count += 1
2025-12-03 13:52:28 +03:00
query += f" ORDER BY a.timestamp DESC LIMIT ${param_count}"
2025-12-02 14:01:34 +03:00
params.append(limit)
rows = await conn.fetch(query, *params)
return [dict(row) for row in rows]
finally:
await release_db(conn)
@app.get("/alerts", dependencies=[Depends(verify_token)])
async def get_alerts(limit: int = 50, unnotified: bool = False):
"""Get alerts"""
conn = await get_db()
try:
if unnotified:
query = "SELECT * FROM attack_alerts WHERE notified = false ORDER BY created_at DESC LIMIT $1"
else:
query = "SELECT * FROM attack_alerts ORDER BY created_at DESC LIMIT $1"
rows = await conn.fetch(query, limit)
return [dict(row) for row in rows]
finally:
await release_db(conn)
@app.post("/alerts/{alert_id}/acknowledge", dependencies=[Depends(verify_token)])
async def acknowledge_alert(alert_id: int):
"""Mark alert as acknowledged"""
conn = await get_db()
try:
await conn.execute("UPDATE attack_alerts SET notified = true WHERE id = $1", alert_id)
return {"status": "acknowledged", "alert_id": alert_id}
finally:
await release_db(conn)
@app.get("/attacks/by-service", dependencies=[Depends(verify_token)])
async def get_attacks_by_service():
"""Get attack statistics grouped by service"""
conn = await get_db()
try:
rows = await conn.fetch("""
SELECT
service_name,
COUNT(*) as total_attacks,
COUNT(*) FILTER (WHERE is_our_attack = true) as our_attacks,
COUNT(*) FILTER (WHERE is_attack_to_us = true) as attacks_to_us,
COALESCE(SUM(points) FILTER (WHERE is_our_attack = true), 0) as points_gained,
COALESCE(SUM(points) FILTER (WHERE is_attack_to_us = true), 0) as points_lost
FROM attacks
GROUP BY service_name
ORDER BY total_attacks DESC
""")
return [dict(row) for row in rows]
finally:
await release_db(conn)
@app.post("/settings/team-id", dependencies=[Depends(verify_token)])
async def set_team_id(team_id: int):
"""Update our team ID"""
global OUR_TEAM_ID
OUR_TEAM_ID = team_id
conn = await get_db()
try:
await conn.execute(
"INSERT INTO settings (key, value) VALUES ('our_team_id', $1) ON CONFLICT (key) DO UPDATE SET value = $1",
str(team_id)
)
return {"team_id": team_id}
finally:
await release_db(conn)
2025-12-03 10:22:19 +03:00
@app.get("/settings/team-id", dependencies=[Depends(verify_token)])
async def get_team_id():
"""Get current team ID setting"""
return {"team_id": OUR_TEAM_ID}
@app.post("/test/inject-attack", dependencies=[Depends(verify_token)])
async def inject_test_attack(attacker_id: int, victim_id: int, service: str = "test-service", points: float = 10.0):
"""Manually inject a test attack event for debugging"""
test_event = {
"type": "attack",
"attacker_id": attacker_id,
"victim_id": victim_id,
"service": service,
"flag": "TEST_FLAG_" + datetime.utcnow().isoformat(),
"points": points,
"time": datetime.utcnow().isoformat(),
"round": 1
}
await process_attack_event(test_event)
return {"status": "injected", "event": test_event}
2025-12-03 10:31:37 +03:00
@app.get("/debug/scoreboard", dependencies=[Depends(verify_token)])
async def debug_scoreboard():
2025-12-03 11:03:37 +03:00
"""Check if scoreboard is reachable and show connection info"""
import aiohttp
2025-12-03 10:31:37 +03:00
results = {
2025-12-03 11:03:37 +03:00
"mode": "socketio",
"config": {
"scoreboard_url": SCOREBOARD_URL,
"our_team_id": OUR_TEAM_ID
},
2025-12-03 10:31:37 +03:00
"endpoints_tested": []
}
try:
async with aiohttp.ClientSession() as session:
2025-12-03 11:03:37 +03:00
# Test Socket.IO endpoint
socketio_url = f"{SCOREBOARD_URL}/socket.io/?EIO=4&transport=polling"
2025-12-03 10:31:37 +03:00
try:
2025-12-03 11:03:37 +03:00
async with session.get(socketio_url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
results["socketio_status"] = {
"url": socketio_url,
"status": resp.status,
"reachable": resp.status == 200,
"response_preview": (await resp.text())[:200] if resp.status == 200 else None
}
2025-12-03 10:31:37 +03:00
except Exception as e:
2025-12-03 11:03:37 +03:00
results["socketio_status"] = {
"url": socketio_url,
"reachable": False,
"error": str(e)
}
2025-12-03 10:31:37 +03:00
2025-12-03 11:03:37 +03:00
# Test base scoreboard URL
try:
async with session.get(SCOREBOARD_URL, timeout=aiohttp.ClientTimeout(total=5)) as resp:
results["base_url_status"] = {
"url": SCOREBOARD_URL,
"status": resp.status,
"reachable": resp.status == 200
}
except Exception as e:
results["base_url_status"] = {
"url": SCOREBOARD_URL,
"reachable": False,
"error": str(e)
}
2025-12-03 10:31:37 +03:00
2025-12-03 11:03:37 +03:00
# Test attack_data endpoint (for reference only)
attack_data_url = f"{SCOREBOARD_URL}/api/client/attack_data"
try:
async with session.get(attack_data_url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
result = {
"url": attack_data_url,
"status": resp.status,
"reachable": resp.status == 200,
"content_type": resp.headers.get('Content-Type', ''),
"note": "Contains exploit credentials, not attack events"
}
if resp.status == 200 and 'application/json' in resp.headers.get('Content-Type', ''):
data = await resp.json()
result["services"] = list(data.keys()) if isinstance(data, dict) else None
results["endpoints_tested"].append(result)
except Exception as e:
results["endpoints_tested"].append({
"url": attack_data_url,
"reachable": False,
"error": str(e)
})
2025-12-03 10:31:37 +03:00
except Exception as e:
results["error"] = str(e)
return results
2025-12-02 14:01:34 +03:00
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8002)