mirror of
https://github.com/Av0idD4rk/firegex.git
synced 2026-10-23 21:42:05 +00:00
minor fixes and integrated test
This commit is contained in:
parent
9e4f93455b
commit
53656d531c
@ -13,7 +13,7 @@ import { Firewall } from './pages/Firewall';
|
|||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import NFProxy from './pages/NFProxy';
|
import NFProxy from './pages/NFProxy';
|
||||||
import ServiceDetailsNFProxy from './pages/NFProxy/ServiceDetails';
|
import ServiceDetailsNFProxy from './pages/NFProxy/ServiceDetails';
|
||||||
import { useAuthStore } from './js/store';
|
import { useAuthStore, useSystemStore } from './js/store';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
@ -24,40 +24,57 @@ function App() {
|
|||||||
const [loadinBtn, setLoadingBtn] = useState(false);
|
const [loadinBtn, setLoadingBtn] = useState(false);
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { access_token } = useAuthStore()
|
const { access_token } = useAuthStore()
|
||||||
|
const { setVersion } = useSystemStore()
|
||||||
|
|
||||||
useEffect(()=>{
|
const getStatus = () => {
|
||||||
socketio.auth = { token: access_token || "" }
|
getstatus().then(res => {
|
||||||
socketio.connect()
|
|
||||||
getStatus()
|
|
||||||
socketio.on("update", (data) => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: data })
|
|
||||||
})
|
|
||||||
socketio.on("connect_error", (err) => {
|
|
||||||
if (access_token){
|
|
||||||
errorNotify("Socket.Io connection failed! ",`Error message: [${err.message}]`)
|
|
||||||
}
|
|
||||||
getStatus()
|
|
||||||
});
|
|
||||||
return () => {
|
|
||||||
socketio.off("update")
|
|
||||||
socketio.off("connect_error")
|
|
||||||
socketio.disconnect()
|
|
||||||
}
|
|
||||||
},[access_token])
|
|
||||||
|
|
||||||
const getStatus = () =>{
|
|
||||||
getstatus().then( res =>{
|
|
||||||
setSystemStatus(res)
|
setSystemStatus(res)
|
||||||
|
setVersion(res.version || "unknown")
|
||||||
|
if (!res.loggined && useAuthStore.getState().getAccessToken()) {
|
||||||
|
useAuthStore.getState().clearAccessToken()
|
||||||
|
}
|
||||||
setReqError(undefined)
|
setReqError(undefined)
|
||||||
}).catch(err=>{
|
}).catch(err => {
|
||||||
setReqError(err.toString())
|
setReqError(err.toString())
|
||||||
setTimeout(getStatus, 500)
|
setTimeout(getStatus, 500)
|
||||||
}).finally( ()=>setLoading(false) )
|
}).finally(() => setLoading(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
getStatus()
|
getStatus()
|
||||||
},[])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (access_token && systemStatus.loggined) {
|
||||||
|
socketio.auth = { token: access_token }
|
||||||
|
if (!socketio.connected) {
|
||||||
|
socketio.connect()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (socketio.connected) {
|
||||||
|
socketio.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [access_token, systemStatus.loggined])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
socketio.on("update", (data) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: data })
|
||||||
|
})
|
||||||
|
socketio.on("connect_error", (err) => {
|
||||||
|
if (err.message === "Unauthorized") {
|
||||||
|
useAuthStore.getState().clearAccessToken()
|
||||||
|
getStatus()
|
||||||
|
} else if (access_token && systemStatus.loggined) {
|
||||||
|
errorNotify("Socket.Io connection failed! ", `Error message: [${err.message}]`)
|
||||||
|
getStatus()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
socketio.off("update")
|
||||||
|
socketio.off("connect_error")
|
||||||
|
}
|
||||||
|
}, [access_token, systemStatus.loggined])
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
|||||||
@ -15,7 +15,8 @@ export type LoginResponse = {
|
|||||||
|
|
||||||
export type ServerStatusResponse = {
|
export type ServerStatusResponse = {
|
||||||
status:string,
|
status:string,
|
||||||
loggined:boolean
|
loggined:boolean,
|
||||||
|
version?:string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PasswordSend = {
|
export type PasswordSend = {
|
||||||
|
|||||||
@ -73,3 +73,13 @@ export const useSession = () => {
|
|||||||
getHomeSection,
|
getHomeSection,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface SystemState {
|
||||||
|
version: string | null;
|
||||||
|
setVersion: (version: string | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSystemStore = create<SystemState>()((set) => ({
|
||||||
|
version: null,
|
||||||
|
setVersion: (version) => set({ version }),
|
||||||
|
}));
|
||||||
|
|||||||
@ -32,6 +32,7 @@ export const socketio = import.meta.env.DEV?
|
|||||||
io("ws://"+DEV_IP_BACKEND, {
|
io("ws://"+DEV_IP_BACKEND, {
|
||||||
path:"/sock/socket.io",
|
path:"/sock/socket.io",
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
|
autoConnect: false,
|
||||||
auth: {
|
auth: {
|
||||||
token: useAuthStore.getState().getAccessToken()
|
token: useAuthStore.getState().getAccessToken()
|
||||||
}
|
}
|
||||||
@ -39,6 +40,7 @@ export const socketio = import.meta.env.DEV?
|
|||||||
io({
|
io({
|
||||||
path:"/sock/socket.io",
|
path:"/sock/socket.io",
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
|
autoConnect: false,
|
||||||
auth: {
|
auth: {
|
||||||
token: useAuthStore.getState().getAccessToken()
|
token: useAuthStore.getState().getAccessToken()
|
||||||
}
|
}
|
||||||
@ -91,7 +93,10 @@ export async function genericapi(method:string,path:string,data:any = undefined,
|
|||||||
},
|
},
|
||||||
body: data? (is_form ? (new URLSearchParams(data)).toString() : JSON.stringify(data)) : undefined
|
body: data? (is_form ? (new URLSearchParams(data)).toString() : JSON.stringify(data)) : undefined
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if(res.status === 401) window.location.reload()
|
if(res.status === 401) {
|
||||||
|
useAuthStore.getState().clearAccessToken();
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
if(res.status === 406) resolve({status:"Wrong Password"})
|
if(res.status === 406) resolve({status:"Wrong Password"})
|
||||||
if(!res.ok){
|
if(!res.ok){
|
||||||
const errorDefault = res.statusText
|
const errorDefault = res.statusText
|
||||||
|
|||||||
@ -783,6 +783,52 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
remove_filters()
|
remove_filters()
|
||||||
|
|
||||||
|
HTTP_HISTORY_TEST_FILTER = textwrap.dedent("""
|
||||||
|
from firegex.nfproxy.models import HttpFullRequest, HttpHistory
|
||||||
|
from firegex.nfproxy import pyfilter, ACCEPT, REJECT
|
||||||
|
|
||||||
|
@pyfilter
|
||||||
|
def history_filter_test(req: HttpFullRequest, history: HttpHistory):
|
||||||
|
past_urls = [r.url for r in history.requests]
|
||||||
|
if "/history_flag" in past_urls and req.url == "/history_secret":
|
||||||
|
return REJECT
|
||||||
|
return ACCEPT
|
||||||
|
""")
|
||||||
|
|
||||||
|
if firegex.nfproxy_set_code(service_id, HTTP_HISTORY_TEST_FILTER):
|
||||||
|
puts(
|
||||||
|
"Successfully added filter for HttpHistory test ✔",
|
||||||
|
color=colors.green,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
puts("Test Failed: Couldn't add HttpHistory filter ✗", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
|
|
||||||
|
server.connect_client()
|
||||||
|
req1 = b"GET /history_flag HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n"
|
||||||
|
resp1 = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK"
|
||||||
|
server.send_packet(req1, server_reply=resp1)
|
||||||
|
if not server.recv_packet():
|
||||||
|
puts("Test Failed: /history_flag request was blocked unexpectedly ✗", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
|
|
||||||
|
req2 = b"GET /history_secret HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n"
|
||||||
|
server.send_packet(req2)
|
||||||
|
if not server.recv_packet():
|
||||||
|
puts(
|
||||||
|
"The HTTP request to /history_secret was successfully blocked based on HttpHistory ✔",
|
||||||
|
color=colors.green,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
puts(
|
||||||
|
"Test Failed: The HTTP request to /history_secret wasn't blocked by HttpHistory filter ✗",
|
||||||
|
color=colors.red,
|
||||||
|
)
|
||||||
|
exit_test(1)
|
||||||
|
server.close_client()
|
||||||
|
|
||||||
|
remove_filters()
|
||||||
|
|
||||||
# Simulating requests is more complex due to websocket extensions handshake
|
# Simulating requests is more complex due to websocket extensions handshake
|
||||||
|
|
||||||
WS_REQUEST_PARSING_TEST = b"GET /sock/?EIO=4&transport=websocket HTTP/1.1\r\nHost: localhost:8080\r\nConnection: Upgrade\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)\xac AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36\r\nUpgrade: websocket\r\nOrigin: http://localhost:8080\r\nSec-WebSocket-Version: 13\r\nAccept-Encoding: gzip, deflate, br, zstd\r\nAccept-Language: it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6,zh;q=0.5\r\nCookie: cookie-consent=true; _iub_cs-86405163=%7B%22timestamp%22%3A%222024-09-12T18%3A20%3A18.627Z%22%2C%22version%22%3A%221.65.1%22%2C%22purposes%22%3A%7B%221%22%3Atrue%2C%224%22%3Atrue%7D%2C%22id%22%3A86405163%2C%22cons%22%3A%7B%22rand%22%3A%222b09e6%22%7D%7D\r\nSec-WebSocket-Key: eE01O3/ZShPKsrykACLAaA==\r\nSec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n\r\n\xc1\x84#\x8a\xb2\xbb\x11\xbb\xb2\xbb"
|
WS_REQUEST_PARSING_TEST = b"GET /sock/?EIO=4&transport=websocket HTTP/1.1\r\nHost: localhost:8080\r\nConnection: Upgrade\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)\xac AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36\r\nUpgrade: websocket\r\nOrigin: http://localhost:8080\r\nSec-WebSocket-Version: 13\r\nAccept-Encoding: gzip, deflate, br, zstd\r\nAccept-Language: it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6,zh;q=0.5\r\nCookie: cookie-consent=true; _iub_cs-86405163=%7B%22timestamp%22%3A%222024-09-12T18%3A20%3A18.627Z%22%2C%22version%22%3A%221.65.1%22%2C%22purposes%22%3A%7B%221%22%3Atrue%2C%224%22%3Atrue%7D%2C%22id%22%3A86405163%2C%22cons%22%3A%7B%22rand%22%3A%222b09e6%22%7D%7D\r\nSec-WebSocket-Key: eE01O3/ZShPKsrykACLAaA==\r\nSec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n\r\n\xc1\x84#\x8a\xb2\xbb\x11\xbb\xb2\xbb"
|
||||||
|
|||||||
344
tests/stress_test_history.py
Executable file
344
tests/stress_test_history.py
Executable file
@ -0,0 +1,344 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Firegex HttpHistory Real-Time Stress Test.
|
||||||
|
Connects to Firegex API, deploys an nfproxy HTTP service with HttpHistory filter enabled,
|
||||||
|
starts an HTTP 200 OK backend server and continuous HTTP client requests.
|
||||||
|
Measures real-time memory usage (Firegex cpproxy / local process) and request throughput until stopped (Ctrl+C).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
import argparse
|
||||||
|
import traceback
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
# Add project paths
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../fgex-lib")))
|
||||||
|
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
HAS_PSUTIL = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_PSUTIL = False
|
||||||
|
|
||||||
|
from tests.utils.colors import colors, puts, sep
|
||||||
|
from tests.utils.firegexapi import FiregexAPI
|
||||||
|
|
||||||
|
HTTP_STRESS_FILTER_CODE = """
|
||||||
|
from firegex.nfproxy.models import HttpFullRequest, HttpFullResponse, HttpHistory
|
||||||
|
from firegex.nfproxy import pyfilter, ACCEPT
|
||||||
|
|
||||||
|
req_count = 0
|
||||||
|
resp_count = 0
|
||||||
|
|
||||||
|
@pyfilter
|
||||||
|
def history_stress_req(req: HttpFullRequest, history: HttpHistory):
|
||||||
|
global req_count
|
||||||
|
req_count += 1
|
||||||
|
_ = len(history.requests)
|
||||||
|
_ = len(history.responses)
|
||||||
|
return ACCEPT
|
||||||
|
|
||||||
|
@pyfilter
|
||||||
|
def history_stress_resp(resp: HttpFullResponse, history: HttpHistory):
|
||||||
|
global resp_count
|
||||||
|
resp_count += 1
|
||||||
|
_ = len(history.requests)
|
||||||
|
_ = len(history.responses)
|
||||||
|
return ACCEPT
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def start_http_backend_server(port: int, ipv6: bool = False):
|
||||||
|
"""Starts a multithreaded HTTP backend server that returns valid HTTP 200 OK responses."""
|
||||||
|
server_sock = socket.socket(socket.AF_INET6 if ipv6 else socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
server_sock.bind(("::1" if ipv6 else "127.0.0.1", port))
|
||||||
|
server_sock.listen(128)
|
||||||
|
server_sock.settimeout(0.5)
|
||||||
|
|
||||||
|
running = True
|
||||||
|
|
||||||
|
def handle_client(conn):
|
||||||
|
conn.settimeout(2.0)
|
||||||
|
resp = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\nConnection: keep-alive\r\n\r\nHello World!"
|
||||||
|
try:
|
||||||
|
while running:
|
||||||
|
buf = conn.recv(4096)
|
||||||
|
if not buf:
|
||||||
|
break
|
||||||
|
conn.sendall(resp)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
conn.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def server_loop():
|
||||||
|
while running:
|
||||||
|
try:
|
||||||
|
conn, _ = server_sock.accept()
|
||||||
|
t = threading.Thread(target=handle_client, args=(conn,), daemon=True)
|
||||||
|
t.start()
|
||||||
|
except socket.timeout:
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
server_sock.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
t = threading.Thread(target=server_loop, daemon=True)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
def stop_server():
|
||||||
|
nonlocal running
|
||||||
|
running = False
|
||||||
|
try:
|
||||||
|
server_sock.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return stop_server
|
||||||
|
|
||||||
|
|
||||||
|
def find_cpproxy_process():
|
||||||
|
"""Finds cpproxy process using psutil if running."""
|
||||||
|
if not HAS_PSUTIL:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
|
||||||
|
name = proc.info['name'] or ''
|
||||||
|
cmdline = ' '.join(proc.info['cmdline'] or [])
|
||||||
|
if 'cpproxy' in name or 'cpproxy' in cmdline:
|
||||||
|
return proc
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_memory_mb(proc=None) -> float:
|
||||||
|
"""Returns memory usage in MB for a given process or current process."""
|
||||||
|
try:
|
||||||
|
if proc:
|
||||||
|
return proc.memory_info().rss / (1024 * 1024)
|
||||||
|
if HAS_PSUTIL:
|
||||||
|
return psutil.Process(os.getpid()).memory_info().rss / (1024 * 1024)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def create_client_socket(port: int, ipv6: bool = False) -> socket.socket:
|
||||||
|
s = socket.socket(socket.AF_INET6 if ipv6 else socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(2.0)
|
||||||
|
s.connect(("::1" if ipv6 else "127.0.0.1", port))
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Firegex HttpHistory Stress Test")
|
||||||
|
parser.add_argument("--address", "-a", type=str, default="http://127.0.0.1:4444/", help="Firegex API address (default: http://127.0.0.1:4444/)")
|
||||||
|
parser.add_argument("--password", "-p", type=str, required=True, help="Firegex admin password")
|
||||||
|
parser.add_argument("--service-name", "-n", type=str, default="StressTestHistory", help="Service name (default: StressTestHistory)")
|
||||||
|
parser.add_argument("--port", "-P", type=int, default=1337, help="Service port (default: 1337)")
|
||||||
|
parser.add_argument("--ipv6", "-6", action="store_true", default=False, help="Use IPv6")
|
||||||
|
parser.add_argument("--connections", "-c", type=int, default=4, help="Number of concurrent client connections (default: 4)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
sep()
|
||||||
|
puts("🔥 Firegex HttpHistory Real-Time Stress Test 🔥", color=colors.cyan, is_bold=True)
|
||||||
|
puts(f"Firegex API: {args.address}", color=colors.yellow)
|
||||||
|
puts(f"Target Port: {args.port}", color=colors.yellow)
|
||||||
|
puts(f"Connections: {args.connections}", color=colors.yellow)
|
||||||
|
puts("Press Ctrl+C at any time to stop the stress test.", color=colors.magenta, is_bold=True)
|
||||||
|
sep()
|
||||||
|
|
||||||
|
firegex = FiregexAPI(args.address)
|
||||||
|
|
||||||
|
# 1. Login to Firegex
|
||||||
|
puts("Logging in to Firegex API...", color=colors.cyan)
|
||||||
|
if firegex.login(args.password):
|
||||||
|
puts("Successfully logged in ✔", color=colors.green)
|
||||||
|
else:
|
||||||
|
puts("Error: Login failed! Check password or address ✗", color=colors.red)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 2. Cleanup existing service if any
|
||||||
|
for srv in firegex.nfproxy_get_services():
|
||||||
|
if srv.get("name") == args.service_name or srv.get("port") == args.port:
|
||||||
|
puts(f"Cleaning up existing service ID {srv['service_id']}...", color=colors.yellow)
|
||||||
|
firegex.nfproxy_delete_service(srv["service_id"])
|
||||||
|
|
||||||
|
# 3. Create service in Firegex
|
||||||
|
ip_int = "::1" if args.ipv6 else "127.0.0.1"
|
||||||
|
service_id = firegex.nfproxy_add_service(args.service_name, args.port, "http", ip_int, fail_open=True)
|
||||||
|
if not service_id:
|
||||||
|
puts("Error: Failed to create nfproxy service in Firegex ✗", color=colors.red)
|
||||||
|
sys.exit(1)
|
||||||
|
puts(f"Created nfproxy service ID: {service_id} ✔", color=colors.green)
|
||||||
|
|
||||||
|
def cleanup_and_exit(code=0):
|
||||||
|
puts("\nCleaning up Firegex service...", color=colors.yellow)
|
||||||
|
try:
|
||||||
|
firegex.nfproxy_stop_service(service_id)
|
||||||
|
firegex.nfproxy_delete_service(service_id)
|
||||||
|
puts("Successfully cleaned up Firegex service ✔", color=colors.green)
|
||||||
|
except Exception as e:
|
||||||
|
puts(f"Error during cleanup: {e}", color=colors.red)
|
||||||
|
sys.exit(code)
|
||||||
|
|
||||||
|
# 4. Upload HttpHistory filter code
|
||||||
|
puts("Deploying HttpHistory pyfilter code to Firegex...", color=colors.cyan)
|
||||||
|
if not firegex.nfproxy_set_code(service_id, HTTP_STRESS_FILTER_CODE):
|
||||||
|
puts("Error: Failed to upload pyfilter code ✗", color=colors.red)
|
||||||
|
cleanup_and_exit(1)
|
||||||
|
puts("Successfully deployed HttpHistory filter ✔", color=colors.green)
|
||||||
|
|
||||||
|
# 5. Start Firegex Service
|
||||||
|
puts("Starting Firegex service...", color=colors.cyan)
|
||||||
|
if not firegex.nfproxy_start_service(service_id):
|
||||||
|
puts("Error: Failed to start Firegex service ✗", color=colors.red)
|
||||||
|
cleanup_and_exit(1)
|
||||||
|
puts("Firegex service started successfully ✔", color=colors.green)
|
||||||
|
|
||||||
|
# 6. Start HTTP Backend Server
|
||||||
|
stop_backend_server = start_http_backend_server(args.port, ipv6=args.ipv6)
|
||||||
|
puts(f"HTTP Backend server listening on port {args.port} ✔", color=colors.green)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
# Find cpproxy process if available
|
||||||
|
cpproxy_proc = find_cpproxy_process()
|
||||||
|
if cpproxy_proc:
|
||||||
|
puts(f"Detected Firegex cpproxy process (PID {cpproxy_proc.pid}) ✔", color=colors.green)
|
||||||
|
|
||||||
|
# 7. Connect client sockets
|
||||||
|
running = True
|
||||||
|
|
||||||
|
def sig_handler(sig, frame):
|
||||||
|
nonlocal running
|
||||||
|
running = False
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, sig_handler)
|
||||||
|
signal.signal(signal.SIGTERM, sig_handler)
|
||||||
|
|
||||||
|
client_socks: List[Optional[socket.socket]] = []
|
||||||
|
for _ in range(args.connections):
|
||||||
|
try:
|
||||||
|
client_socks.append(create_client_socket(args.port, args.ipv6))
|
||||||
|
except Exception:
|
||||||
|
client_socks.append(None)
|
||||||
|
|
||||||
|
puts(f"Initialized {len(client_socks)} client socket slot(s) ✔", color=colors.green)
|
||||||
|
puts("\n🚀 Spamming HTTP requests continuously. Press Ctrl+C to stop...\n", color=colors.cyan, is_bold=True)
|
||||||
|
|
||||||
|
req_index = 0
|
||||||
|
total_requests = 0
|
||||||
|
start_time = time.time()
|
||||||
|
last_print_time = start_time
|
||||||
|
last_req_count = 0
|
||||||
|
peak_script_mem = get_memory_mb()
|
||||||
|
peak_cpproxy_mem = get_memory_mb(cpproxy_proc) if cpproxy_proc else 0.0
|
||||||
|
|
||||||
|
try:
|
||||||
|
while running:
|
||||||
|
for i in range(len(client_socks)):
|
||||||
|
if not running:
|
||||||
|
break
|
||||||
|
|
||||||
|
s = client_socks[i]
|
||||||
|
if s is None:
|
||||||
|
try:
|
||||||
|
s = create_client_socket(args.port, args.ipv6)
|
||||||
|
client_socks[i] = s
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
req_index += 1
|
||||||
|
req_bytes = f"GET /stress_req_{req_index} HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: keep-alive\r\nUser-Agent: FiregexStressClient\r\n\r\n".encode()
|
||||||
|
|
||||||
|
try:
|
||||||
|
s.sendall(req_bytes)
|
||||||
|
resp = s.recv(4096)
|
||||||
|
if resp and b"200 OK" in resp:
|
||||||
|
total_requests += 1
|
||||||
|
else:
|
||||||
|
raise ConnectionResetError("Invalid or empty HTTP response")
|
||||||
|
except Exception:
|
||||||
|
# Safely close broken socket and mark slot for reconnection
|
||||||
|
try:
|
||||||
|
s.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
client_socks[i] = None
|
||||||
|
|
||||||
|
curr_time = time.time()
|
||||||
|
elapsed_print = curr_time - last_print_time
|
||||||
|
|
||||||
|
if elapsed_print >= 1.0 or not running:
|
||||||
|
script_mem = get_memory_mb()
|
||||||
|
cpproxy_mem = get_memory_mb(cpproxy_proc) if cpproxy_proc else 0.0
|
||||||
|
|
||||||
|
if script_mem > peak_script_mem:
|
||||||
|
peak_script_mem = script_mem
|
||||||
|
if cpproxy_mem > peak_cpproxy_mem:
|
||||||
|
peak_cpproxy_mem = cpproxy_mem
|
||||||
|
|
||||||
|
batch_reqs = total_requests - last_req_count
|
||||||
|
reqs_per_sec = batch_reqs / elapsed_print if elapsed_print > 0 else 0
|
||||||
|
total_elapsed = curr_time - start_time
|
||||||
|
|
||||||
|
mem_str = f"Script RSS: {script_mem:.1f}MB"
|
||||||
|
if cpproxy_proc:
|
||||||
|
mem_str += f" | cpproxy RSS: {cpproxy_mem:.1f}MB (Peak: {peak_cpproxy_mem:.1f}MB)"
|
||||||
|
|
||||||
|
sys.stdout.write(
|
||||||
|
f"\r\033[K[{time.strftime('%H:%M:%S')}] Elapsed: {total_elapsed:.1f}s | "
|
||||||
|
f"Reqs: {total_requests:,} | "
|
||||||
|
f"Speed: {reqs_per_sec:,.0f} req/s | "
|
||||||
|
f"{mem_str}"
|
||||||
|
)
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
last_print_time = curr_time
|
||||||
|
last_req_count = total_requests
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|
||||||
|
end_time = time.time()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
# Close client sockets
|
||||||
|
for s in client_socks:
|
||||||
|
if s:
|
||||||
|
try:
|
||||||
|
s.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
stop_backend_server()
|
||||||
|
|
||||||
|
print() # New line
|
||||||
|
sep()
|
||||||
|
puts("🛑 Stress Test Interrupted by User", color=colors.yellow, is_bold=True)
|
||||||
|
puts(f"Total Time Elapsed: {duration:.2f} seconds")
|
||||||
|
puts(f"Total HTTP Reqs: {total_requests:,}")
|
||||||
|
puts(f"Average Throughput: {total_requests / max(duration, 0.001):,.0f} req/sec")
|
||||||
|
if cpproxy_proc:
|
||||||
|
puts(f"cpproxy Peak Memory: {peak_cpproxy_mem:.2f} MB", color=colors.cyan, is_bold=True)
|
||||||
|
sep()
|
||||||
|
|
||||||
|
cleanup_and_exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue
Block a user