Openresty: Simple GeoIP and ASNnum query server


Read first: How to build openresty with GeoIP and Naxsi on CentOS 7

We build a simple GeoIP and ASNnum query server:

  1. http://example.com:31000 for current GeoIP and http://example.com/ip for current IP only
  2. http://example.com:31000/x.x.x.x to query any IP.
  3. http://example.com:31000/domain-name for GeoIP of domain-name and http://example.com:31000/domain-name/ip to return IP of domain-name only. If multiple IPs are set to single domain-name, all of them will be returned. Only A record will be used.

Sample requests are:

$ curl http://example.com/
x.x.x.x
Country, Region, City
ASN number

$ curl http://example.com:31000/ip
x.x.x.x

$ curl http://example.com:31000/rdns
x.x.x.x.com

$ curl http://example.com:31000/74.125.203.199
74.125.203.199  th-in-f199.1e100.net
United States, California, Mountain View
AS15169 Google Inc.

$ curl http://example.com:31000/www.google.com.hk
74.125.203.199  th-in-f199.1e100.net
United States, California, Mountain View
AS15169 Google Inc.

$ curl http://example.com:31000/www.google.com.hk/ip
74.125.203.199

$ curl http://example.com:31000/www.google.com.hk/dns
www-wide.l.google.com 74.125.203.199

File nginx.conf:

load_module /usr/local/openresty/nginx/modules/ngx_http_geoip2_module.so;

worker_processes  1;
 
events {
    worker_connections  1024;
}

