Monday, November 9, 2015

The first step of Norikra

http://norikra.github.io/
https://github.com/norikra/norikra
Norikra is an open-source Stream Processing Server with SQL.
Schema-less event streams (called as 'target')
SQL processing with window specifier supports, and JOINs, SubQueries
Complex input/output events with nested Hashes and Arrays, and Query supports
Dynamic query registration/removing, without any restarts
Ultra fast bootstrap and small start
UDF plugins
This entry provides that how to run Norikra on Docker, and some example of put JSON and get result by SQL like query over HTTP API. Why using Norikra on Docker? I thought that It is important to cut down on install operations as much as possible.
Let's try.

Environment: CentOS 6.7

Install Norikra on Docker
Install Docker.
# yum install docker-io
# service docker start
Run Norikra on Docker. Use this Docker image. https://registry.hub.docker.com/u/myfinder/docker-norikra/
# docker pull myfinder/docker-norikra
# docker run -d -p 26578:26578 -p 26571:26571 -v /var/tmp/norikra:/var/tmp/norikra:rw -t myfinder/docker-norikra norikra start --stats /var/tmp/norikra/stats.json -l /var/tmp/norikra
Check it. What's up? Norikra.
# docker ps
CONTAINER ID        IMAGE                     COMMAND                CREATED             STATUS              PORTS                                                NAMES
25842cda847f        myfinder/docker-norikra   "norikra start --sta   5 seconds ago       Up 4 seconds        0.0.0.0:26571->26571/tcp, 0.0.0.0:26578->26578/tcp   naughty_mcclintock
# docker exec -it 25842cda847f ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0 10 03:07 ?        00:00:28 java -Xmx500m -Xss2048k -Djffi.boot.library.path=/jruby-1.7.19/lib/jni -server -XX:-UseGCO
root        64     0  0 03:11 ?        00:00:00 ps -ef
About one minute later..
# tail -f /var/tmp/norikra/norikra.log
2015-11-09 03:08:16 +0000 [INFO] : thread configurations, engine:{:inbound=>{:threads=>0, :capacity=>0}, :outbound=>{:threads=>0, :capacity=>0}, :route_exec=>{:threads=>0, :capacity=>0}, :timer_exec=>{:threads=>0, :capacity=>0}}, rpc:{:threads=>2}, web:{:threads=>2}
2015-11-09 03:08:16 +0000 [INFO] : logging configurations, level:nil, dir:"/var/tmp/norikra", filesize:nil, backups:nil, bufferlines:nil
2015-11-09 03:08:17 +0000 [INFO] : Loading UDF plugins
2015-11-09 03:08:17 +0000 [INFO] : Loading Listener plugins
2015-11-09 03:08:17 +0000 [INFO] : Listener loaded, name:Norikra::Listener::Stdout
2015-11-09 03:08:17 +0000 [INFO] : Listener loaded, name:Norikra::Listener::Loopback
2015-11-09 03:08:17 +0000 [INFO] : RPC server 0.0.0.0:26571, 2 threads
2015-11-09 03:08:17 +0000 [INFO] : WebUI server 0.0.0.0:26578, 2 threads
2015-11-09 03:08:17 +0000 [INFO] : Norikra server started.
# curl -I localhost:26578
HTTP/1.1 200 OK
You can see web console.

Examples
Set alias of norikra-client for docker exec.
# alias norikra-client='docker exec -it 25842cda847f norikra-client'
Make target.
# norikra-client target open www path:string status:integer referer:string agent:string userid:integer
# norikra-client target list
TARGET AUTO_FIELD
www true
1 targets found.

Example 1
Make querie name of "www.toppageviews".
# norikra-client query add www.toppageviews 'SELECT count(*) AS cnt FROM www.win:time_batch(10 sec) WHERE path="/" AND status=200'
Send events.
# curl -X POST -H "Content-Type: application/json" --data '{"target":"www", "events":[{"path":"/", "status":200, "referer":"", "agent":"MSIE", "userid":3}]}' http://localhost:26578/api/send
# curl -X POST -H "Content-Type: application/json" --data '{"target":"www", "events":[{"path":"/login", "status":301, "referer":"/", "agent":"MSIE", "userid":3}]}' http://localhost:26578/api/send
# curl -X POST -H "Content-Type: application/json" --data '{"target":"www", "events":[{"path":"/content", "status":200, "referer":"/login", "agent":"MSIE", "userid":3}]}' http://localhost:26578/api/send
# curl -X POST -H "Content-Type: application/json" --data '{"target":"www", "events":[{"path":"/page/1", "status":200, "referer":"/content", "agent":"MSIE", "userid":3}]}' http://localhost:26578/api/send
Result of querie.
# curl -X GET -H "Content-Type: application/json" --data '{"query_name":"www.toppageviews"}' http://localhost:26578/api/see
[[1447046244,{"cnt":1}]]

