From 7ee89fa19426e3bedc705432b62ed1b319d05c4d Mon Sep 17 00:00:00 2001 From: LapshinAE0 Date: Tue, 17 Feb 2026 00:38:35 +0300 Subject: [PATCH 1/9] generator tcp-udp --- scripts/gen_traf_tcp-udp/README | 24 +++ .../gen_traf_tcp-udp/generate_rand_traf.py | 173 ++++++++++++++++++ scripts/gen_traf_tcp-udp/generate_traf.sh | 23 +++ scripts/gen_traf_tcp-udp/sites.txt | 10 + 4 files changed, 230 insertions(+) create mode 100644 scripts/gen_traf_tcp-udp/README create mode 100644 scripts/gen_traf_tcp-udp/generate_rand_traf.py create mode 100755 scripts/gen_traf_tcp-udp/generate_traf.sh create mode 100644 scripts/gen_traf_tcp-udp/sites.txt diff --git a/scripts/gen_traf_tcp-udp/README b/scripts/gen_traf_tcp-udp/README new file mode 100644 index 0000000..7086e94 --- /dev/null +++ b/scripts/gen_traf_tcp-udp/README @@ -0,0 +1,24 @@ +Traffic Generator +Асинхронный генератор трафика для тестирования сетевых подключений. Запускает заданное количество запросов к случайным сайтам из списка с контролем RPS (requests per second), таймаутами и ограничением параллельных задач. Результаты сохраняются в JSON-лог. + + +generate_traf.py -- основной скрипт на Python +generate_traf.sh -- вспомогательный bash-скрипт, вызывается для каждого запроса + +Для запуска: + 1. Сделайте bash-скрипт исполняемым: chmod +x generate_traf.sh + 2. Создайте файл со списком сайтов (или используйте пример приведенного списка: sites.txt). В каждой строке – доменное имя (без протокола и порта). + + +Запуск осуществляется командой: python3 generate_traf.py [--quantity N] [--rps N] [--timeout N] [--file FILENAME] [--max_concurrent N] + +Параметры: + Короткая Полная Значение поумолчанию + -q --quantity Количество запросов (обязательный параметр) 10 + -r --rps Желаемое количество запросов в секунду 10 + -t --timeout Таймаут на один запрос (секунды) 5 + -f --file Файл со списком сайтов sites.txt + -m --max_concurrent Максимальное число одновременно выполняемых задач 50 + + +После выполнения всех запросов создаётся JSON-файл с именем вида log_YYYYMMDD_HHMMSS.json. \ No newline at end of file diff --git a/scripts/gen_traf_tcp-udp/generate_rand_traf.py b/scripts/gen_traf_tcp-udp/generate_rand_traf.py new file mode 100644 index 0000000..af32002 --- /dev/null +++ b/scripts/gen_traf_tcp-udp/generate_rand_traf.py @@ -0,0 +1,173 @@ +import asyncio +import random +import sys +import argparse +from datetime import datetime +import json + + +def check(): + parser = argparse.ArgumentParser( + description="Генератор трафика с контролем RPS и таймаутами", + formatter_class=argparse.RawTextHelpFormatter + ) + parser.add_argument( + "--quantity", "-q", + type=int, + default=10, + help="Количество запросов (обязательный параметр)" + ) + parser.add_argument( + "--rps", "-r", + type=int, + default=10, + help="Желаемое количество запросов в секунду (по умолчанию 10)" + ) + parser.add_argument( + "--timeout", "-t", + type=int, + default=5, + help="Таймаут на один запрос в секундах (по умолчанию 5)" + ) + parser.add_argument( + "--file", "-f", + type=str, + default="sites.txt", + help="Файл со списком сайтов (по умолчанию sites.txt)" + ) + parser.add_argument( + "--max_concurrent", "-m", + type=int, + default=50, + help="Максимальное количество одновременно выполеняемых задач (по умолчанию 50)" + ) + + args = parser.parse_args() + + if args.quantity <= 0: + print("Ошибка: количество запросов должно быть положительным числом") + sys.exit(1) + if args.rps <= 0: + print("Ошибка: RPS должно быть положительным числом, используем значение по умолчанию 10") + args.rps = 10 + if args.timeout <= 0: + print("Ошибка: таймаут должен быть положительным числом, используем значение по умолчанию 5") + args.timeout = 5 + if args.max_concurrent <= 0: + print("Ошибка: количество одновременно выполеняемых задач должно быть положительным числом, используем значение по умолчанию 50") + args.max_concurrent = 50 + + try: + with open(args.file, 'r') as f: + sites = [line.strip() for line in f if line.strip()] + except FileNotFoundError: + print(f"Ошибка: файл '{args.file}' не найден") + sys.exit(1) + + if not sites: + print(f"Ошибка: файл '{args.file}' пуст") + sys.exit(1) + + return args.quantity, sites, args.rps, args.timeout, args.max_concurrent + + +async def check_one(site, timeout): + try: + process = await asyncio.create_subprocess_exec('./generate_traf.sh', '1',\ + site, stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL) + await asyncio.wait_for(process.communicate(), timeout=timeout) + if process.returncode == 0: + return site, 0 + return site, -1 + + except asyncio.TimeoutError: + process.kill() + await process.wait() + return site, 1 + +def log(quantity, rps, timeout, max_concurrent, results): + success_count = 0 + timeout_count = 0 + error_count = 0 + fatal_error_count = 0 + + log_data = { + "parameters": { + "quantity": quantity, + "rps": rps, + "timeout": timeout, + "max_concurrent": max_concurrent + }, + "results": [] + } + + for res in results: + if isinstance(res, Exception): + fatal_error_count += 1 + log_data["results"].append({ + "site": "unknown", + "status": "exception", + "details": str(res) + }) + else: + site, code = res + if code == 0: + status = "success" + success_count += 1 + elif code == 1: + status = "timeout" + timeout_count += 1 + else: + status = "error" + error_count += 1 + + log_data["results"].append({ + "site": site, + "status": status, + "code": code + }) + + log_data["statistics"] = { + "success": success_count, + "timeout": timeout_count, + "error": error_count, + "total": quantity, + "fatal_error": fatal_error_count + } + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"log_{timestamp}.json" + + try: + with open(filename, 'w', encoding='utf-8') as file: + json.dump(log_data, file, indent=2, ensure_ascii=False) + print(f"\nЛоги сохранёны в файл: {filename}") + except Exception as e: + print(f"\nОшибка при сохранении логов: {e}") + + +async def main(): + quantity, sites, rps, timeout, max_concurrent = check() + + semaphore = asyncio.Semaphore(max_concurrent) + delay = 1.0 / rps + + tasks = [] + for _ in range(quantity): + site = random.choice(sites) + async def task_wrapper(): + async with semaphore: + return await check_one(site, timeout) + + task = asyncio.create_task(task_wrapper()) + tasks.append(task) + + await asyncio.sleep(delay) + + results = await asyncio.gather(*tasks, return_exceptions=True) + + log(quantity, rps, timeout, max_concurrent, results) + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/scripts/gen_traf_tcp-udp/generate_traf.sh b/scripts/gen_traf_tcp-udp/generate_traf.sh new file mode 100755 index 0000000..785691a --- /dev/null +++ b/scripts/gen_traf_tcp-udp/generate_traf.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +count=$1 +site=$2 + +if [ -z "$count" ] || [ -z "$site" ]; then + echo "Error: the argument was not passed!" + echo "Usage: ./generate_traf.sh " + echo "Example: ./generate_traf.sh 8 google.com" + exit 1 +fi + +if ! [[ "$count" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: '$count' is not a positive number!" + echo "Usage: ./generate_traf.sh " + echo "Example: ./generate_traf.sh 8 google.com" + exit 1 +fi + +for (( i=1; i<=$count;i++ )); do + nc -zv $site 80 + nc -uzv $site 80 +done diff --git a/scripts/gen_traf_tcp-udp/sites.txt b/scripts/gen_traf_tcp-udp/sites.txt new file mode 100644 index 0000000..dd5b93d --- /dev/null +++ b/scripts/gen_traf_tcp-udp/sites.txt @@ -0,0 +1,10 @@ +4chan.org +www.reddit.com +www.yahoo.com +www.cnn.com +www.ebay.com +wikipedia.org +youtube.com +github.com +medium.com +thepiratebay.org \ No newline at end of file From a067dd1edda5c8b08cf3f20235c2e1e0915dc1d1 Mon Sep 17 00:00:00 2001 From: LapshinAE0 Date: Sun, 22 Feb 2026 00:05:16 +0300 Subject: [PATCH 2/9] fixed 1 --- .../gen_traf_tcp-udp/{README => README.md} | 2 +- .../gen_traf_tcp-udp/generate_rand_traf.py | 55 +++++++++++-------- 2 files changed, 33 insertions(+), 24 deletions(-) rename scripts/gen_traf_tcp-udp/{README => README.md} (96%) diff --git a/scripts/gen_traf_tcp-udp/README b/scripts/gen_traf_tcp-udp/README.md similarity index 96% rename from scripts/gen_traf_tcp-udp/README rename to scripts/gen_traf_tcp-udp/README.md index 7086e94..696d3ea 100644 --- a/scripts/gen_traf_tcp-udp/README +++ b/scripts/gen_traf_tcp-udp/README.md @@ -2,7 +2,7 @@ Traffic Generator Асинхронный генератор трафика для тестирования сетевых подключений. Запускает заданное количество запросов к случайным сайтам из списка с контролем RPS (requests per second), таймаутами и ограничением параллельных задач. Результаты сохраняются в JSON-лог. -generate_traf.py -- основной скрипт на Python +generate_rand_traf.py -- основной скрипт на Python generate_traf.sh -- вспомогательный bash-скрипт, вызывается для каждого запроса Для запуска: diff --git a/scripts/gen_traf_tcp-udp/generate_rand_traf.py b/scripts/gen_traf_tcp-udp/generate_rand_traf.py index af32002..9d009ba 100644 --- a/scripts/gen_traf_tcp-udp/generate_rand_traf.py +++ b/scripts/gen_traf_tcp-udp/generate_rand_traf.py @@ -3,87 +3,96 @@ import sys import argparse from datetime import datetime +from enum import Enum import json +import socket -def check(): + +class ResultFunction(Enum): + TIME_EXCEEDED = 1 + REQUEST_COMPLETED = 0 + ERROR_EXECUTING_SCRIPT = -1 + +def pars(): parser = argparse.ArgumentParser( - description="Генератор трафика с контролем RPS и таймаутами", + description="Traffic generator with RPS control and timeouts", formatter_class=argparse.RawTextHelpFormatter ) parser.add_argument( "--quantity", "-q", type=int, default=10, - help="Количество запросов (обязательный параметр)" + help="Number of requests (10 by default)" ) parser.add_argument( "--rps", "-r", type=int, default=10, - help="Желаемое количество запросов в секунду (по умолчанию 10)" + help="The desired number of requests per second (10 by default)" ) parser.add_argument( "--timeout", "-t", type=int, default=5, - help="Таймаут на один запрос в секундах (по умолчанию 5)" + help="Timeout per request in seconds (5 by default)" ) parser.add_argument( "--file", "-f", type=str, default="sites.txt", - help="Файл со списком сайтов (по умолчанию sites.txt)" + help="A file with a list of sites (by default sites.txt )" ) parser.add_argument( "--max_concurrent", "-m", type=int, default=50, - help="Максимальное количество одновременно выполеняемых задач (по умолчанию 50)" + help="Maximum number of simultaneous tasks (50 by default)" ) args = parser.parse_args() if args.quantity <= 0: - print("Ошибка: количество запросов должно быть положительным числом") - sys.exit(1) + print("Error: the number of requests must be a positive number, using the default value of 10") + args.quantity = 10 if args.rps <= 0: - print("Ошибка: RPS должно быть положительным числом, используем значение по умолчанию 10") + print("Error: RPS must be a positive number, using the default value of 10") args.rps = 10 if args.timeout <= 0: - print("Ошибка: таймаут должен быть положительным числом, используем значение по умолчанию 5") + print("Error: the timeout must be a positive number, using the default value of 5") args.timeout = 5 if args.max_concurrent <= 0: - print("Ошибка: количество одновременно выполеняемых задач должно быть положительным числом, используем значение по умолчанию 50") + print("Error: the number of tasks being completed at the same time must be a positive number, using the default value of 50") args.max_concurrent = 50 try: with open(args.file, 'r') as f: sites = [line.strip() for line in f if line.strip()] except FileNotFoundError: - print(f"Ошибка: файл '{args.file}' не найден") + print(f"Error: file '{args.file}' not found") sys.exit(1) if not sites: - print(f"Ошибка: файл '{args.file}' пуст") + print(f"Error: file '{args.file}' is empty") sys.exit(1) return args.quantity, sites, args.rps, args.timeout, args.max_concurrent - async def check_one(site, timeout): try: + # process_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + # process_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) process = await asyncio.create_subprocess_exec('./generate_traf.sh', '1',\ site, stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL) await asyncio.wait_for(process.communicate(), timeout=timeout) if process.returncode == 0: - return site, 0 - return site, -1 + return site, ResultFunction.REQUEST_COMPLETED + return site, ResultFunction.ERROR_EXECUTING_SCRIPT except asyncio.TimeoutError: process.kill() await process.wait() - return site, 1 + return site, ResultFunction.TIME_EXCEEDED def log(quantity, rps, timeout, max_concurrent, results): success_count = 0 @@ -141,13 +150,13 @@ def log(quantity, rps, timeout, max_concurrent, results): try: with open(filename, 'w', encoding='utf-8') as file: json.dump(log_data, file, indent=2, ensure_ascii=False) - print(f"\nЛоги сохранёны в файл: {filename}") + print(f"\nLogs are saved to a file: {filename}") except Exception as e: - print(f"\nОшибка при сохранении логов: {e}") + print(f"\nError saving logs: {e}") async def main(): - quantity, sites, rps, timeout, max_concurrent = check() + quantity, sites, rps, timeout, max_concurrent = pars() semaphore = asyncio.Semaphore(max_concurrent) delay = 1.0 / rps @@ -155,9 +164,9 @@ async def main(): tasks = [] for _ in range(quantity): site = random.choice(sites) - async def task_wrapper(): + async def task_wrapper(certain_site=site): async with semaphore: - return await check_one(site, timeout) + return await check_one(certain_site, timeout) task = asyncio.create_task(task_wrapper()) tasks.append(task) From 1a3f0936035fd1773e6760a3761907feea0d5332 Mon Sep 17 00:00:00 2001 From: LapshinAE0 Date: Wed, 25 Feb 2026 15:41:30 +0300 Subject: [PATCH 3/9] add loging --- .../gen_traf_tcp-udp/generate_rand_traf.py | 81 +++++++++++++++---- 1 file changed, 64 insertions(+), 17 deletions(-) diff --git a/scripts/gen_traf_tcp-udp/generate_rand_traf.py b/scripts/gen_traf_tcp-udp/generate_rand_traf.py index 9d009ba..66bbaa9 100644 --- a/scripts/gen_traf_tcp-udp/generate_rand_traf.py +++ b/scripts/gen_traf_tcp-udp/generate_rand_traf.py @@ -6,7 +6,8 @@ from enum import Enum import json import socket - +import logging +from pathlib import Path class ResultFunction(Enum): @@ -49,6 +50,18 @@ def pars(): default=50, help="Maximum number of simultaneous tasks (50 by default)" ) + parser.add_argument( + "--log_level", "-l", + type=str, + default="INFO", + choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], + help="Logging level: DEBUG, INFO, WARNING, ERROR, CRITICAL (INFO by default)" + ) + parser.add_argument( + "--console_log", "-ncl", + action="store_true", + help="Disable console logging (by default console logging is enabled)" + ) args = parser.parse_args() @@ -76,7 +89,7 @@ def pars(): print(f"Error: file '{args.file}' is empty") sys.exit(1) - return args.quantity, sites, args.rps, args.timeout, args.max_concurrent + return args.quantity, sites, args.rps, args.timeout, args.max_concurrent, args.log_level, args.console_log async def check_one(site, timeout): try: @@ -94,11 +107,42 @@ async def check_one(site, timeout): await process.wait() return site, ResultFunction.TIME_EXCEEDED -def log(quantity, rps, timeout, max_concurrent, results): - success_count = 0 - timeout_count = 0 - error_count = 0 - fatal_error_count = 0 +def setup_logger(flag_stream_handler, input_level_logging): + log_dir = Path("logs") + log_dir.mkdir(exist_ok=True) + file_log = log_dir / f"LOG: {datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.json" + + + logger = logging.getLogger(__name__) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + + logger.setLevel(input_level_logging) + + + + if flag_stream_handler: + console_handler = logging.StreamHandler() + console_handler.setLevel(input_level_logging) + console_handler.setFormatter(formatter) + logger.addHandler(console_handler) + + + file_handler = logging.FileHandler(file_log, encoding='utf-8') + file_handler.setLevel(input_level_logging) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + return logger, file_log + +def log(quantity, rps, timeout, max_concurrent, results, logger, file_log): + + logger.info(f" Запросов: {quantity}") + logger.info(f" RPS: {rps}") + logger.info(f" Таймаут: {timeout}с") + logger.info(f" Конкурентность: {max_concurrent}") + logger.debug(f" Файл результатов: {file_log}") + + success_count = timeout_count = error_count = fatal_error_count = 0 log_data = { "parameters": { @@ -118,22 +162,27 @@ def log(quantity, rps, timeout, max_concurrent, results): "status": "exception", "details": str(res) }) + logger.exception(f"Request to {site} - EXCEPTION: {res}") + else: site, code = res - if code == 0: + if code.value == 0: status = "success" success_count += 1 - elif code == 1: + logger.debug(f"Request to {site} - SUCCESS") + elif code.value == 1: status = "timeout" timeout_count += 1 + logger.error(f"Request to {site} - TIMEOUT") else: status = "error" error_count += 1 + logger.error(f"Request to {site} - ERROR") log_data["results"].append({ "site": site, "status": status, - "code": code + "code": code.name }) log_data["statistics"] = { @@ -144,19 +193,16 @@ def log(quantity, rps, timeout, max_concurrent, results): "fatal_error": fatal_error_count } - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = f"log_{timestamp}.json" - try: - with open(filename, 'w', encoding='utf-8') as file: + with open(file_log, 'w', encoding='utf-8') as file: json.dump(log_data, file, indent=2, ensure_ascii=False) - print(f"\nLogs are saved to a file: {filename}") + print(f"\nLogs are saved to a file: {file_log}") except Exception as e: print(f"\nError saving logs: {e}") async def main(): - quantity, sites, rps, timeout, max_concurrent = pars() + quantity, sites, rps, timeout, max_concurrent, log_level, console_log = pars() semaphore = asyncio.Semaphore(max_concurrent) delay = 1.0 / rps @@ -175,7 +221,8 @@ async def task_wrapper(certain_site=site): results = await asyncio.gather(*tasks, return_exceptions=True) - log(quantity, rps, timeout, max_concurrent, results) + logger, file_log = setup_logger(console_log, log_level) + log(quantity, rps, timeout, max_concurrent, results, logger, file_log) if __name__ == "__main__": From a89194ae24e22670510b8c275d8937c50d565e07 Mon Sep 17 00:00:00 2001 From: LapshinAE0 Date: Thu, 26 Feb 2026 03:53:48 +0300 Subject: [PATCH 4/9] Replacing the bash script with nmap --- .../gen_traf_tcp-udp/generate_rand_traf.py | 126 ++++++++++++------ 1 file changed, 86 insertions(+), 40 deletions(-) diff --git a/scripts/gen_traf_tcp-udp/generate_rand_traf.py b/scripts/gen_traf_tcp-udp/generate_rand_traf.py index 66bbaa9..1c030dd 100644 --- a/scripts/gen_traf_tcp-udp/generate_rand_traf.py +++ b/scripts/gen_traf_tcp-udp/generate_rand_traf.py @@ -3,17 +3,12 @@ import sys import argparse from datetime import datetime -from enum import Enum import json -import socket import logging from pathlib import Path +import nmap -class ResultFunction(Enum): - TIME_EXCEEDED = 1 - REQUEST_COMPLETED = 0 - ERROR_EXECUTING_SCRIPT = -1 def pars(): parser = argparse.ArgumentParser( @@ -35,7 +30,7 @@ def pars(): parser.add_argument( "--timeout", "-t", type=int, - default=5, + default=17, help="Timeout per request in seconds (5 by default)" ) parser.add_argument( @@ -72,8 +67,8 @@ def pars(): print("Error: RPS must be a positive number, using the default value of 10") args.rps = 10 if args.timeout <= 0: - print("Error: the timeout must be a positive number, using the default value of 5") - args.timeout = 5 + print("Error: the timeout must be a positive number, using the default value of 17 - the optimal time for analyzing a compound is at standard values.") + args.timeout = 17 if args.max_concurrent <= 0: print("Error: the number of tasks being completed at the same time must be a positive number, using the default value of 50") args.max_concurrent = 50 @@ -92,20 +87,63 @@ def pars(): return args.quantity, sites, args.rps, args.timeout, args.max_concurrent, args.log_level, args.console_log async def check_one(site, timeout): - try: - # process_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - # process_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - process = await asyncio.create_subprocess_exec('./generate_traf.sh', '1',\ - site, stdout=asyncio.subprocess.DEVNULL, stderr=asyncio.subprocess.DEVNULL) - await asyncio.wait_for(process.communicate(), timeout=timeout) - if process.returncode == 0: - return site, ResultFunction.REQUEST_COMPLETED - return site, ResultFunction.ERROR_EXECUTING_SCRIPT + scan_timeout = max(2, timeout - 2) + + result = { + 'site': site, + 'tcp_ports': {}, + 'udp_ports': {}, + 'status': 'unknown', + 'ip': None + } + try: + nm = nmap.PortScanner() + + await asyncio.wait_for( + asyncio.to_thread( + nm.scan, + site, + '53,80,443,123,161', + f'-sS -sU -T4 --host-timeout {scan_timeout}s' + ), + timeout + ) + + if nm.all_hosts(): + result['ip'] = nm.all_hosts()[0] + + for proto in nm[result['ip']].all_protocols(): + for port in nm[result['ip']][proto].keys(): + state = nm[result['ip']][proto][port]['state'] + service = nm[result['ip']][proto][port].get('name', 'unknown') + + if state == 'open': + if proto == 'tcp': + result['tcp_ports'][port] = { + 'state': state, + 'service': service + } + elif proto == 'udp': + result['udp_ports'][port] = { + 'state': state, + 'service': service + } + + if result['tcp_ports'] or result['udp_ports']: + result['status'] = 'success' + else: + result['status'] = 'no_open_ports' + except asyncio.TimeoutError: - process.kill() - await process.wait() - return site, ResultFunction.TIME_EXCEEDED + result['status'] = 'timeout' + + except Exception as e: + result['status'] = 'error' + result['error'] = str(e) + + return result + def setup_logger(flag_stream_handler, input_level_logging): log_dir = Path("logs") @@ -142,7 +180,7 @@ def log(quantity, rps, timeout, max_concurrent, results, logger, file_log): logger.info(f" Конкурентность: {max_concurrent}") logger.debug(f" Файл результатов: {file_log}") - success_count = timeout_count = error_count = fatal_error_count = 0 + no_ports_count = success_count = timeout_count = error_count = 0 log_data = { "parameters": { @@ -156,41 +194,49 @@ def log(quantity, rps, timeout, max_concurrent, results, logger, file_log): for res in results: if isinstance(res, Exception): - fatal_error_count += 1 log_data["results"].append({ "site": "unknown", "status": "exception", "details": str(res) }) - logger.exception(f"Request to {site} - EXCEPTION: {res}") + logger.exception(f"Request - EXCEPTION: {res}") else: - site, code = res - if code.value == 0: - status = "success" + site_data = res + + log_entry = { + "site": site_data['site'], + "ip": site_data['ip'], + "status": site_data['status'], + "tcp_ports": site_data['tcp_ports'], + "udp_ports": site_data['udp_ports'] + } + + if 'error' in site_data: + log_entry["error"] = site_data['error'] + + log_data["results"].append(log_entry) + + if site_data['status'] == 'success': success_count += 1 - logger.debug(f"Request to {site} - SUCCESS") - elif code.value == 1: - status = "timeout" + logger.debug(f"Request to {site_data['site']} - SUCCESS") + elif site_data['status'] == 'timeout': timeout_count += 1 - logger.error(f"Request to {site} - TIMEOUT") + logger.error(f"Request to {site_data['site']} - TIMEOUT") + elif site_data['status'] == 'no_open_ports': + no_ports_count += 1 + logger.warning(f"Request to {site_data['site']} - NO OPEN PORTS") else: - status = "error" error_count += 1 - logger.error(f"Request to {site} - ERROR") + logger.error(f"Request to {site_data['site']} - ERROR") - log_data["results"].append({ - "site": site, - "status": status, - "code": code.name - }) log_data["statistics"] = { + "no_ports_count" : no_ports_count, "success": success_count, "timeout": timeout_count, "error": error_count, - "total": quantity, - "fatal_error": fatal_error_count + "total": quantity } try: From 74d7eacee5196905c9af1229257ffc52b0cd98e2 Mon Sep 17 00:00:00 2001 From: LapshinAE0 Date: Fri, 27 Feb 2026 03:42:45 +0300 Subject: [PATCH 5/9] add tests --- scripts/gen_traf_tcp-udp/README.md | 70 ++++++--- .../gen_traf_tcp-udp/generate_rand_traf.py | 46 +++--- scripts/gen_traf_tcp-udp/setup.sh | 7 + scripts/gen_traf_tcp-udp/tests.py | 139 ++++++++++++++++++ 4 files changed, 218 insertions(+), 44 deletions(-) create mode 100755 scripts/gen_traf_tcp-udp/setup.sh create mode 100644 scripts/gen_traf_tcp-udp/tests.py diff --git a/scripts/gen_traf_tcp-udp/README.md b/scripts/gen_traf_tcp-udp/README.md index 696d3ea..aa80f85 100644 --- a/scripts/gen_traf_tcp-udp/README.md +++ b/scripts/gen_traf_tcp-udp/README.md @@ -1,24 +1,46 @@ -Traffic Generator -Асинхронный генератор трафика для тестирования сетевых подключений. Запускает заданное количество запросов к случайным сайтам из списка с контролем RPS (requests per second), таймаутами и ограничением параллельных задач. Результаты сохраняются в JSON-лог. - - -generate_rand_traf.py -- основной скрипт на Python -generate_traf.sh -- вспомогательный bash-скрипт, вызывается для каждого запроса - -Для запуска: - 1. Сделайте bash-скрипт исполняемым: chmod +x generate_traf.sh - 2. Создайте файл со списком сайтов (или используйте пример приведенного списка: sites.txt). В каждой строке – доменное имя (без протокола и порта). - - -Запуск осуществляется командой: python3 generate_traf.py [--quantity N] [--rps N] [--timeout N] [--file FILENAME] [--max_concurrent N] - -Параметры: - Короткая Полная Значение поумолчанию - -q --quantity Количество запросов (обязательный параметр) 10 - -r --rps Желаемое количество запросов в секунду 10 - -t --timeout Таймаут на один запрос (секунды) 5 - -f --file Файл со списком сайтов sites.txt - -m --max_concurrent Максимальное число одновременно выполняемых задач 50 - - -После выполнения всех запросов создаётся JSON-файл с именем вида log_YYYYMMDD_HHMMSS.json. \ No newline at end of file +# Traffic Generator + +Асинхронный генератор трафика для тестирования сетевых подключений с использованием `nmap`. Запускает заданное количество запросов к случайным сайтам из списка с контролем RPS (requests per second), таймаутами и ограничением параллельных задач. Результаты сканирования портов (TCP/UDP) сохраняются в JSON-лог (по умолчанию logs/LOG: YYYY-MM-DD_HH-MM-SS-ffffff.json). + +## Требования + +- Python 3.7+ +- Nmap (устанавливается автоматически) +- Зависимости Python: `python-nmap` + +## Установка + +```bash +# Запустите скрипт установки (установит nmap и python-зависимости) +chmod +x setup.sh +./setup.sh +``` + + +## Использование +```bash +sudo python3 generate_rand_traf.py [параметры] +``` + +Параметры командной строки +Короткая Полная По умолчанию Описание +-q --quantity 10 Количество запросов +-r --rps 15 Желаемое количество запросов в секунду +-t --timeout 20 Таймаут на один запрос (секунды) +-f --file sites.txt Файл со списком сайтов +-m --max_concurrent 50 Максимальное число одновременно выполняемых задач +-l --log_level INFO Уровень логирования (DEBUG, INFO, WARNING, ERROR, CRITICAL) +-ncl --no_console_log (флаг) Отключить логирование в консоль +-n --name_folder_log logs Имя папки для логов + +## Тестирование +Проект включает интеграционные тесты для проверки основной функциональности: + +```bash +sudo python3 test.py +``` +Тесты проверяют: + Работу с заведомо существующими сайтами + Обработку несуществующих доменов (используются зарезервированные домены .test, .invalid, .local) + Смешанные сценарии с разными типами сайтов + Корректность подсчета статистики \ No newline at end of file diff --git a/scripts/gen_traf_tcp-udp/generate_rand_traf.py b/scripts/gen_traf_tcp-udp/generate_rand_traf.py index 1c030dd..2839619 100644 --- a/scripts/gen_traf_tcp-udp/generate_rand_traf.py +++ b/scripts/gen_traf_tcp-udp/generate_rand_traf.py @@ -24,14 +24,14 @@ def pars(): parser.add_argument( "--rps", "-r", type=int, - default=10, - help="The desired number of requests per second (10 by default)" + default=15, + help="The desired number of requests per second (15 by default)" ) parser.add_argument( "--timeout", "-t", type=int, - default=17, - help="Timeout per request in seconds (5 by default)" + default=20, + help="Timeout per request in seconds (20 by default)" ) parser.add_argument( "--file", "-f", @@ -53,10 +53,16 @@ def pars(): help="Logging level: DEBUG, INFO, WARNING, ERROR, CRITICAL (INFO by default)" ) parser.add_argument( - "--console_log", "-ncl", + "--no_console_log", "-ncl", action="store_true", help="Disable console logging (by default console logging is enabled)" ) + parser.add_argument( + "--name_folder_log", "-n", + type=str, + default="logs", + help="A name of folder logs(by default logs)" + ) args = parser.parse_args() @@ -64,11 +70,11 @@ def pars(): print("Error: the number of requests must be a positive number, using the default value of 10") args.quantity = 10 if args.rps <= 0: - print("Error: RPS must be a positive number, using the default value of 10") - args.rps = 10 + print("Error: RPS must be a positive number, using the default value of 15") + args.rps = 15 if args.timeout <= 0: - print("Error: the timeout must be a positive number, using the default value of 17 - the optimal time for analyzing a compound is at standard values.") - args.timeout = 17 + print("Error: the timeout must be a positive number, using the default value of 20 - the optimal time for analyzing a compound is at standard values.") + args.timeout = 20 if args.max_concurrent <= 0: print("Error: the number of tasks being completed at the same time must be a positive number, using the default value of 50") args.max_concurrent = 50 @@ -84,7 +90,7 @@ def pars(): print(f"Error: file '{args.file}' is empty") sys.exit(1) - return args.quantity, sites, args.rps, args.timeout, args.max_concurrent, args.log_level, args.console_log + return args.quantity, sites, args.rps, args.timeout, args.max_concurrent, args.log_level, args.no_console_log, args.name_folder_log async def check_one(site, timeout): scan_timeout = max(2, timeout - 2) @@ -145,10 +151,10 @@ async def check_one(site, timeout): return result -def setup_logger(flag_stream_handler, input_level_logging): - log_dir = Path("logs") +def setup_logger(flag_stream_handler, input_level_logging, name_folder_log): + log_dir = Path(name_folder_log) log_dir.mkdir(exist_ok=True) - file_log = log_dir / f"LOG: {datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.json" + file_log = log_dir / f"LOG_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S-%f')}.json" logger = logging.getLogger(__name__) @@ -158,17 +164,17 @@ def setup_logger(flag_stream_handler, input_level_logging): - if flag_stream_handler: + if not flag_stream_handler: console_handler = logging.StreamHandler() console_handler.setLevel(input_level_logging) console_handler.setFormatter(formatter) logger.addHandler(console_handler) - file_handler = logging.FileHandler(file_log, encoding='utf-8') - file_handler.setLevel(input_level_logging) - file_handler.setFormatter(formatter) - logger.addHandler(file_handler) + # file_handler = logging.FileHandler(file_log, encoding='utf-8') + # file_handler.setLevel(input_level_logging) + # file_handler.setFormatter(formatter) + # logger.addHandler(file_handler) return logger, file_log @@ -248,7 +254,7 @@ def log(quantity, rps, timeout, max_concurrent, results, logger, file_log): async def main(): - quantity, sites, rps, timeout, max_concurrent, log_level, console_log = pars() + quantity, sites, rps, timeout, max_concurrent, log_level, console_log, name_folder_log = pars() semaphore = asyncio.Semaphore(max_concurrent) delay = 1.0 / rps @@ -267,7 +273,7 @@ async def task_wrapper(certain_site=site): results = await asyncio.gather(*tasks, return_exceptions=True) - logger, file_log = setup_logger(console_log, log_level) + logger, file_log = setup_logger(console_log, log_level, name_folder_log) log(quantity, rps, timeout, max_concurrent, results, logger, file_log) diff --git a/scripts/gen_traf_tcp-udp/setup.sh b/scripts/gen_traf_tcp-udp/setup.sh new file mode 100755 index 0000000..2062ad5 --- /dev/null +++ b/scripts/gen_traf_tcp-udp/setup.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e +sudo apt update +sudo apt install -y nmap +sudo apt install -y python3-pip +pip3 install python-nmap diff --git a/scripts/gen_traf_tcp-udp/tests.py b/scripts/gen_traf_tcp-udp/tests.py new file mode 100644 index 0000000..da790f3 --- /dev/null +++ b/scripts/gen_traf_tcp-udp/tests.py @@ -0,0 +1,139 @@ +import unittest +import asyncio +import json +import time +import tempfile +from pathlib import Path +from unittest.mock import patch +from generate_rand_traf import main + +class TestIntegration(unittest.TestCase): + + def setUp(self): + self.temp_dir = tempfile.TemporaryDirectory() + self.sites_file = Path(self.temp_dir.name) / "sites.txt" + + self.logs_dir = Path(f"logs/test") + self.logs_dir.mkdir(exist_ok=True) + + def tearDown(self): + self.temp_dir.cleanup() + + def create_sites_file(self, sites): + with open(self.sites_file, 'w') as f: + for site in sites: + f.write(f"{site}\n") + + def get_last_log_file(self): + log_files = list(self.logs_dir.glob("LOG_*.json")) + if not log_files: + return None + return max(log_files, key=lambda p: p.stat().st_mtime) + + def run_main(self, args): + with patch('sys.argv', ['script.py'] + args + ['-f', str(self.sites_file)] + ['-n', "logs/test"]): + asyncio.run(main()) + time.sleep(0.5) + return self.get_last_log_file() + + + + + def test_good_sites(self): + good_sites = [ + "cloudflare.com", + "amazon.com", + "4chan.org", + "www.reddit.com", + "wikipedia.org", + "youtube.com", + "github.com", + "medium.com", + "thepiratebay.org" + ] + + self.create_sites_file(good_sites) + log_file = self.run_main(['-q', '20', '-t', '90', '-r', '50']) + self.assertIsNotNone(log_file) + + with open(log_file, 'r') as f: + data = json.load(f) + + self.assertEqual(len(data['results']), 20) + self.assertEqual(data['statistics']['total'], 20) + self.assertTrue(data['statistics']['success'] > 12) #this condition is enough for us to confirm the success of the test. Packets can be ignored with a large number of simultaneous scanners. + self.assertEqual(data['statistics']['timeout'], 0) + self.assertEqual(data['statistics']['error'], 0) + + for result in data['results']: + if result['status'] == "success": + self.assertIsNotNone(result['ip']) + + + def test_bad_sites(self): + bad_sites = [ + "this-site-does-not-exist-12345.xyz", + "nonexistent.domain.test", + "project.local", + "my.project.local", + "test-project.local", + "nonexistent.invalid.test", + "nonexistent.domain.invalid", + "invalid.invalid.invalid", + ] + self.create_sites_file(bad_sites) + + log_file = self.run_main(['-q', '20',]) + self.assertIsNotNone(log_file) + + with open(log_file, 'r') as f: + data = json.load(f) + + self.assertEqual(len(data['results']), 20) + + self.assertEqual(data['statistics']['total'], 20) + self.assertEqual(data['statistics']['success'], 0) + self.assertEqual(data['statistics']['no_ports_count'] + data['statistics']['timeout'] + data['statistics']['error'], 20) + + for result in data['results']: + self.assertIsNone(result['ip']) + self.assertIn(result['status'], ['timeout', 'error', 'no_open_ports']) + + def test_mixed_sites(self): + mixed_sites = [ + "google.com", + "github.com", + "www.yahoo.com", + "www.cnn.com", + "www.ebay.com", + "this-site-does-not-exist-12345.xyz", + "nonexistent.domain.test", + "project.local", + "my.project.local" + ] + + self.create_sites_file(mixed_sites) + + log_file = self.run_main(['-q', '20', '-t', '80', '-r', '41']) + self.assertIsNotNone(log_file) + + with open(log_file, 'r') as f: + data = json.load(f) + + self.assertEqual(data['statistics']['total'], 20) + + self.assertGreater(data['statistics']['success'], 0) + self.assertGreater(data['statistics']['timeout'] + data['statistics']['error'] + data['statistics']['no_ports_count'], 0) + + self.assertEqual(data['statistics']['success'] + data['statistics']['timeout'] + + data['statistics']['error'] + data['statistics']['no_ports_count'], data['statistics']['total']) + + for result in data['results']: + if result['status'] == "success": + self.assertIsNotNone(result['ip']) + else: + self.assertIsNone(result['ip']) + + +if __name__ == '__main__': + unittest.main(verbosity=1) \ No newline at end of file From 03e54d39b386403e41317f88fa07d5e8166a6b99 Mon Sep 17 00:00:00 2001 From: LapshinAE0 Date: Fri, 27 Feb 2026 13:27:15 +0300 Subject: [PATCH 6/9] remove sleep --- scripts/gen_traf_tcp-udp/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/gen_traf_tcp-udp/tests.py b/scripts/gen_traf_tcp-udp/tests.py index da790f3..fb46346 100644 --- a/scripts/gen_traf_tcp-udp/tests.py +++ b/scripts/gen_traf_tcp-udp/tests.py @@ -33,7 +33,6 @@ def get_last_log_file(self): def run_main(self, args): with patch('sys.argv', ['script.py'] + args + ['-f', str(self.sites_file)] + ['-n', "logs/test"]): asyncio.run(main()) - time.sleep(0.5) return self.get_last_log_file() From 82bf4f2ff9d8ca3dfbb024296bbc1294e71a6e51 Mon Sep 17 00:00:00 2001 From: LapshinAE0 Date: Sat, 28 Feb 2026 16:46:00 +0300 Subject: [PATCH 7/9] add in logs code http and label 80 and 443 port --- .../gen_traf_tcp-udp/generate_rand_traf.py | 35 +++++++++++++------ scripts/gen_traf_tcp-udp/tests.py | 11 +++--- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/scripts/gen_traf_tcp-udp/generate_rand_traf.py b/scripts/gen_traf_tcp-udp/generate_rand_traf.py index 2839619..db22580 100644 --- a/scripts/gen_traf_tcp-udp/generate_rand_traf.py +++ b/scripts/gen_traf_tcp-udp/generate_rand_traf.py @@ -7,7 +7,10 @@ import logging from pathlib import Path import nmap - +import requests +from bs4 import BeautifulSoup +import urllib3 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def pars(): @@ -126,10 +129,24 @@ async def check_one(site, timeout): if state == 'open': if proto == 'tcp': + result['tcp_ports'][port] = { 'state': state, 'service': service } + + if port == 80 or port == 443: + protocol = 'https' if port == 443 else 'http' + url = f"{protocol}://{site}:{port}" + response = requests.get(url, timeout=5, verify=False) + status_code = response.status_code + soup = BeautifulSoup(response.text, 'html.parser') + title = soup.find('title').text if soup.find('title') else None + + result['tcp_ports'][port]['http_status'] = status_code + result['tcp_ports'][port]['title'] = title + + elif proto == 'udp': result['udp_ports'][port] = { 'state': state, @@ -147,6 +164,9 @@ async def check_one(site, timeout): except Exception as e: result['status'] = 'error' result['error'] = str(e) + + + return result @@ -171,20 +191,15 @@ def setup_logger(flag_stream_handler, input_level_logging, name_folder_log): logger.addHandler(console_handler) - # file_handler = logging.FileHandler(file_log, encoding='utf-8') - # file_handler.setLevel(input_level_logging) - # file_handler.setFormatter(formatter) - # logger.addHandler(file_handler) - return logger, file_log def log(quantity, rps, timeout, max_concurrent, results, logger, file_log): - logger.info(f" Запросов: {quantity}") + logger.info(f" Requests: {quantity}") logger.info(f" RPS: {rps}") - logger.info(f" Таймаут: {timeout}с") - logger.info(f" Конкурентность: {max_concurrent}") - logger.debug(f" Файл результатов: {file_log}") + logger.info(f" Timeout: {timeout}с") + logger.info(f" Max concurrent processes: {max_concurrent}") + logger.debug(f" The results file: {file_log}") no_ports_count = success_count = timeout_count = error_count = 0 diff --git a/scripts/gen_traf_tcp-udp/tests.py b/scripts/gen_traf_tcp-udp/tests.py index fb46346..883cf2d 100644 --- a/scripts/gen_traf_tcp-udp/tests.py +++ b/scripts/gen_traf_tcp-udp/tests.py @@ -6,10 +6,13 @@ from pathlib import Path from unittest.mock import patch from generate_rand_traf import main +import urllib3 class TestIntegration(unittest.TestCase): def setUp(self): + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + self.temp_dir = tempfile.TemporaryDirectory() self.sites_file = Path(self.temp_dir.name) / "sites.txt" @@ -31,7 +34,7 @@ def get_last_log_file(self): return max(log_files, key=lambda p: p.stat().st_mtime) def run_main(self, args): - with patch('sys.argv', ['script.py'] + args + ['-f', str(self.sites_file)] + ['-n', "logs/test"]): + with patch('sys.argv', ['script.py'] + args + ['-f', str(self.sites_file)] + ['-n', "logs/test"] + ['-ncl'] + ['-l' "CRITICAL"]): asyncio.run(main()) return self.get_last_log_file() @@ -41,14 +44,10 @@ def run_main(self, args): def test_good_sites(self): good_sites = [ "cloudflare.com", - "amazon.com", "4chan.org", "www.reddit.com", "wikipedia.org", - "youtube.com", "github.com", - "medium.com", - "thepiratebay.org" ] self.create_sites_file(good_sites) @@ -61,8 +60,6 @@ def test_good_sites(self): self.assertEqual(len(data['results']), 20) self.assertEqual(data['statistics']['total'], 20) self.assertTrue(data['statistics']['success'] > 12) #this condition is enough for us to confirm the success of the test. Packets can be ignored with a large number of simultaneous scanners. - self.assertEqual(data['statistics']['timeout'], 0) - self.assertEqual(data['statistics']['error'], 0) for result in data['results']: if result['status'] == "success": From 9aa6cfffd48df5d1a8896e4efd7f5e089c1e4de8 Mon Sep 17 00:00:00 2001 From: LapshinAE0 Date: Thu, 5 Mar 2026 19:50:10 +0300 Subject: [PATCH 8/9] init af_xdp and dns parser --- worker/Makefile.test_af_xdp | 22 ++++++ worker/include/dpdk_filter/af_xdp_port.h | 13 ++++ worker/include/dpdk_filter/dns_parser.h | 8 +++ worker/src/dpdk_filter/af_xdp_port.c | 92 ++++++++++++++++++++++++ worker/src/dpdk_filter/dns_parser.c | 79 ++++++++++++++++++++ worker/src/dpdk_filter/test.c | 53 ++++++++++++++ 6 files changed, 267 insertions(+) create mode 100644 worker/Makefile.test_af_xdp create mode 100644 worker/include/dpdk_filter/af_xdp_port.h create mode 100644 worker/include/dpdk_filter/dns_parser.h create mode 100644 worker/src/dpdk_filter/af_xdp_port.c create mode 100644 worker/src/dpdk_filter/dns_parser.c create mode 100644 worker/src/dpdk_filter/test.c diff --git a/worker/Makefile.test_af_xdp b/worker/Makefile.test_af_xdp new file mode 100644 index 0000000..1aba2b0 --- /dev/null +++ b/worker/Makefile.test_af_xdp @@ -0,0 +1,22 @@ +CC = gcc +CFLAGS = -Iinclude -O2 -msse4.2 -mpclmul -maes +LDFLAGS = -lrte_eal -lrte_ethdev -lrte_mempool -lrte_mbuf -lrte_bus_vdev -lpthread -lnuma -ldl + +SRCS = src/dpdk_filter/test.c src/dpdk_filter/af_xdp_port.c +TARGET = test + +all: $(TARGET) + +$(TARGET): $(SRCS) + $(CC) $(CFLAGS) -o $(TARGET) $(SRCS) $(LDFLAGS) + +clean: + rm -f $(TARGET) + +run: $(TARGET) + sudo ./$(TARGET) eth0 + +run-wlan: $(TARGET) + sudo ./$(TARGET) wlan0 + +.PHONY: all clean run run-wlan \ No newline at end of file diff --git a/worker/include/dpdk_filter/af_xdp_port.h b/worker/include/dpdk_filter/af_xdp_port.h new file mode 100644 index 0000000..d933c6a --- /dev/null +++ b/worker/include/dpdk_filter/af_xdp_port.h @@ -0,0 +1,13 @@ +#ifndef AF_XDP_PORT_H +#define AF_XDP_PORT_H + +#include +#include + +int af_xdp_port_init(const char *iface_name, uint16_t *port_id, struct rte_mempool *mbuf_pool); + +int af_xdp_port_start(uint16_t port_id); + +void af_xdp_port_close(uint16_t port_id); + +#endif \ No newline at end of file diff --git a/worker/include/dpdk_filter/dns_parser.h b/worker/include/dpdk_filter/dns_parser.h new file mode 100644 index 0000000..ce1eb49 --- /dev/null +++ b/worker/include/dpdk_filter/dns_parser.h @@ -0,0 +1,8 @@ +#ifndef DNS_PARSER_H +#define DNS_PARSER_H + +#include + +void extract_dns_domain(struct rte_mbuf *pkt, char *domain, int max_len); + +#endif \ No newline at end of file diff --git a/worker/src/dpdk_filter/af_xdp_port.c b/worker/src/dpdk_filter/af_xdp_port.c new file mode 100644 index 0000000..f98fe59 --- /dev/null +++ b/worker/src/dpdk_filter/af_xdp_port.c @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "../../include/dpdk_filter/af_xdp_port.h" + +#define RX_RING_SIZE 1024 +#define TX_RING_SIZE 1024 + +int af_xdp_port_init(const char *iface_name, uint16_t *port_id, struct rte_mempool *mbuf_pool) +{ + int ret; + char vdev_args[256]; + struct rte_eth_conf port_conf = {0}; + + printf("[AF_XDP] Initializing on %s\n", iface_name); + + snprintf(vdev_args, sizeof(vdev_args), "iface=%s,start_queue=0,queue_count=1", iface_name); + ret = rte_vdev_init("net_af_xdp", vdev_args); + + if (ret < 0) { + printf("[AF_XDP ERROR] Failed to create vdev: %s\n", strerror(-ret)); + return ret; + } + + *port_id = rte_eth_dev_count_avail() - 1; + + ret = rte_eth_dev_configure(*port_id, 1, 1, &port_conf); + if (ret < 0) { + printf("[AF_XDP ERROR] Failed to configure port: %s\n", strerror(-ret)); + rte_vdev_uninit("net_af_xdp"); + return ret; + } + + ret = rte_eth_rx_queue_setup(*port_id, 0, RX_RING_SIZE, rte_eth_dev_socket_id(*port_id), NULL, mbuf_pool); + if (ret < 0) { + printf("[AF_XDP ERROR] Failed to setup RX queue: %s\n", strerror(-ret)); + return ret; + } + + ret = rte_eth_tx_queue_setup(*port_id, 0, TX_RING_SIZE, rte_eth_dev_socket_id(*port_id), NULL); + + if (ret < 0) { + printf("[AF_XDP ERROR] Failed to setup TX queue: %s\n", strerror(-ret)); + return ret; + } + + printf("[AF_XDP] Port %u initialized\n", *port_id); + return 0; +} + + + + +int af_xdp_port_start(uint16_t port_id) +{ + int ret; + + printf("[AF_XDP] Starting port %u\n", port_id); + + ret = rte_eth_dev_start(port_id); + if (ret < 0) { + printf("[AF_XDP ERROR] Failed to start: %s\n", strerror(-ret)); + return ret; + } + + rte_eth_promiscuous_enable(port_id); + + printf("[AF_XDP] Port %u started\n", port_id); + return 0; +} + + + + +void af_xdp_port_close(uint16_t port_id) +{ + printf("[AF_XDP] Closing port %u\n", port_id); + + rte_eth_dev_stop(port_id); + rte_eth_dev_close(port_id); + rte_vdev_uninit("net_af_xdp"); + + printf("[AF_XDP] Port %u closed\n", port_id); +} \ No newline at end of file diff --git a/worker/src/dpdk_filter/dns_parser.c b/worker/src/dpdk_filter/dns_parser.c new file mode 100644 index 0000000..c6155b2 --- /dev/null +++ b/worker/src/dpdk_filter/dns_parser.c @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "../../include/dpdk_filter/dns_parser.h" + +static const char *blocked_domains[] = { // пока как заглушка вместо политики воркера + "facebook.com", + "youtube.com", + "instagram.com", +}; + + +static int is_domain_blocked(const char *domain) { + for (int i = 0; blocked_domains[i] != NULL; i++) { + if (strstr(domain, blocked_domains[i]) != NULL) { + return 1; + } + } + return 0; +} + +void extract_dns_domain(struct rte_mbuf *pkt, char *domain, int max_len) { + struct rte_ether_hdr *eth_hdr; + struct rte_ipv4_hdr *ip_hdr; + struct rte_udp_hdr *udp_hdr; + int dns_hdr = 12; + uint8_t *dns_data; + + eth_hdr = rte_pktmbuf_mtod(pkt, struct rte_ether_hdr *); + + if (rte_be_to_cpu_16(eth_hdr->ether_type) != RTE_ETHER_TYPE_IPV4) { + domain[0] = '\0'; + return; + } + + ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1); + + if (ip_hdr->next_proto_id != IPPROTO_UDP) { + domain[0] = '\0'; + return; + } + + udp_hdr = (struct rte_udp_hdr *)((uint8_t *)ip_hdr + ((ip_hdr->version_ihl) & 0x0f) * 4); + + if (rte_be_to_cpu_16(udp_hdr->dst_port) != 53) { + domain[0] = '\0'; + return; + } + + dns_data = (uint8_t *)(udp_hdr + 1); + uint8_t *qname = dns_data + dns_hdr; + int pos = 0; + + while (*qname != 0 && pos < max_len - 1) { + uint8_t label_len = *qname; + qname++; + + for (int i = 0; i < label_len && pos < max_len - 1; i++) { + domain[pos++] = *qname++; + } + + if (*qname != 0 && pos < max_len - 1) { + domain[pos++] = '.'; + } + } + + domain[pos] = '\0'; + + if (is_domain_blocked(domain)) { + printf("\n block: %s\n", domain); + } else { + printf("\n allow: %s\n", domain); + } +} \ No newline at end of file diff --git a/worker/src/dpdk_filter/test.c b/worker/src/dpdk_filter/test.c new file mode 100644 index 0000000..7b1b544 --- /dev/null +++ b/worker/src/dpdk_filter/test.c @@ -0,0 +1,53 @@ +#include + +#include "../../include/dpdk_filter/af_xdp_port.h" + +int main(int argc, char **argv) +{ + struct rte_mbuf *pkts[32]; + uint64_t total_pkts = 0; + uint16_t port_id; + struct rte_mempool *mbuf_pool; + + int ret = rte_eal_init(argc, argv); + if (ret < 0) { + printf("ERROR: EAL init failed\n"); + return -1; + } + + mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", 8192, 250, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id()); + if (!mbuf_pool) { + printf("ERROR: Failed to create mbuf pool\n"); + return -1; + } + + if (af_xdp_port_init("eth0", &port_id, mbuf_pool) < 0) { + printf("ERROR: Failed to init port\n"); + return -1; + } + + if (af_xdp_port_start(port_id) < 0) { + af_xdp_port_close(port_id); + return -1; + } + + printf("AF_XDP port %u is running. Press Ctrl+C to stop.\n", port_id); + + while (1) { + uint16_t nb_rx = rte_eth_rx_burst(port_id, 0, pkts, 32); + if (nb_rx > 0) { + total_pkts += nb_rx; + printf("\rPackets received: %u", total_pkts); + fflush(stdout); + // parse_packet(pkts[0]); + for (int i = 0; i < nb_rx; i++) { + rte_pktmbuf_free(pkts[i]); + } + } + usleep(5); + } + + printf("total pkts: %u", total_pkts); + af_xdp_port_close(port_id); + return 0; +} \ No newline at end of file From 84f6979fb95c3db79f23e7e8a405fbd9b446c41d Mon Sep 17 00:00:00 2001 From: LapshinAE0 Date: Thu, 5 Mar 2026 20:49:45 +0300 Subject: [PATCH 9/9] deleting files from another-86 branch --- scripts/gen_traf_tcp-udp/README.md | 46 --- .../gen_traf_tcp-udp/generate_rand_traf.py | 296 ------------------ scripts/gen_traf_tcp-udp/generate_traf.sh | 23 -- scripts/gen_traf_tcp-udp/setup.sh | 7 - scripts/gen_traf_tcp-udp/sites.txt | 10 - scripts/gen_traf_tcp-udp/tests.py | 135 -------- 6 files changed, 517 deletions(-) delete mode 100644 scripts/gen_traf_tcp-udp/README.md delete mode 100644 scripts/gen_traf_tcp-udp/generate_rand_traf.py delete mode 100755 scripts/gen_traf_tcp-udp/generate_traf.sh delete mode 100755 scripts/gen_traf_tcp-udp/setup.sh delete mode 100644 scripts/gen_traf_tcp-udp/sites.txt delete mode 100644 scripts/gen_traf_tcp-udp/tests.py diff --git a/scripts/gen_traf_tcp-udp/README.md b/scripts/gen_traf_tcp-udp/README.md deleted file mode 100644 index aa80f85..0000000 --- a/scripts/gen_traf_tcp-udp/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Traffic Generator - -Асинхронный генератор трафика для тестирования сетевых подключений с использованием `nmap`. Запускает заданное количество запросов к случайным сайтам из списка с контролем RPS (requests per second), таймаутами и ограничением параллельных задач. Результаты сканирования портов (TCP/UDP) сохраняются в JSON-лог (по умолчанию logs/LOG: YYYY-MM-DD_HH-MM-SS-ffffff.json). - -## Требования - -- Python 3.7+ -- Nmap (устанавливается автоматически) -- Зависимости Python: `python-nmap` - -## Установка - -```bash -# Запустите скрипт установки (установит nmap и python-зависимости) -chmod +x setup.sh -./setup.sh -``` - - -## Использование -```bash -sudo python3 generate_rand_traf.py [параметры] -``` - -Параметры командной строки -Короткая Полная По умолчанию Описание --q --quantity 10 Количество запросов --r --rps 15 Желаемое количество запросов в секунду --t --timeout 20 Таймаут на один запрос (секунды) --f --file sites.txt Файл со списком сайтов --m --max_concurrent 50 Максимальное число одновременно выполняемых задач --l --log_level INFO Уровень логирования (DEBUG, INFO, WARNING, ERROR, CRITICAL) --ncl --no_console_log (флаг) Отключить логирование в консоль --n --name_folder_log logs Имя папки для логов - -## Тестирование -Проект включает интеграционные тесты для проверки основной функциональности: - -```bash -sudo python3 test.py -``` -Тесты проверяют: - Работу с заведомо существующими сайтами - Обработку несуществующих доменов (используются зарезервированные домены .test, .invalid, .local) - Смешанные сценарии с разными типами сайтов - Корректность подсчета статистики \ No newline at end of file diff --git a/scripts/gen_traf_tcp-udp/generate_rand_traf.py b/scripts/gen_traf_tcp-udp/generate_rand_traf.py deleted file mode 100644 index db22580..0000000 --- a/scripts/gen_traf_tcp-udp/generate_rand_traf.py +++ /dev/null @@ -1,296 +0,0 @@ -import asyncio -import random -import sys -import argparse -from datetime import datetime -import json -import logging -from pathlib import Path -import nmap -import requests -from bs4 import BeautifulSoup -import urllib3 -urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - - -def pars(): - parser = argparse.ArgumentParser( - description="Traffic generator with RPS control and timeouts", - formatter_class=argparse.RawTextHelpFormatter - ) - parser.add_argument( - "--quantity", "-q", - type=int, - default=10, - help="Number of requests (10 by default)" - ) - parser.add_argument( - "--rps", "-r", - type=int, - default=15, - help="The desired number of requests per second (15 by default)" - ) - parser.add_argument( - "--timeout", "-t", - type=int, - default=20, - help="Timeout per request in seconds (20 by default)" - ) - parser.add_argument( - "--file", "-f", - type=str, - default="sites.txt", - help="A file with a list of sites (by default sites.txt )" - ) - parser.add_argument( - "--max_concurrent", "-m", - type=int, - default=50, - help="Maximum number of simultaneous tasks (50 by default)" - ) - parser.add_argument( - "--log_level", "-l", - type=str, - default="INFO", - choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], - help="Logging level: DEBUG, INFO, WARNING, ERROR, CRITICAL (INFO by default)" - ) - parser.add_argument( - "--no_console_log", "-ncl", - action="store_true", - help="Disable console logging (by default console logging is enabled)" - ) - parser.add_argument( - "--name_folder_log", "-n", - type=str, - default="logs", - help="A name of folder logs(by default logs)" - ) - - args = parser.parse_args() - - if args.quantity <= 0: - print("Error: the number of requests must be a positive number, using the default value of 10") - args.quantity = 10 - if args.rps <= 0: - print("Error: RPS must be a positive number, using the default value of 15") - args.rps = 15 - if args.timeout <= 0: - print("Error: the timeout must be a positive number, using the default value of 20 - the optimal time for analyzing a compound is at standard values.") - args.timeout = 20 - if args.max_concurrent <= 0: - print("Error: the number of tasks being completed at the same time must be a positive number, using the default value of 50") - args.max_concurrent = 50 - - try: - with open(args.file, 'r') as f: - sites = [line.strip() for line in f if line.strip()] - except FileNotFoundError: - print(f"Error: file '{args.file}' not found") - sys.exit(1) - - if not sites: - print(f"Error: file '{args.file}' is empty") - sys.exit(1) - - return args.quantity, sites, args.rps, args.timeout, args.max_concurrent, args.log_level, args.no_console_log, args.name_folder_log - -async def check_one(site, timeout): - scan_timeout = max(2, timeout - 2) - - result = { - 'site': site, - 'tcp_ports': {}, - 'udp_ports': {}, - 'status': 'unknown', - 'ip': None - } - - try: - nm = nmap.PortScanner() - - await asyncio.wait_for( - asyncio.to_thread( - nm.scan, - site, - '53,80,443,123,161', - f'-sS -sU -T4 --host-timeout {scan_timeout}s' - ), - timeout - ) - - if nm.all_hosts(): - result['ip'] = nm.all_hosts()[0] - - for proto in nm[result['ip']].all_protocols(): - for port in nm[result['ip']][proto].keys(): - state = nm[result['ip']][proto][port]['state'] - service = nm[result['ip']][proto][port].get('name', 'unknown') - - if state == 'open': - if proto == 'tcp': - - result['tcp_ports'][port] = { - 'state': state, - 'service': service - } - - if port == 80 or port == 443: - protocol = 'https' if port == 443 else 'http' - url = f"{protocol}://{site}:{port}" - response = requests.get(url, timeout=5, verify=False) - status_code = response.status_code - soup = BeautifulSoup(response.text, 'html.parser') - title = soup.find('title').text if soup.find('title') else None - - result['tcp_ports'][port]['http_status'] = status_code - result['tcp_ports'][port]['title'] = title - - - elif proto == 'udp': - result['udp_ports'][port] = { - 'state': state, - 'service': service - } - - if result['tcp_ports'] or result['udp_ports']: - result['status'] = 'success' - else: - result['status'] = 'no_open_ports' - - except asyncio.TimeoutError: - result['status'] = 'timeout' - - except Exception as e: - result['status'] = 'error' - result['error'] = str(e) - - - - - return result - - -def setup_logger(flag_stream_handler, input_level_logging, name_folder_log): - log_dir = Path(name_folder_log) - log_dir.mkdir(exist_ok=True) - file_log = log_dir / f"LOG_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S-%f')}.json" - - - logger = logging.getLogger(__name__) - formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') - - logger.setLevel(input_level_logging) - - - - if not flag_stream_handler: - console_handler = logging.StreamHandler() - console_handler.setLevel(input_level_logging) - console_handler.setFormatter(formatter) - logger.addHandler(console_handler) - - - return logger, file_log - -def log(quantity, rps, timeout, max_concurrent, results, logger, file_log): - - logger.info(f" Requests: {quantity}") - logger.info(f" RPS: {rps}") - logger.info(f" Timeout: {timeout}с") - logger.info(f" Max concurrent processes: {max_concurrent}") - logger.debug(f" The results file: {file_log}") - - no_ports_count = success_count = timeout_count = error_count = 0 - - log_data = { - "parameters": { - "quantity": quantity, - "rps": rps, - "timeout": timeout, - "max_concurrent": max_concurrent - }, - "results": [] - } - - for res in results: - if isinstance(res, Exception): - log_data["results"].append({ - "site": "unknown", - "status": "exception", - "details": str(res) - }) - logger.exception(f"Request - EXCEPTION: {res}") - - else: - site_data = res - - log_entry = { - "site": site_data['site'], - "ip": site_data['ip'], - "status": site_data['status'], - "tcp_ports": site_data['tcp_ports'], - "udp_ports": site_data['udp_ports'] - } - - if 'error' in site_data: - log_entry["error"] = site_data['error'] - - log_data["results"].append(log_entry) - - if site_data['status'] == 'success': - success_count += 1 - logger.debug(f"Request to {site_data['site']} - SUCCESS") - elif site_data['status'] == 'timeout': - timeout_count += 1 - logger.error(f"Request to {site_data['site']} - TIMEOUT") - elif site_data['status'] == 'no_open_ports': - no_ports_count += 1 - logger.warning(f"Request to {site_data['site']} - NO OPEN PORTS") - else: - error_count += 1 - logger.error(f"Request to {site_data['site']} - ERROR") - - - log_data["statistics"] = { - "no_ports_count" : no_ports_count, - "success": success_count, - "timeout": timeout_count, - "error": error_count, - "total": quantity - } - - try: - with open(file_log, 'w', encoding='utf-8') as file: - json.dump(log_data, file, indent=2, ensure_ascii=False) - print(f"\nLogs are saved to a file: {file_log}") - except Exception as e: - print(f"\nError saving logs: {e}") - - -async def main(): - quantity, sites, rps, timeout, max_concurrent, log_level, console_log, name_folder_log = pars() - - semaphore = asyncio.Semaphore(max_concurrent) - delay = 1.0 / rps - - tasks = [] - for _ in range(quantity): - site = random.choice(sites) - async def task_wrapper(certain_site=site): - async with semaphore: - return await check_one(certain_site, timeout) - - task = asyncio.create_task(task_wrapper()) - tasks.append(task) - - await asyncio.sleep(delay) - - results = await asyncio.gather(*tasks, return_exceptions=True) - - logger, file_log = setup_logger(console_log, log_level, name_folder_log) - log(quantity, rps, timeout, max_concurrent, results, logger, file_log) - - -if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file diff --git a/scripts/gen_traf_tcp-udp/generate_traf.sh b/scripts/gen_traf_tcp-udp/generate_traf.sh deleted file mode 100755 index 785691a..0000000 --- a/scripts/gen_traf_tcp-udp/generate_traf.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -count=$1 -site=$2 - -if [ -z "$count" ] || [ -z "$site" ]; then - echo "Error: the argument was not passed!" - echo "Usage: ./generate_traf.sh " - echo "Example: ./generate_traf.sh 8 google.com" - exit 1 -fi - -if ! [[ "$count" =~ ^[1-9][0-9]*$ ]]; then - echo "Error: '$count' is not a positive number!" - echo "Usage: ./generate_traf.sh " - echo "Example: ./generate_traf.sh 8 google.com" - exit 1 -fi - -for (( i=1; i<=$count;i++ )); do - nc -zv $site 80 - nc -uzv $site 80 -done diff --git a/scripts/gen_traf_tcp-udp/setup.sh b/scripts/gen_traf_tcp-udp/setup.sh deleted file mode 100755 index 2062ad5..0000000 --- a/scripts/gen_traf_tcp-udp/setup.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -set -e -sudo apt update -sudo apt install -y nmap -sudo apt install -y python3-pip -pip3 install python-nmap diff --git a/scripts/gen_traf_tcp-udp/sites.txt b/scripts/gen_traf_tcp-udp/sites.txt deleted file mode 100644 index dd5b93d..0000000 --- a/scripts/gen_traf_tcp-udp/sites.txt +++ /dev/null @@ -1,10 +0,0 @@ -4chan.org -www.reddit.com -www.yahoo.com -www.cnn.com -www.ebay.com -wikipedia.org -youtube.com -github.com -medium.com -thepiratebay.org \ No newline at end of file diff --git a/scripts/gen_traf_tcp-udp/tests.py b/scripts/gen_traf_tcp-udp/tests.py deleted file mode 100644 index 883cf2d..0000000 --- a/scripts/gen_traf_tcp-udp/tests.py +++ /dev/null @@ -1,135 +0,0 @@ -import unittest -import asyncio -import json -import time -import tempfile -from pathlib import Path -from unittest.mock import patch -from generate_rand_traf import main -import urllib3 - -class TestIntegration(unittest.TestCase): - - def setUp(self): - urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - - self.temp_dir = tempfile.TemporaryDirectory() - self.sites_file = Path(self.temp_dir.name) / "sites.txt" - - self.logs_dir = Path(f"logs/test") - self.logs_dir.mkdir(exist_ok=True) - - def tearDown(self): - self.temp_dir.cleanup() - - def create_sites_file(self, sites): - with open(self.sites_file, 'w') as f: - for site in sites: - f.write(f"{site}\n") - - def get_last_log_file(self): - log_files = list(self.logs_dir.glob("LOG_*.json")) - if not log_files: - return None - return max(log_files, key=lambda p: p.stat().st_mtime) - - def run_main(self, args): - with patch('sys.argv', ['script.py'] + args + ['-f', str(self.sites_file)] + ['-n', "logs/test"] + ['-ncl'] + ['-l' "CRITICAL"]): - asyncio.run(main()) - return self.get_last_log_file() - - - - - def test_good_sites(self): - good_sites = [ - "cloudflare.com", - "4chan.org", - "www.reddit.com", - "wikipedia.org", - "github.com", - ] - - self.create_sites_file(good_sites) - log_file = self.run_main(['-q', '20', '-t', '90', '-r', '50']) - self.assertIsNotNone(log_file) - - with open(log_file, 'r') as f: - data = json.load(f) - - self.assertEqual(len(data['results']), 20) - self.assertEqual(data['statistics']['total'], 20) - self.assertTrue(data['statistics']['success'] > 12) #this condition is enough for us to confirm the success of the test. Packets can be ignored with a large number of simultaneous scanners. - - for result in data['results']: - if result['status'] == "success": - self.assertIsNotNone(result['ip']) - - - def test_bad_sites(self): - bad_sites = [ - "this-site-does-not-exist-12345.xyz", - "nonexistent.domain.test", - "project.local", - "my.project.local", - "test-project.local", - "nonexistent.invalid.test", - "nonexistent.domain.invalid", - "invalid.invalid.invalid", - ] - self.create_sites_file(bad_sites) - - log_file = self.run_main(['-q', '20',]) - self.assertIsNotNone(log_file) - - with open(log_file, 'r') as f: - data = json.load(f) - - self.assertEqual(len(data['results']), 20) - - self.assertEqual(data['statistics']['total'], 20) - self.assertEqual(data['statistics']['success'], 0) - self.assertEqual(data['statistics']['no_ports_count'] + data['statistics']['timeout'] + data['statistics']['error'], 20) - - for result in data['results']: - self.assertIsNone(result['ip']) - self.assertIn(result['status'], ['timeout', 'error', 'no_open_ports']) - - def test_mixed_sites(self): - mixed_sites = [ - "google.com", - "github.com", - "www.yahoo.com", - "www.cnn.com", - "www.ebay.com", - "this-site-does-not-exist-12345.xyz", - "nonexistent.domain.test", - "project.local", - "my.project.local" - ] - - self.create_sites_file(mixed_sites) - - log_file = self.run_main(['-q', '20', '-t', '80', '-r', '41']) - self.assertIsNotNone(log_file) - - with open(log_file, 'r') as f: - data = json.load(f) - - self.assertEqual(data['statistics']['total'], 20) - - self.assertGreater(data['statistics']['success'], 0) - self.assertGreater(data['statistics']['timeout'] + data['statistics']['error'] + data['statistics']['no_ports_count'], 0) - - self.assertEqual(data['statistics']['success'] + data['statistics']['timeout'] + - data['statistics']['error'] + data['statistics']['no_ports_count'], data['statistics']['total']) - - for result in data['results']: - if result['status'] == "success": - self.assertIsNotNone(result['ip']) - else: - self.assertIsNone(result['ip']) - - -if __name__ == '__main__': - unittest.main(verbosity=1) \ No newline at end of file