http {
	map $http_x_forwarded_for $realip {
        ~^(\d+\.\d+\.\d+\.\d+) $1;
        default $remote_addr;
    }
 
	geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
        auto_reload 5m;
        $geoip2_metadata_country_build metadata build_epoch;
        $geoip2_data_country_code default=US source=$realip country iso_code;
        $geoip2_data_country_name source=$realip country names en;
		$geoip2_data_country_geonameid country geoname_id;         
        $geoip2_data_country_is_eu country is_in_european_union;
    }

    geoip2 /usr/share/GeoIP/GeoLite2-City.mmdb {
		auto_reload 5m;         
		$geoip2_metadata_city_build metadata build_epoch;         
		$geoip2_data_city_name source=$realip city names en;
        $geoip2_data_city_geonameid source=$realip city geoname_id;
        $geoip2_data_continent_code source=$realip continent code;
        $geoip2_data_continent_geonameid source=$realip continent geoname_id;
        $geoip2_data_continent_name source=$realip continent names en;
        $geoip2_data_location_accuracyradius source=$realip location accuracy_radius;
        $geoip2_data_location_latitude source=$realip location latitude;
        $geoip2_data_location_longitude source=$realip location longitude;
        $geoip2_data_location_metrocode source=$realip location metro_code;
        $geoip2_data_location_timezone source=$realip location time_zone;
        $geoip2_data_postal_code source=$realip postal code;
        $geoip2_data_rcountry_geonameid source=$realip registered_country geoname_id;
        $geoip2_data_rcountry_iso source=$realip registered_country iso_code;
        $geoip2_data_rcountry_name source=$realip registered_country names en;
        $geoip2_data_rcountry_is_eu source=$realip registered_country is_in_european_union;
        $geoip2_data_region_geonameid source=$realip subdivisions 0 geoname_id;
        $geoip2_data_region_iso source=$realip subdivisions 0 iso_code;
        $geoip2_data_region_name source=$realip subdivisions 0 names en;
    }
	geoip2 /usr/share/GeoIP/GeoLite2-ASN.mmdb {
        auto_reload 5m;        
		$geoip2_data_org source=$realip autonomous_system_organization;
		$geoip2_data_asn source=$realip autonomous_system_number;
    }
  
	## FastCGI GeoIP 2
    fastcgi_param MM_ADDR $remote_addr;    
    fastcgi_param MM_CITY_NAME $geoip2_data_city_name;
    fastcgi_param MM_CITY_GEONAMEID $geoip2_data_city_geonameid;
    fastcgi_param MM_CONTINENT_CODE $geoip2_data_continent_code;
    fastcgi_param MM_CONTINENT_GEONAMEID $geoip2_data_continent_geonameid;
    fastcgi_param MM_CONTINENT_NAME $geoip2_data_continent_name;
    fastcgi_param MM_COUNTRY_GEONAMEID $geoip2_data_country_geonameid;
    fastcgi_param MM_COUNTRY_CODE $geoip2_data_country_code;
    fastcgi_param MM_COUNTRY_NAME $geoip2_data_country_name;
    fastcgi_param MM_COUNTRY_IN_EU $geoip2_data_country_is_eu;
    fastcgi_param MM_LOCATION_ACCURACY_RADIUS $geoip2_data_location_accuracyradius;
    fastcgi_param MM_LATITUDE $geoip2_data_location_latitude;
    fastcgi_param MM_LONGITUDE $geoip2_data_location_longitude;
    fastcgi_param MM_LOCATION_METROCODE $geoip2_data_location_metrocode;
    fastcgi_param MM_LOCATION_TIMEZONE $geoip2_data_location_timezone;
    fastcgi_param MM_POSTAL_CODE $geoip2_data_postal_code;
    fastcgi_param MM_REGISTERED_COUNTRY_GEONAMEID $geoip2_data_rcountry_geonameid;
    fastcgi_param MM_REGISTERED_COUNTRY_ISO $geoip2_data_rcountry_iso;
    fastcgi_param MM_REGISTERED_COUNTRY_NAME $geoip2_data_rcountry_name;
    fastcgi_param MM_REGISTERED_COUNTRY_IN_EU $geoip2_data_rcountry_is_eu;
    fastcgi_param MM_REGION_GEONAMEID $geoip2_data_region_geonameid;
    fastcgi_param MM_REGION $geoip2_data_region_iso;
    fastcgi_param MM_REGION_NAME $geoip2_data_region_name;
    fastcgi_param MM_ISP $geoip2_data_org;
    fastcgi_param MM_ORG $geoip2_data_org;

    include       mime.types;
    default_type  application/octet-stream;
 
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
	
	log_format app_log '$remote_addr [$time_local] "$request" $status $body_bytes_sent '
                   '$request_time $http_host "$http_referer" $http_user_agent';
				   
	map $remote_addr $app_loggable {
		default 1;
		"127.0.0.1" 0;
		"::1" 0;
	}

    server {
        listen       80;
        server_name  localhost;
 
        location / {
            root   html;
            index  index.html index.htm;
			 
        }
		 
		location /json {
	
			add_header Access-Control-Allow-Origin *;
		
			add_header Content-Type application/json;
			 
			content_by_lua_block {				
				ngx.say('{"ip":"'..ngx.var.remote_addr..'","code":"'.. ngx.var.geoip2_data_country_code..'"}')
			}
		}
		        
        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
 
    }

 
	server {
		listen 31000;
		server_name localhost;

		access_log logs/access.log app_log if=$app_loggable;
		error_log logs/error.log warn;

		location = / {
			default_type "text/plain";
			content_by_lua_block {
				local query_ip = ngx.var.remote_addr
				if ngx.var.remote_addr == "127.0.0.1"then
					query_ip = ngx.var.http_x_forwarded_for
				end

				local resolver = require "resty.dns.resolver"
				local r, err = resolver:new {
				   nameservers = {"8.8.8.8", {"8.8.4.4", 53} },
				   retrans = 5,
				   timeout = 2000
				}
				local rdns = ""
				while true do
					if not r then
						ngx.log(ngx.ERR, "can't new resolver", err)
						break
					end

					local answers, err = r:reverse_query(query_ip)
					if not answers or #answers == 0 then
						break
					end
					rdns = answers[1].ptrdname
					break
				end

				if rdns then
					ngx.say(query_ip .. "  " .. rdns)
				else
					ngx.say(query_ip)
				end
				local has_line = false
				if ngx.var.geoip2_data_country_name ~= nil then
					ngx.print(ngx.var.geoip2_data_country_name)
					has_line = true
				end
				if ngx.var.geoip2_data_region_name ~= nil then
					ngx.print(", ", ngx.var.geoip2_data_region_name)
					has_line = true
				end
				if ngx.var.geoip2_data_city_name ~= nil then
					ngx.print(", ", ngx.var.geoip2_data_city_name)
					has_line = true
				end
				if has_line then
					ngx.print("\n")
				end
				if ngx.var.geoip2_data_org ~= nil then
					ngx.say('AS'..ngx.var.geoip2_data_asn..' '..ngx.var.geoip2_data_org)					
				end
			}
		}

		location ~ ^/([\d\.\:]+)$ {
			set_by_lua $query_ip '
				local m, err = ngx.re.match(ngx.var.uri, "^/(.+)")
				if m then
					ngx.log(ngx.INFO, "ip", m[1])
					return m[1]
				end
			';
			set $up "http://127.0.0.1:31000/";
			proxy_set_header X-Forwarded-For $query_ip;
			proxy_set_header Host $http_host;
			proxy_pass $up;
			proxy_hide_header 'Content-Type';
			proxy_hide_header 'Vary';
			proxy_hide_header 'Access-Control-Allow-Origin';
			add_header 'Content-Type' 'text/plain';
			add_header 'Vary' 'Accept-Encoding';
			add_header 'Access-Control-Allow-Origin' '*';
		}

		location = /ip {
			default_type "text/plain";
			echo $remote_addr;
		}

		location = /rdns {
			default_type "text/plain";
			content_by_lua_block {
				local resolver = require "resty.dns.resolver"
				local r, err = resolver:new {
				   nameservers = {"8.8.8.8", {"8.8.4.4", 53} },
				   retrans = 5,
				   timeout = 2000
				}
				local rdns = "default"
				if not r then
					ngx.log(ngx.ERR, "can't new resolver", err)
					ngx.exit(200)
				end

				local answers, err = r:reverse_query(ngx.var.remote_addr)
				if not answers or #answers == 0 then
					ngx.exit(200)
				end
				rds = answers[1].ptrdname
				ngx.say(rds or "")
			}
		}


		location ~ /([^/]+)/*([^/]*)$ {
			default_type "text/plain";
			set $query_host $1;
			set $qopt $2;
			content_by_lua_block {
				local resolver = require "resty.dns.resolver"
				local r, err = resolver:new {
				   nameservers = {"8.8.8.8", {"8.8.4.4", 53} },
				   retrans = 5,
				   timeout = 2000
				}
				if not r then
					ngx.log(ngx.ERR, "can't new resolver", err)
					ngx.exit(500)
				end

				local answers, err = r:query(ngx.var.query_host)

				if not answers then
					ngx.log(ngx.ERR, "query error", ngx.var.query_host, ":", err, " answer: ", answers.errcode, answers.errstr)
					ngx.exit(200)
				elseif #answers == 0 then
					ngx.exit(200)
				end
				if string.len(ngx.var.qopt) > 0 then
					table.foreach(answers, function(i, a)
						if ngx.var.qopt == "dns" and a.cname then
							ngx.print(a.cname.. " ")
						end
						if a.address then
							ngx.say(a.address)
						end
					end)
				else
					local _ = {}
					table.foreach(answers, function(i, a)
						 if a.address then
							 table.insert(_, {"/"..a.address})
						 end
					end)

					if #_ == 0 then
						ngx.exit(200)
					end

					local resps = { ngx.location.capture_multi(_) }
					for i, resp in ipairs(resps) do
						ngx.say(resp.body)
					end
				end

			}
		}
	}

}

Reference: https://gist.github.com/fffonion/44e5fb59e2a8f0efba5c1965c6043584

You can change port 31000 to 80

Leave a Reply