Example 2
Add other querie name of "www.search".
# norikra-client query add www.search 'SELECT count(*) AS cnt FROM www.win:time_batch(10 sec) WHERE path="/content" AND search_param.length() > 0'
Send events.
# curl -X POST -H "Content-Type: application/json" --data '{"target":"www", "events":[{"path":"/", "status":200, "referer":"", "agent":"MSIE", "userid":3}]}' http://localhost:26578/api/send
# curl -X POST -H "Content-Type: application/json" --data '{"target":"www", "events":[{"path":"/", "status":200, "referer":"", "agent":"Firefox", "userid":4}]}' http://localhost:26578/api/send
# curl -X POST -H "Content-Type: application/json" --data '{"target":"www", "events":[{"path":"/content", "status":200, "referer":"/login", "agent":"MSIE", "userid":3}]}' http://localhost:26578/api/send
# curl -X POST -H "Content-Type: application/json" --data '{"target":"www", "events":[{"path":"/content", "status":200, "referer":"/login", "agent":"Firefox", "userid":4, "search_param":"news worldwide"}]}' http://localhost:26578/api/send
Result of querie with removing events.
# curl -X GET -H "Content-Type: application/json" --data '{"query_name":"www.search"}' http://localhost:26578/api/see
[[1447046270,{"cnt":1}],[1447046280,{"cnt":0}]]

It seems good!

Top image from https://github.com/norikra/norikra.github.io

Thursday, November 5, 2015

Mitigating DDoS Attacks nginx with module

nginx module of ngx_http_limit_conn_module and ngx_http_limit_req_module has resistant to DDoS attack.
This nginx modules will provides regulating the incoming HTTP/S traffic and controlling the traffic as it is proxied to backend servers.
This is native nginx module. so settings is easy and performance is nice. If you want to anti DDoS system easily, maybe this solution is good for you.

Let's Try.

HTTP Connection limit
http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html

Set zone name of shared memory and maximum size of this zone.
http {
    # memory limit for number of connections
    limit_conn_zone $remote_addr zone=connection_limit_per_ip:10m;
Allow 10 connection per an IP address at a time.
server {
    # connection limit
    limit_conn connection_limit_per_ip 30;

Let's Check.

Connection number is 10.
It's no Failed.
# ab -c 10 -n 100000 http://127.0.0.1/
Complete requests:      100000
Failed requests:        0
Connection number is 100.
It has Failed.
# ab -c 100 -n 100000 http://127.0.0.1/
Complete requests:      100000
Failed requests:        11866

HTTP request limit
http://nginx.org/en/docs/http/ngx_http_limit_req_module.html

Allow 100 request per an IP address at a one secound.
http {
    # memory limit for number of requests
    limit_req_zone $remote_addr zone=request_limit_per_ip:10m rate=100r/s;
Sets the maximum burst size of requests.
For example, when request is over the 100r/s, max number of connection will be 10.
If you want to no
server {
    # request limit
    limit_req zone=request_limit_per_ip burst=10;

Let's Check.

Connection number is 10. burst=10.
It has no failed. but it has delay.
Time taken for tests is long.
# ab -c 10 -n 1000 http://127.0.0.1/
Time taken for tests:   10.365 seconds
Complete requests:      1000
Failed requests:        0
# grep delay /var/log/nginx/error.log
2015/11/05 15:06:18 [warn] 8595#0: *999 delaying request, excess: 9.700, by zone "request_limit_per_ip", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "127.0.0.1"
2015/11/05 15:06:18 [warn] 8595#0: *1000 delaying request, excess: 9.700, by zone "request_limit_per_ip", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "127.0.0.1"

Connection number is 10. burst=9.
It has failed and delay.
Time taken for tests is short. because many requests has faild.
# ab -c 10 -n 1000 http://127.0.0.1/
Time taken for tests:   0.231 seconds
Complete requests:      1000
Failed requests:        954
# grep delay /var/log/nginx/error.log
2015/11/05 15:07:06 [warn] 8652#0: *912 delaying request, excess: 9.000, by zone "request_limit_per_ip", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "127.0.0.1"
2015/11/05 15:07:06 [warn] 8652#0: *961 delaying request, excess: 9.000, by zone "request_limit_per_ip", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "127.0.0.1"

Connection number is 10. burst=10 nodelay.
If you want to no delay, you can add nodelay opetion.
# ab -c 10 -n 1000 http://127.0.0.1/
Time taken for tests:   0.204 seconds
Complete requests:      1000
Failed requests:        969
# grep delay /var/log/nginx/error.log
Nothing delay.

Exsample Settings of nginx conf
http {
    # Amazon ELB (VPC) 
    set_real_ip_from 172.16.0.0;
    real_ip_header     X-Forwarded-For;

    # memory limit for number of connections
    limit_conn_zone $remote_addr zone=connection_limit_per_ip:10m;

    # memory limit for number of requests
    limit_req_zone $remote_addr zone=request_limit_per_ip:10m rate=1000r/s;

server {
    # connection limit
    limit_conn connection_limit_per_ip 100;

    # request limit
    limit_req zone=request_limit_per_ip burst=10;


nginx logo from NGINX Newsroom - NGINX

Wednesday, November 4, 2015

Mitigating DDoS Attacks nginx + ngx_mruby + http-dos-detector

https://github.com/matsumoto-r/http-dos-detector
Detect Huge Number of HTTP Requests on Apache and nginx using mruby code.
http-dos-detector use same Ruby code between Apache(mod_mruby) and nginx(ngx_mruby).
It seems, programmable DDoS firewall by mruby on nginx.
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.d
Customize 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.html
Run 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