Detect Huge Number of HTTP Requests on Apache and nginx using mruby code.It seems, programmable DDoS firewall by mruby on nginx.
http-dos-detector use same Ruby code between Apache(mod_mruby) and nginx(ngx_mruby).
This solution provides regulating the incoming HTTP/S traffic and controlling the traffic as it is proxied to backend servers.
Let's try.
Environment: Amazon Linux AMI 2015.09.1 (HVM), SSD Volume Type - ami-383c1956
c4.xlarge
Update Ruby
(Just update Ruby for Amazon Linux AMI. Actually, no meaning for ngx_mruby. ngx_mruby uses mruby.)
# yum remove ruby* # yum install ruby22 ruby22-devel rubygem22 rubygem22-rake aws-amitools-ec2 # ruby -v ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-linux-gnu]
Install ngx_mruby with nginx
Why install nginx by yum version? because yum helps some setup operation easily. for example, mkdir, set logrotate, set start script and more.
Use /opt for FHS (http://www.pathname.com/fhs/pub/fhs-2.3.html#OPTADDONAPPLICATIONSOFTWAREPACKAGES).
# yum install git gcc bison openssl-devel pcre-devel nginx # cd /opt # git clone git://github.com/matsumoto-r/ngx_mruby.git && cd ngx_mruby # NGINX_CONFIG_OPT_ENV='--prefix=/opt/nginx' sh build.sh # make install # cp /opt/nginx/sbin/nginx /usr/sbin/nginx # nginx -V nginx version: nginx/1.9.6 built by gcc 4.8.3 20140911 (Red Hat 4.8.3-9) (GCC) configure arguments: --add-module=/opt/ngx_mruby --add-module=/opt/ngx_mruby/dependence/ngx_devel_kit --prefix=/opt/nginx
Running "Hello mruby"
# emacs /etc/nginx/nginx.conf
server {
location /hello {
mruby_content_handler_code '
Server = nginx
Server.echo "Hello mruby"
';
}
# service nginx start
# curl 127.0.0.1/hello
Hello mruby
Benchmark nginx and ngx_mruby "Hello mruby"
nginx returns static file.
# echo "Hello nginx" > /usr/share/nginx/html/index.html # ab -c 100 -n 1000000 127.0.0.1/ Requests per second: 38185.31 [#/sec] (mean) Time per request: 2.619 [ms] (mean)nginx + ngx_mruby returns "Hello mruby"
# ab -c 100 -n 1000000 127.0.0.1/hello Requests per second: 36466.16 [#/sec] (mean) Time per request: 2.742 [ms] (mean)This is 95.04% power than "nginx static file".
Setup http-dos-detector
Git clone source code.
# cd /opt # git clone git://github.com/matsumoto-r/http-dos-detector.git # cp -r /opt/http-dos-detector/dos_detector /etc/nginx/conf.dCustomize dos_detector.rb file. This example is IP address base blocking.
# emacs /etc/nginx/conf.d/dos_detector/dos_detector.rb
Server = get_server_class
r = Server::Request.new
c = Server::Connection.new
cache = Userdata.new.shared_cache
global_mutex = Userdata.new.shared_mutex
host = r.hostname
ip_address = c.remote_ip
if r.headers_in["X-Real-IP"]
ip_address = r.headers_in["X-Real-IP"]
elsif r.headers_in["X-Forwarded-For"]
ip_address = r.headers_in["X-Forwarded-For"].split(",").first
end
config = {
# dos counter by key
:counter_key => ip_address,
:magic_str => "....",
# judging dos access when the number of counter is between :behind_counter and 0
:behind_counter => -20000,
# set behind counter when the number of counter is over :threshold_counter
# in :threshold_time sec
:threshold_counter => 10000,
:threshold_time => 5,
# expire dos counter and initialize counter even
# if the number of counter is between :behind_counter and 0
:expire_time => 10,
}
unless r.sub_request?
# process-shared lock
timeout = global_mutex.try_lock_loop(50000) do
dos = DosDetector.new r, cache, config
data = dos.analyze
Server.errlogger Server::LOG_NOTICE, "[INFO] dos_detetor: detect dos: #{data}"
begin
if dos.detect?
Server.errlogger Server::LOG_NOTICE, "dos_detetor: detect dos: #{data}"
Server.return Server::HTTP_SERVICE_UNAVAILABLE
end
rescue => e
raise "DosDetector failed: #{e}"
ensure
global_mutex.unlock
end
end
if timeout
Server.errlogger Server::LOG_NOTICE, "dos_detetor: get timeout mutex lock, #{data}"
end
end
Modify nginx.conf. add error.log to notice and mruby_init, mruby_init_worker and mruby_access_handler.
# emacs /etc/nginx/nginx.conf
error_log /var/log/nginx/error.log notice;
http {
mruby_init /etc/nginx/conf.d/dos_detector/dos_detector_init.rb cache;
mruby_init_worker /etc/nginx/conf.d/dos_detector/dos_detector_worker_init.rb cache;
server {
location /dos_detector {
mruby_access_handler /etc/nginx/conf.d/dos_detector/dos_detector.rb cache;
}
}
}
# service nginx restart
Benchmark http-dos-detector
nginx + ngx_mruby + dos_detector returns "Hello dos_detector"
# mkdir /usr/share/nginx/html/dos_detector/ # echo "Hello dos_detector" > /usr/share/nginx/html/dos_detector/index.htmlRun Apache Bench
# ab -c 100 -n 1000000 127.0.0.1/dos_detector/ Complete requests: 1000000 Non-2xx responses: 800000 Requests per second: 9280.54 [#/sec] (mean) Time per request: 10.775 [ms] (mean)"nginx static file" can run much faster this. But, If backend application provides small power than this response (you have no C10k problem), nginx + ngx_mruby + http-dos-detector will be good solution for Mitigating DDoS Attacks.
See also.
ngx_mruby
https://github.com/matsumoto-r/ngx_mruby
mruby-logo provided by h2so5