diff --git a/Makefile b/Makefile index d6c5dcc..0b05528 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ UNAME_S := $(shell uname -s) -CXX = g++-4.8 +CXX = g++ PROGS = ddosdetector CLEANFILES = $(PROGS) *.o -LDFLAGS = -lboost_system -lboost_thread -llog4cpp -lboost_program_options -lcurl +LDFLAGS = -lboost_system -lboost_thread -llog4cpp -lboost_program_options -lcurl -lpthread CPPFLAGS = -std=c++11 -Wall #-O2 CPPFLAGS += -I ./sys -I ./proto @@ -66,6 +66,8 @@ test: clean-test build-test run-test check: @./test/cppcheck/cppcheck -q -j12 --platform=unix64 -itest --std=c++11 --enable=all --inconclusive --suppressions-list=./test/cppcheck_suppress.cfg ./ +build: all + build-test: $(TESTS_BIN) @echo "====> Run tests <====" diff --git a/README.md b/README.md index 041a5ee..6de5450 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # DDoS attack detector # Ddosdetector System - a flexible tool for analyzing network traffic and automation of the process of protection against DDoS attacks. The system is based on the framework, [Luigi Rizzo](https://github.com/luigirizzo/netmap) [netmap](https://habrahabr.ru/post/183832/) and is designed to work with a large volume of traffic (10GB / sec and more) without loss of performance. -The system is written in C++ (Standard 11) using *STL* and *Boost (1.55)*. Writing and assembling was done on *Ubuntu 12.04.5 LTS* and compiler *g++4.8*. For static analysis and research style blunders used *cppcheck* version 1.73. +The system is written in C++ (Standard 11) using *STL* and *Boost (1.58)*. Writing and assembling was done on *Ubuntu 16.04 LTS* and compiler *g++-5*. For static analysis and research style blunders used *cppcheck* version 1.73. InfluxDB can be used for monitoring and collection of statistics. ![Grafana](docs/images/grafana.png) @@ -68,6 +68,7 @@ then the system should appear interface with netmap: ### Installing ddosdetector ### Build ddosdetector from source: ```bash +sudo apt-get install g++ libboost-all-dev libcurl4-gnutls-dev liblog4cpp5-dev git clone https://velizarx@bitbucket.org/velizarx/ddosdetector.git cd ./ddosdetector make @@ -86,12 +87,12 @@ depends: mdio,netmap,dca Run ddosdetector (in example interface eth4): ```bash cd -./ddosdetector -i eth4 -r ~/ddosdetector.rules -p /tmp/ddosd.sock -l ~/ddosdetector.log +./ddosdetector -i eth4 -r ~/ddosdetector.rules -s /tmp/ddosd.sock -l ~/ddosdetector.log ``` Where: * *-i eth4 (**parameter is required**)* - *eth4* interface system that gets mirrored traffic; -* *-r ~/ddosdetector.rules* - файл откуда будут загружены правила (этот параметр необязателен, по-умолчанию поиск файла производится по пути */etc/ddosdetector.rules*); -* *-p /tmp/ddosd.sock* - how to run the management server (in this case, the UNIX socket, the file */tmp/ddosd.sock*), may also be the path to the file or port number (then run TCP server to 127.0.0.1 and port specified) parameter is optional. By default, TCP server runs on port 9090; +* *-r ~/ddosdetector.rules* - файл откуда будут загружены правила (этот параметр необязателен, по-умолчанию поиск файла производится по пути */etc/ddosdetector/rules.conf*); +* *-s /tmp/ddosd.sock* - how to run the management server (in this case, the UNIX socket, the file */tmp/ddosd.sock*), may also be the path to the file or ip:port (then run TCP server to ip and port specified) parameter is optional. By default, TCP server runs on 127.0.0.1:9090; * *-l ~/ddosdetector.log* - the path to the log file, the default output in the stdout then you can connect to the system: @@ -102,16 +103,16 @@ socat - UNIX-CONNECT:/tmp/ddosd.sock ## Control ## ### Configuration files ### When you start the system tries to read the two configuration files: -* /etc/ddosdetector.conf - general system settings -* /etc/ddosdetector.rules - saved rules +* /etc/ddosdetector/conf.ini - general system settings +* /etc/ddosdetector/rules.conf - saved rules -File */etc/ddosdetector.conf* can contain the following settings (the value of the name): +File */etc/ddosdetector/conf.ini* can contain the following settings (the value of the name): ```ini [Main] Interface = eth0 -Rules = /etc/ddosdetector.rules +Rules = /etc/ddosdetector/rules.conf Log = /var/log/ddosdetector.log -Port = 9090 +Listen = 127.0.0.1:9090 [IndluxDB] Enable = yes @@ -134,7 +135,7 @@ To manage the system you want to connect to a running daemon. Depending on the s ```bash telnet 127.1 9090 ``` -where 9090 - this is the default start port or the specified port option +where 127.1 and 9090 - this is the default start ip:port or specified ip:port options #### UNIX socket server #### ```bash @@ -275,7 +276,7 @@ ICMP rules (num, rule, counter): 1: -d 92.53.96.141/32 --pps-th 100p --type =0 --code =0 : 0.00p/s (0.00b/s), 0 packets, 0 bytes ``` #### Reload rules from file #### -At startup, the system checks the configuration file previously saved rules (by default */etc/ddosdetector.rules*). If the reference system is already running, you can restart the rules from the file manually. Restart the rules of the file is performed either from the command control console **reload rules** any SIGHUP signal sent to the demon. For example rules file has the contents: +At startup, the system checks the configuration file previously saved rules (by default */etc/ddosdetector/rules.conf*). If the reference system is already running, you can restart the rules from the file manually. Restart the rules of the file is performed either from the command control console **reload rules** any SIGHUP signal sent to the demon. For example rules file has the contents: ```bash $ cat ~/ddosdetector.rules TCP -d 92.53.96.141/32 --pps-th 100p --seq =0 --pps-th-period 60 --action log:/tmp/test.log --next diff --git a/controld.cpp b/controld.cpp index 3260603..a467928 100644 --- a/controld.cpp +++ b/controld.cpp @@ -122,9 +122,9 @@ void ControlSession::parse() do_write(collect_->get_rules()); return; } - if(t_cmd[0] == "reload" && t_cmd[1] == "rules") // show rules + if(t_cmd[0] == "reload" && t_cmd[1] == "rules") // reload rules from file { - raise(1); + raise(SIGHUP); return; } // TODO: сделать monitor rules команду, с обновлением раз в секунду @@ -267,39 +267,60 @@ void ControlSession::parse() } -ControlServer::ControlServer(io_service& io_service, const std::string& port, +ControlServer::ControlServer(io_service& io_service, const std::string& listen, std::shared_ptr collect) - : is_unix_socket_(true), port_(port), collect_(collect) + : is_unix_socket_(true), listen_(listen), collect_(collect) { - short num_port = 0; - try - { - num_port = boost::lexical_cast(port_); + if (listen_.find(":") != std::string::npos) { is_unix_socket_ = false; } - catch(boost::bad_lexical_cast &) {} if(is_unix_socket_) { - local::stream_protocol::endpoint ep(port_); - unix_acceptor_ = std::make_shared(io_service, ep); - unix_socket_ = std::make_shared(io_service); - logger.info("Start controld unix socket server on " + port_); + try{ + local::stream_protocol::endpoint ep(listen_); + unix_acceptor_ = std::make_shared(io_service, ep); + unix_socket_ = std::make_shared(io_service); + logger.info("Start controld unix socket server on " + listen_); + } + catch(std::exception& e) + { + throw ControldException(e.what()); + } do_unix_accept(); } else { - ip::tcp::tcp::endpoint ep(ip::tcp::tcp::v4(), num_port); - tcp_acceptor_ = std::make_shared(io_service, ep); - tcp_socket_ = std::make_shared(io_service); - logger.info("Start controld tcp server on " + to_string(num_port)); + std::vector ip_port = tokenize(listen_, ":"); + short num_port = 0; + try{ + if (ip_port.size() != 2) + { + throw ControldException("Bad ip or port parametr. Use :."); + } + try + { + num_port = boost::lexical_cast(ip_port[1]); + } + catch(boost::bad_lexical_cast &) { + throw ControldException("Bad port number."); + } + ip::tcp::tcp::endpoint ep(ip::address::from_string(ip_port[0]), num_port); + tcp_acceptor_ = std::make_shared(io_service, ep); + tcp_socket_ = std::make_shared(io_service); + logger.info("Start controld tcp server on " + listen_); + } + catch(std::exception& e) + { + throw ControldException(e.what()); + } do_tcp_accept(); } } ControlServer::~ControlServer() { - if(is_unix_socket_ && is_file_exist(port_)) + if(is_unix_socket_ && is_file_exist(listen_)) { - remove(port_.c_str()); + remove(listen_.c_str()); } } void ControlServer::do_tcp_accept() diff --git a/controld.hpp b/controld.hpp index e5f3db0..90746bb 100644 --- a/controld.hpp +++ b/controld.hpp @@ -97,7 +97,7 @@ class ControlServer // флаг unix сервера bool is_unix_socket_; // порт запуска - std::string port_; + std::string listen_; // acceptors std::shared_ptr tcp_acceptor_; std::shared_ptr unix_acceptor_; diff --git a/ddosdetector.cpp b/ddosdetector.cpp index 3aedaea..da9fd59 100644 --- a/ddosdetector.cpp +++ b/ddosdetector.cpp @@ -96,13 +96,18 @@ void monitor(std::shared_ptr collect, Thread sends the data to the database InfluxDB, each period */ void start_control(boost::asio::io_service& io_service, - std::string port, std::shared_ptr collect) + std::string controld_serv, std::shared_ptr collect) { try { - ControlServer serv(io_service, port, collect); + ControlServer serv(io_service, controld_serv, collect); io_service.run(); } + catch (ControldException& e) + { + std::cout << "Error: " << e.what() << std::endl; + raise(SIGTERM); + } catch (std::exception& e) { logger << log4cpp::Priority::ERROR @@ -137,10 +142,10 @@ void task_runner(std::shared_ptr> task_list) int main(int argc, char** argv) { // Default settings std::string interface = ""; - std::string config_file = "/etc/ddosdetector.conf"; - std::string rules_file = "/etc/ddosdetector.rules"; + std::string config_file = "/etc/ddosdetector/conf.ini"; + std::string rules_file = "/etc/ddosdetector/rules.conf"; std::string log_file = ""; - std::string port = "9090"; + std::string controld_serv = "127.0.0.1:9090"; bool debug_mode = false; // Default for InfluxDB std::string influx_enable = "no"; @@ -157,10 +162,10 @@ int main(int argc, char** argv) { argv_opt.add_options() ("help,h", "show this help") ("interface,i", po::value(&interface), "network interface (default eth4)") - //("config,c", po::value(&config_file), "load config (default /etc/ddosdetector.conf)") - ("rules,r", po::value(&rules_file), "load rules from file (default /etc/ddosdetector.rules)") + //("config,c", po::value(&config_file), "load config (default /etc/ddosdetector/conf.ini)") + ("rules,r", po::value(&rules_file), "load rules from file (default /etc/ddosdetector/rules.conf)") ("log,l", po::value(&log_file), "log file (default output to console)") - ("port,p", po::value(&port), "port for controld tcp server (may be unix socket file)") + ("server,s", po::value(&controld_serv), "ip:port or unix socket file for controld server") ("debug,d", "enable debug output") ; // Configuration file options @@ -169,7 +174,7 @@ int main(int argc, char** argv) { ("Main.Interface", po::value(&interface)) ("Main.Rules", po::value(&rules_file)) ("Main.Log", po::value(&log_file)) - ("Main.Port", po::value(&port)) + ("Main.Listen", po::value(&controld_serv)) ("IndluxDB.Enable", po::value(&influx_enable)) ("IndluxDB.User", po::value(&influx_user)) ("IndluxDB.Password", po::value(&influx_pass)) @@ -368,7 +373,7 @@ int main(int argc, char** argv) { // run control server threads.add_thread(new boost::thread(start_control, - std::ref(io_s), port, main_collect)); + std::ref(io_s), controld_serv, main_collect)); // run triggerjob watcher thread threads.add_thread(new boost::thread(task_runner, task_list)); diff --git a/docs/EXAMPLE_RULES.md b/docs/EXAMPLE_RULES.md index 9013540..21430bf 100644 --- a/docs/EXAMPLE_RULES.md +++ b/docs/EXAMPLE_RULES.md @@ -8,8 +8,8 @@ TCP -d 92.53.116.23/32 --bps-th 100Mb --action log:/tmp/test.log TCP -d 92.53.116.22/32 --bps-th 100Mb --action log:/tmp/test.log TCP -d 92.53.116.40/32 --bps-th 100Mb --action log:/tmp/test.log TCP -d 92.53.98.109/32 --bps-th 100Mb --action log:/tmp/test.log -TCP -d 92.53.98.104/32 --bps-th 100Mb --action log:/tmp/test.log -TCP -d 92.53.116.66/32 --bps-th 100Mb --action log:/tmp/test.log +TCP 10 -d 92.53.98.104/32 --bps-th 100Mb --action log:/tmp/test.log +TCP 11 -d 92.53.116.66/32 --bps-th 100Mb --action log:/tmp/test.log TCP -d 92.53.116.67/32 --bps-th 100Mb --action log:/tmp/test.log TCP -d 92.53.116.68/32 --bps-th 100Mb --action log:/tmp/test.log TCP -d 92.53.116.69/32 --bps-th 100Mb --action log:/tmp/test.log diff --git a/docs/INFLUXDB.md b/docs/INFLUXDB.md index f1ae921..206de6f 100644 --- a/docs/INFLUXDB.md +++ b/docs/INFLUXDB.md @@ -35,7 +35,7 @@ auth-enabled = false ``` /etc/init.d/influxdb restart ``` -настраиваем доступ в конфигурационном файле ddosdetector (/etc/ddosdetector.conf) +настраиваем доступ в конфигурационном файле ddosdetector (/etc/ddosdetector/conf.ini) ```bash [IndluxDB] Enable = yes diff --git a/docs/README_RUS.md b/docs/README_RUS.md index 742e9e3..39eb38f 100644 --- a/docs/README_RUS.md +++ b/docs/README_RUS.md @@ -1,7 +1,7 @@ # Детектор DDoS атак # Система **ddosdetector** - это гибкий инструмент для анализа сетевого трафика и автоматизации процесса защиты от DDoS атак. Система основана на фреимворке [Luigi Rizzo](https://github.com/luigirizzo/netmap) [netmap](https://habrahabr.ru/post/183832/) и спроектирована для работы с большим объемом трафика (10Гб/сек и больше) без потерь производительности. -Система написана на языке C++ (стандарт 11) с использованием OpenSource библиотек *STL* и *Boost (1.55)*. Написание и сборка производилось на *Ubuntu 12.04.5 LTS* и компиляторе *g++-4.8*. Для статического анализа кода, проверки стиля и поиска грубых ошибок использовался *cppcheck* версии 1.73. +Система написана на языке C++ (стандарт 11) с использованием OpenSource библиотек *STL* и *Boost (1.58)*. Написание и сборка производилось на *Ubuntu 16.04 LTS* и компиляторе *g++-5*. Для статического анализа кода, проверки стиля и поиска грубых ошибок использовался *cppcheck* версии 1.73. Для мониторинга и сбора статистики используется InfluxDB. ![Grafana](images/grafana.png) @@ -71,6 +71,7 @@ rmmod e1000e && insmod /usr/src/netmap/LINUX/e1000e/e1000e.ko ### Установка ddosdetector ### собираем ddosdetector: ```bash +sudo apt-get install g++ libboost-all-dev libcurl4-gnutls-dev liblog4cpp5-dev git clone https://velizarx@bitbucket.org/velizarx/ddosdetector.git cd ./ddosdetector make @@ -93,7 +94,7 @@ cd ``` где: * *-i eth4 (**параметр обязателен**)* - *eth4* интерфейс запуска системы (на этом интерфейсе должен быть драйвер netmap см. README); -* *-r ~/ddosdetector.rules* - файл откуда будут загружены правила (этот параметр необязателен, по-умолчанию поиск файла производится по пути */etc/ddosdetector.rules*); +* *-r ~/ddosdetector.rules* - файл откуда будут загружены правила (этот параметр необязателен, по-умолчанию поиск файла производится по пути */etc/ddosdetector/rules.conf*); * *-p /tmp/ddosd.sock* - как запустить консоль (в данном случае UNIX socket, файл */tmp/ddosd.sock*), может быть путем к файлу, либо номером порта (тогда запускается TCP сервер на 127.0.0.1 и указанном порту), параметр необязателен, по-умолчанию запускается TCP сервер на порту 9090; * *-l ~/ddosdetector.log* - путь к лог файлу, по-умолчанию вывод в консоль @@ -105,14 +106,14 @@ socat - UNIX-CONNECT:/tmp/ddosd.sock ## Управление ## ### Конфигурационные файлы ### При запуске система пытается прочитать два конфигурационных файла: -* /etc/ddosdetector.conf - общие настройки системы -* /etc/ddosdetector.rules - сохраненные правила +* /etc/ddosdetector/conf.ini - общие настройки системы +* /etc/ddosdetector/rules.conf - сохраненные правила -Файл */etc/ddosdetector.conf* сожет содержать следующие настройки (значение натроек понятно из названия): +Файл */etc/ddosdetector/conf.ini* сожет содержать следующие настройки (значение натроек понятно из названия): ```ini [Main] Interface = eth0 -Rules = /etc/ddosdetector.rules +Rules = /etc/ddosdetector/rules.conf Log = /var/log/ddosdetector.log Port = 9090 @@ -282,7 +283,7 @@ ICMP rules (num, rule, counter): 1: -d 92.53.96.141/32 --pps-th 100p --type =0 --code =0 : 0.00p/s (0.00b/s), 0 packets, 0 bytes ``` #### Перезагрузка правил из файла #### -При запуске, система проверяет конфигурационный файл сохраненных ранее правил (по умолчанию */etc/ddosdetector.rules*). Если сиситема уже запущена, можно перезагрузить правила из файла вручную. Перезагрузка правил из файла выполняется либо из консоли управления командой **reload rules** либо отправкой сигнала SIGHUP демону. Например файл правил имеет содержимое: +При запуске, система проверяет конфигурационный файл сохраненных ранее правил (по умолчанию */etc/ddosdetector/rules.conf*). Если сиситема уже запущена, можно перезагрузить правила из файла вручную. Перезагрузка правил из файла выполняется либо из консоли управления командой **reload rules** либо отправкой сигнала SIGHUP демону. Например файл правил имеет содержимое: ```bash $ cat ~/ddosdetector.rules TCP -d 92.53.96.141/32 --pps-th 100p --seq =0 --pps-th-period 60 --action log:/tmp/test.log --next diff --git a/docs/images/dev-scheme.png b/docs/images/dev-scheme.png new file mode 100644 index 0000000..d88872b Binary files /dev/null and b/docs/images/dev-scheme.png differ diff --git a/docs/images/grafana.png b/docs/images/grafana.png new file mode 100644 index 0000000..8cc6c8c Binary files /dev/null and b/docs/images/grafana.png differ diff --git a/exceptions.cpp b/exceptions.cpp index 3960c7b..fe5c2fd 100644 --- a/exceptions.cpp +++ b/exceptions.cpp @@ -21,3 +21,10 @@ const char* NetmapException::what() const throw() { return message_.c_str(); } + +ControldException::ControldException(const std::string& message) + : message_(message) {}; +const char* ControldException::what() const throw() +{ + return message_.c_str(); +} diff --git a/exceptions.hpp b/exceptions.hpp index 098bcab..d278458 100644 --- a/exceptions.hpp +++ b/exceptions.hpp @@ -30,5 +30,14 @@ class NetmapException: public std::exception std::string message_; }; +class ControldException: public std::exception +{ +public: + explicit ControldException(const std::string& message); + virtual const char* what() const throw(); +private: + std::string message_; +}; + #endif // end EXCEPTIONS_HPP \ No newline at end of file diff --git a/functions.cpp b/functions.cpp index f513cfb..b14b145 100644 --- a/functions.cpp +++ b/functions.cpp @@ -121,6 +121,12 @@ bool is_executable(const std::string& file_name) return false; } +bool is_number(const std::string& s) +{ + return !s.empty() && std::find_if(s.begin(), + s.end(), [](char c) { return !std::isdigit(c); }) == s.end(); +} + std::string format_len(const std::string& s, unsigned int len) { std::string s_format = "%-" @@ -134,6 +140,7 @@ std::vector tokenize(const std::string& input, const separator_type // Tokenize the intput. boost::tokenizer tokens(input, separator); + // Copy non-empty tokens from the tokenizer into the result. std::vector result; for(const auto& t: tokens) @@ -151,6 +158,13 @@ std::vector tokenize(const std::string& input) separator_type separator("\\", // The escape characters. " ", // The separator characters. "\"\'"); // The quote characters. + return tokenize(input, separator); +} +std::vector tokenize(const std::string& input, const char *symbol) +{ + separator_type separator("\\", // The escape characters. + symbol, // The separator characters. + "\"\'"); // The quote characters. return tokenize(input, separator); } \ No newline at end of file diff --git a/functions.hpp b/functions.hpp index 03b85a4..e8ed192 100644 --- a/functions.hpp +++ b/functions.hpp @@ -54,6 +54,10 @@ bool is_file_exist(const std::string& file_name); проверяет исполняемый ли файл */ bool is_executable(const std::string& file_name); +/* + проверяет является ли строка числом +*/ +bool is_number(const std::string& s); /* форматирует строку по определенной длинне для выравнивания вывода @param s: строка @@ -67,6 +71,8 @@ std::string format_len(const std::string& s, unsigned int len); typedef boost::escaped_list_separator separator_type; std::vector tokenize(const std::string& input, const separator_type& separator); +std::vector tokenize(const std::string& input, + const char *symbol); std::vector tokenize(const std::string& input); /* возвращает номер элемента value в списке vec, или вызывает исключение diff --git a/rules.cpp b/rules.cpp index c75698c..a82be2d 100644 --- a/rules.cpp +++ b/rules.cpp @@ -347,30 +347,75 @@ void RulesFileLoader::reload_config() } if(t_cmd[0] == "TCP") // добавляем TCP правило { - buff_collect.tcp.add_rule( - TcpRule(std::vector( - t_cmd.begin() + 1, t_cmd.end() + if(is_number(t_cmd[1])) + { + int num = std::atoi(t_cmd[1].c_str()); + buff_collect.tcp.insert_rule( + num, + TcpRule( + std::vector( + t_cmd.begin() + 2, t_cmd.end() + ) ) - ) - ); + ); + } + else + { + buff_collect.tcp.add_rule( + TcpRule(std::vector( + t_cmd.begin() + 1, t_cmd.end() + ) + ) + ); + } } else if(t_cmd[0] == "UDP") // добавлем UDP правило { - buff_collect.udp.add_rule( - UdpRule(std::vector( - t_cmd.begin() + 1, t_cmd.end() + if(is_number(t_cmd[1])) + { + int num = std::atoi(t_cmd[1].c_str()); + buff_collect.udp.insert_rule( + num, + UdpRule( + std::vector( + t_cmd.begin() + 2, t_cmd.end() + ) + ) + ); + } + else + { + buff_collect.udp.add_rule( + UdpRule(std::vector( + t_cmd.begin() + 1, t_cmd.end() + ) ) - ) - ); + ); + } } else if(t_cmd[0] == "ICMP") // добавляем ICMP правило { - buff_collect.icmp.add_rule( - IcmpRule(std::vector( - t_cmd.begin() + 1, t_cmd.end() + if(is_number(t_cmd[1])) + { + int num = std::atoi(t_cmd[1].c_str()); + buff_collect.icmp.insert_rule( + num, + IcmpRule( + std::vector( + t_cmd.begin() + 2, t_cmd.end() + ) + ) + ); + } + else + { + buff_collect.icmp.add_rule( + IcmpRule(std::vector( + t_cmd.begin() + 1, t_cmd.end() + ) ) - ) - ); + ); + } } else // если правило неопределенного типа {