ddos.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. # -*- coding: utf-8 -*-
  2. """
  3. DDoS-Schutz für Trixy Network.
  4. Bietet Erkennung und Schutz vor verschiedenen DDoS-Angriffen:
  5. - Connection Flooding
  6. - SYN Floods
  7. - Slowloris-Angriffe
  8. - Amplification-Angriffe
  9. """
  10. import asyncio
  11. import time
  12. from collections import deque
  13. from dataclasses import dataclass, field
  14. from datetime import datetime
  15. from enum import IntEnum, auto
  16. from typing import Any, Callable
  17. class ThreatLevel(IntEnum):
  18. """Bedrohungsstufe."""
  19. NONE = auto() # Keine Bedrohung
  20. LOW = auto() # Geringe Bedrohung
  21. MEDIUM = auto() # Mittlere Bedrohung
  22. HIGH = auto() # Hohe Bedrohung
  23. CRITICAL = auto() # Kritische Bedrohung
  24. @dataclass
  25. class DDoSConfig:
  26. """
  27. Konfiguration für DDoS-Schutz.
  28. Attributes:
  29. max_connections_per_ip: Maximale gleichzeitige Verbindungen pro IP
  30. max_connection_rate: Maximale neue Verbindungen pro Sekunde pro IP
  31. max_global_connections: Maximale globale Verbindungen
  32. max_global_connection_rate: Maximale globale Verbindungsrate
  33. slowloris_timeout: Timeout für Slowloris-Erkennung
  34. suspicious_threshold: Schwelle für verdächtige Aktivität
  35. ban_duration: Dauer der automatischen Sperre in Sekunden
  36. monitoring_window: Zeitfenster für Analyse in Sekunden
  37. """
  38. max_connections_per_ip: int = 10
  39. max_connection_rate: float = 5.0 # pro Sekunde
  40. max_global_connections: int = 1000
  41. max_global_connection_rate: float = 100.0
  42. slowloris_timeout: float = 30.0
  43. suspicious_threshold: int = 5
  44. ban_duration: float = 600.0 # 10 Minuten
  45. monitoring_window: float = 60.0 # 1 Minute
  46. @dataclass
  47. class ConnectionInfo:
  48. """Informationen über eine einzelne Verbindung."""
  49. ip: str
  50. connected_at: float
  51. last_activity: float
  52. bytes_received: int = 0
  53. bytes_sent: int = 0
  54. requests_count: int = 0
  55. state: str = "connected"
  56. @dataclass
  57. class IPStatistics:
  58. """Statistiken für eine IP-Adresse."""
  59. ip: str
  60. connections: list[str] = field(default_factory=list) # Connection-IDs
  61. connection_times: deque = field(default_factory=lambda: deque(maxlen=100))
  62. suspicious_events: int = 0
  63. last_suspicious: float | None = None
  64. banned_until: float | None = None
  65. total_connections: int = 0
  66. total_bytes_received: int = 0
  67. class ConnectionTracker:
  68. """
  69. Verfolgt alle aktiven Verbindungen.
  70. Überwacht Verbindungen pro IP und global für DDoS-Erkennung.
  71. """
  72. def __init__(self, config: DDoSConfig | None = None) -> None:
  73. """
  74. Initialisiert den Connection-Tracker.
  75. Args:
  76. config: DDoS-Konfiguration
  77. """
  78. self._config = config or DDoSConfig()
  79. self._connections: dict[str, ConnectionInfo] = {}
  80. self._ip_stats: dict[str, IPStatistics] = {}
  81. self._connection_counter: int = 0
  82. self._lock = asyncio.Lock()
  83. def _get_or_create_ip_stats(self, ip: str) -> IPStatistics:
  84. """Gibt oder erstellt IP-Statistiken."""
  85. if ip not in self._ip_stats:
  86. self._ip_stats[ip] = IPStatistics(ip=ip)
  87. return self._ip_stats[ip]
  88. def register_connection(self, ip: str) -> tuple[str, bool]:
  89. """
  90. Registriert eine neue Verbindung.
  91. Args:
  92. ip: IP-Adresse der Verbindung
  93. Returns:
  94. Tuple aus (connection_id, erlaubt)
  95. """
  96. stats = self._get_or_create_ip_stats(ip)
  97. now = time.monotonic()
  98. # Prüfe Sperre
  99. if stats.banned_until and now < stats.banned_until:
  100. return "", False
  101. # Prüfe Verbindungslimit pro IP
  102. if len(stats.connections) >= self._config.max_connections_per_ip:
  103. stats.suspicious_events += 1
  104. stats.last_suspicious = now
  105. return "", False
  106. # Prüfe globales Limit
  107. if len(self._connections) >= self._config.max_global_connections:
  108. return "", False
  109. # Prüfe Verbindungsrate
  110. stats.connection_times.append(now)
  111. recent_connections = sum(
  112. 1 for t in stats.connection_times
  113. if now - t < 1.0 # Letzter Sekunde
  114. )
  115. if recent_connections > self._config.max_connection_rate:
  116. stats.suspicious_events += 1
  117. stats.last_suspicious = now
  118. return "", False
  119. # Verbindung erlauben
  120. self._connection_counter += 1
  121. conn_id = f"conn_{self._connection_counter}"
  122. self._connections[conn_id] = ConnectionInfo(
  123. ip=ip,
  124. connected_at=now,
  125. last_activity=now
  126. )
  127. stats.connections.append(conn_id)
  128. stats.total_connections += 1
  129. return conn_id, True
  130. def unregister_connection(self, conn_id: str) -> None:
  131. """
  132. Entfernt eine Verbindung.
  133. Args:
  134. conn_id: Verbindungs-ID
  135. """
  136. if conn_id not in self._connections:
  137. return
  138. conn = self._connections.pop(conn_id)
  139. if conn.ip in self._ip_stats:
  140. stats = self._ip_stats[conn.ip]
  141. if conn_id in stats.connections:
  142. stats.connections.remove(conn_id)
  143. stats.total_bytes_received += conn.bytes_received
  144. def update_activity(
  145. self,
  146. conn_id: str,
  147. bytes_received: int = 0,
  148. bytes_sent: int = 0
  149. ) -> None:
  150. """
  151. Aktualisiert die Aktivität einer Verbindung.
  152. Args:
  153. conn_id: Verbindungs-ID
  154. bytes_received: Empfangene Bytes
  155. bytes_sent: Gesendete Bytes
  156. """
  157. if conn_id not in self._connections:
  158. return
  159. conn = self._connections[conn_id]
  160. conn.last_activity = time.monotonic()
  161. conn.bytes_received += bytes_received
  162. conn.bytes_sent += bytes_sent
  163. conn.requests_count += 1
  164. def get_connection_count(self, ip: str | None = None) -> int:
  165. """
  166. Gibt die Anzahl aktiver Verbindungen zurück.
  167. Args:
  168. ip: Optionale IP-Filterung
  169. Returns:
  170. Anzahl Verbindungen
  171. """
  172. if ip is None:
  173. return len(self._connections)
  174. if ip in self._ip_stats:
  175. return len(self._ip_stats[ip].connections)
  176. return 0
  177. def is_banned(self, ip: str) -> bool:
  178. """Prüft, ob eine IP gesperrt ist."""
  179. if ip not in self._ip_stats:
  180. return False
  181. stats = self._ip_stats[ip]
  182. if stats.banned_until is None:
  183. return False
  184. if time.monotonic() > stats.banned_until:
  185. stats.banned_until = None
  186. return False
  187. return True
  188. def ban_ip(self, ip: str, duration: float | None = None) -> None:
  189. """
  190. Sperrt eine IP-Adresse.
  191. Args:
  192. ip: Die zu sperrende IP
  193. duration: Sperrdauer (Standard: config.ban_duration)
  194. """
  195. stats = self._get_or_create_ip_stats(ip)
  196. duration = duration or self._config.ban_duration
  197. stats.banned_until = time.monotonic() + duration
  198. # Alle Verbindungen dieser IP schließen
  199. for conn_id in list(stats.connections):
  200. self.unregister_connection(conn_id)
  201. def unban_ip(self, ip: str) -> bool:
  202. """
  203. Entsperrt eine IP-Adresse.
  204. Args:
  205. ip: Die zu entsperrende IP
  206. Returns:
  207. True wenn IP gesperrt war
  208. """
  209. if ip not in self._ip_stats:
  210. return False
  211. stats = self._ip_stats[ip]
  212. was_banned = stats.banned_until is not None
  213. stats.banned_until = None
  214. stats.suspicious_events = 0
  215. return was_banned
  216. def get_statistics(self) -> dict[str, Any]:
  217. """Gibt globale Statistiken zurück."""
  218. now = time.monotonic()
  219. banned_count = sum(
  220. 1 for s in self._ip_stats.values()
  221. if s.banned_until and now < s.banned_until
  222. )
  223. return {
  224. "total_connections": len(self._connections),
  225. "unique_ips": len(self._ip_stats),
  226. "banned_ips": banned_count,
  227. "max_connections": self._config.max_global_connections
  228. }
  229. class SuspiciousActivityDetector:
  230. """
  231. Erkennt verdächtige Aktivitätsmuster.
  232. Analysiert Verbindungs- und Anfragemuster für DDoS-Erkennung.
  233. """
  234. def __init__(self, config: DDoSConfig | None = None) -> None:
  235. """
  236. Initialisiert den Detektor.
  237. Args:
  238. config: DDoS-Konfiguration
  239. """
  240. self._config = config or DDoSConfig()
  241. self._events: deque[tuple[float, str, str]] = deque(maxlen=10000)
  242. self._patterns: dict[str, int] = {}
  243. def record_event(self, ip: str, event_type: str) -> None:
  244. """
  245. Zeichnet ein Ereignis auf.
  246. Args:
  247. ip: Quell-IP
  248. event_type: Art des Ereignisses
  249. """
  250. now = time.monotonic()
  251. self._events.append((now, ip, event_type))
  252. key = f"{ip}:{event_type}"
  253. self._patterns[key] = self._patterns.get(key, 0) + 1
  254. def analyze(self, ip: str) -> ThreatLevel:
  255. """
  256. Analysiert die Bedrohungsstufe für eine IP.
  257. Args:
  258. ip: Die zu analysierende IP
  259. Returns:
  260. Bedrohungsstufe
  261. """
  262. now = time.monotonic()
  263. window_start = now - self._config.monitoring_window
  264. # Zähle Events in Zeitfenster
  265. ip_events = [
  266. e for e in self._events
  267. if e[0] > window_start and e[1] == ip
  268. ]
  269. event_count = len(ip_events)
  270. if event_count == 0:
  271. return ThreatLevel.NONE
  272. elif event_count < 10:
  273. return ThreatLevel.LOW
  274. elif event_count < 50:
  275. return ThreatLevel.MEDIUM
  276. elif event_count < 100:
  277. return ThreatLevel.HIGH
  278. else:
  279. return ThreatLevel.CRITICAL
  280. def get_suspicious_ips(self, min_level: ThreatLevel = ThreatLevel.MEDIUM) -> list[tuple[str, ThreatLevel]]:
  281. """
  282. Gibt verdächtige IPs zurück.
  283. Args:
  284. min_level: Minimale Bedrohungsstufe
  285. Returns:
  286. Liste von (IP, ThreatLevel) Tupeln
  287. """
  288. now = time.monotonic()
  289. window_start = now - self._config.monitoring_window
  290. # Sammle IPs aus aktuellem Fenster
  291. ips: set[str] = set()
  292. for timestamp, ip, _ in self._events:
  293. if timestamp > window_start:
  294. ips.add(ip)
  295. # Analysiere jede IP
  296. suspicious = []
  297. for ip in ips:
  298. level = self.analyze(ip)
  299. if level >= min_level:
  300. suspicious.append((ip, level))
  301. # Sortiere nach Bedrohungsstufe (höchste zuerst)
  302. suspicious.sort(key=lambda x: x[1], reverse=True)
  303. return suspicious
  304. def cleanup(self) -> None:
  305. """Bereinigt alte Daten."""
  306. now = time.monotonic()
  307. window_start = now - self._config.monitoring_window * 2
  308. # Entferne alte Events
  309. while self._events and self._events[0][0] < window_start:
  310. self._events.popleft()
  311. class DDoSProtection:
  312. """
  313. Hauptklasse für DDoS-Schutz.
  314. Kombiniert Connection-Tracking und Aktivitäts-Analyse.
  315. Example:
  316. protection = DDoSProtection(DDoSConfig(
  317. max_connections_per_ip=10,
  318. max_connection_rate=5.0
  319. ))
  320. # Bei neuer Verbindung
  321. conn_id, allowed = await protection.on_connect("192.168.1.100")
  322. if not allowed:
  323. # Verbindung ablehnen
  324. return
  325. # Bei Aktivität
  326. await protection.on_activity(conn_id, bytes_received=1024)
  327. # Bei Trennung
  328. await protection.on_disconnect(conn_id)
  329. # Periodische Prüfung
  330. for ip, level in protection.get_threats():
  331. if level >= ThreatLevel.HIGH:
  332. protection.ban_ip(ip)
  333. """
  334. def __init__(self, config: DDoSConfig | None = None) -> None:
  335. """
  336. Initialisiert den DDoS-Schutz.
  337. Args:
  338. config: DDoS-Konfiguration
  339. """
  340. self._config = config or DDoSConfig()
  341. self._tracker = ConnectionTracker(self._config)
  342. self._detector = SuspiciousActivityDetector(self._config)
  343. self._lock = asyncio.Lock()
  344. self._monitoring_task: asyncio.Task | None = None
  345. self._callbacks: list[Callable[[str, ThreatLevel], None]] = []
  346. @property
  347. def tracker(self) -> ConnectionTracker:
  348. """Connection-Tracker."""
  349. return self._tracker
  350. @property
  351. def detector(self) -> SuspiciousActivityDetector:
  352. """Aktivitäts-Detektor."""
  353. return self._detector
  354. def on_threat(
  355. self,
  356. callback: Callable[[str, ThreatLevel], None]
  357. ) -> Callable[[str, ThreatLevel], None]:
  358. """
  359. Registriert einen Callback für Bedrohungen.
  360. Args:
  361. callback: Funktion(ip, level)
  362. Returns:
  363. Der registrierte Callback
  364. """
  365. self._callbacks.append(callback)
  366. return callback
  367. async def on_connect(self, ip: str) -> tuple[str, bool]:
  368. """
  369. Wird bei neuer Verbindung aufgerufen.
  370. Args:
  371. ip: IP-Adresse
  372. Returns:
  373. (connection_id, erlaubt)
  374. """
  375. async with self._lock:
  376. conn_id, allowed = self._tracker.register_connection(ip)
  377. if not allowed:
  378. self._detector.record_event(ip, "connection_denied")
  379. # Prüfe Bedrohungsstufe
  380. level = self._detector.analyze(ip)
  381. if level >= ThreatLevel.HIGH:
  382. self._tracker.ban_ip(ip)
  383. self._notify_threat(ip, level)
  384. return conn_id, allowed
  385. async def on_disconnect(self, conn_id: str) -> None:
  386. """
  387. Wird bei Verbindungstrennung aufgerufen.
  388. Args:
  389. conn_id: Verbindungs-ID
  390. """
  391. async with self._lock:
  392. self._tracker.unregister_connection(conn_id)
  393. async def on_activity(
  394. self,
  395. conn_id: str,
  396. bytes_received: int = 0,
  397. bytes_sent: int = 0
  398. ) -> None:
  399. """
  400. Wird bei Verbindungsaktivität aufgerufen.
  401. Args:
  402. conn_id: Verbindungs-ID
  403. bytes_received: Empfangene Bytes
  404. bytes_sent: Gesendete Bytes
  405. """
  406. async with self._lock:
  407. self._tracker.update_activity(conn_id, bytes_received, bytes_sent)
  408. def is_allowed(self, ip: str) -> bool:
  409. """
  410. Prüft, ob eine IP erlaubt ist.
  411. Args:
  412. ip: IP-Adresse
  413. Returns:
  414. True wenn nicht gesperrt
  415. """
  416. return not self._tracker.is_banned(ip)
  417. def ban_ip(self, ip: str, duration: float | None = None) -> None:
  418. """
  419. Sperrt eine IP.
  420. Args:
  421. ip: IP-Adresse
  422. duration: Optionale Sperrdauer
  423. """
  424. self._tracker.ban_ip(ip, duration)
  425. def unban_ip(self, ip: str) -> bool:
  426. """
  427. Entsperrt eine IP.
  428. Args:
  429. ip: IP-Adresse
  430. Returns:
  431. True wenn IP gesperrt war
  432. """
  433. return self._tracker.unban_ip(ip)
  434. def get_threats(
  435. self,
  436. min_level: ThreatLevel = ThreatLevel.MEDIUM
  437. ) -> list[tuple[str, ThreatLevel]]:
  438. """
  439. Gibt aktuelle Bedrohungen zurück.
  440. Args:
  441. min_level: Minimale Bedrohungsstufe
  442. Returns:
  443. Liste von (IP, ThreatLevel)
  444. """
  445. return self._detector.get_suspicious_ips(min_level)
  446. def _notify_threat(self, ip: str, level: ThreatLevel) -> None:
  447. """Benachrichtigt über eine Bedrohung."""
  448. for callback in self._callbacks:
  449. try:
  450. callback(ip, level)
  451. except Exception:
  452. pass
  453. async def _monitoring_loop(self) -> None:
  454. """Hintergrund-Monitoring-Loop."""
  455. while True:
  456. await asyncio.sleep(10.0) # Alle 10 Sekunden
  457. # Bereinigung
  458. self._detector.cleanup()
  459. # Prüfe auf kritische Bedrohungen
  460. for ip, level in self.get_threats(ThreatLevel.HIGH):
  461. if level >= ThreatLevel.CRITICAL:
  462. self.ban_ip(ip)
  463. self._notify_threat(ip, level)
  464. async def start(self) -> None:
  465. """Startet das DDoS-Monitoring."""
  466. if self._monitoring_task is None:
  467. self._monitoring_task = asyncio.create_task(
  468. self._monitoring_loop()
  469. )
  470. async def stop(self) -> None:
  471. """Stoppt das DDoS-Monitoring."""
  472. if self._monitoring_task is not None:
  473. self._monitoring_task.cancel()
  474. try:
  475. await self._monitoring_task
  476. except asyncio.CancelledError:
  477. pass
  478. self._monitoring_task = None
  479. def get_statistics(self) -> dict[str, Any]:
  480. """Gibt Statistiken zurück."""
  481. threats = self.get_threats(ThreatLevel.LOW)
  482. return {
  483. "connections": self._tracker.get_statistics(),
  484. "threats": {
  485. "low": sum(1 for _, l in threats if l == ThreatLevel.LOW),
  486. "medium": sum(1 for _, l in threats if l == ThreatLevel.MEDIUM),
  487. "high": sum(1 for _, l in threats if l == ThreatLevel.HIGH),
  488. "critical": sum(1 for _, l in threats if l == ThreatLevel.CRITICAL),
  489. },
  490. "config": {
  491. "max_connections_per_ip": self._config.max_connections_per_ip,
  492. "max_connection_rate": self._config.max_connection_rate,
  493. "ban_duration": self._config.ban_duration,
  494. }
  495. }