Check your IP infos using nginx

       792 words, 4 minutes

There are a few times when I want to check which public IP I am browsing from… I used to use the WhatsMyIP sites but the Internet being what it is these days, I switched to using my SearXNG instance which has the IP plugin enabled.

Still, there are times I need to get my IP from a script and want a dead simple option for this. So I switched to using nginx and GeoLite2.

I was looking at some Web 3.0 pretty GUI but mostly found bloated stuff made out of Go or Rust or Node.js. I finally stumbled upon ipinfo.tw which had both the advantage of requiring little stuff to run and providing all the informations I wanted without unnecessary extra stuff. The only drawback is that it is supposed to run using Docker… Which doesn’t match my OpenBSD requirements. But I am Docker-fluent enough to understand a Dockerfile, reverse engineer it and have it working on OpenBSD.

The overall process is that for each HTTP connection, nginx will query the incoming IP from GeoLite2 databases, format a text HTTP response and send it back to the HTTP client.

Required packages

From a bare OpenBSD instance, one has to install the nginx package and its nginx-geoip2 module package.

# pkg_add nginx-geoip2 nginx

Unfortunately, OpenBSD doesn’t provide the latest version of the GeoLite2 databases. Because

Last version prior to license change https://blog.maxmind.com/2019/12/significant-changes-to-accessing-and-using-geolite2-databases/ Up to date files available free of charge, but an account and EULA agreement are needed: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data/

GeoLite2 databases

You can sign up for free GeoLite databases access from https://www.maxmind.com/en/geolite2/signup . For reasons, I had to use a Gmail account for it to work… When done, sign in and create License key. Write down your Account ID and License key. There seem to be an existing software, Automatic Updates for GeoIP Databases, that will update the databases. But I’d rather use my own script.

# pkg_add wget
# vi /opt/bin/update-geoip
#!/bin/ksh

LICENSE="<change_me>"
URL="https://download.maxmind.com/app/geoip_download?license_key=${LICENSE}&edition_id="

cd /var/www/cache
for DB in GeoLite2-ASN GeoLite2-City; do
        doas -u www wget -q -O ${DB}.tar.gz "${URL}${DB}&suffix=tar.gz"
        doas -u www tar -xzf ${DB}.tar.gz -s ':.*/::' '*.mmdb'
        rm ${DB}.tar.gz
        rcctl reload nginx
done
cd -

exit 0
#EOF
# chmod 0755 /opt/bin/update-geoip
# /opt/bin/update-geoip

Setting up a cron job to daily update the databases ensures I get the latest information.

nginx configuration

Stealing configuration bits from ipinfo.tw, and adapting it to OpenBSD, the nginx configuration to send back IP information from HTTP queries goes like this:

# vi /etc/nginx/nginx.conf
#user  www;
worker_processes auto;
load_module modules/ngx_http_geoip2_module.so;
pid logs/nginx.pid;
worker_rlimit_nofile 32768;
events {
  accept_mutex       off;
  multi_accept       on;
  worker_connections 4096;
}
http {
  # query city databases
  geoip2 /var/www/htdocs/GeoLite2-City.mmdb {
    auto_reload          1d;
    $ip_continent_name   source=$remote_addr continent names en;
    $ip_country_code     source=$remote_addr country iso_code;
    $ip_country_name     source=$remote_addr country names en;
    $ip_subdivision_name source=$remote_addr subdivisions 0 names en;
    $ip_city_code        source=$remote_addr postal code;
    $ip_city_name        source=$remote_addr city names en;
    $ip_loc_lat          source=$remote_addr location latitude;
    $ip_loc_lon          source=$remote_addr location longitude;
    $ip_loc_tz           source=$remote_addr location time_zone;
  }
  # if values are empty, deal with it for proper output
  map $ip_subdivision_name $subdivision_name {
    ""      "n/a";
    default $ip_subdivision_name;
  }
  map $ip_city_code $city_code {
    ""      "";
    default "($ip_city_code)";
  }
  map $ip_city_name $city_name {
    ""      "n/a";
    default $ip_city_name;
  }
  # query provider database
  geoip2 /var/www/htdocs/GeoLite2-ASN.mmdb {
    auto_reload        1d;
    $ip_asn            source=$remote_addr autonomous_system_number;
    $ip_aso            source=$remote_addr autonomous_system_organization;
    $ip_as_build_epoch metadata build_epoch;
  }
  include              mime.types;
  default_type         text/plain;
  index index.html     index.htm;
  tcp_nopush           on;
  keepalive_timeout    60;
  gzip                 on;
  server_tokens        off;
  autoindex            off;
  client_max_body_size 512k;
  keepalive_requests   100;
  tcp_nodelay          on;
  types_hash_max_size  2048;
  server {
    listen      80;
    listen      [::]:80;
    server_name ip.example.com;
    root        /var/www/htdocs;
    charset     utf-8;
    error_page  500 502 503 504 /50x.html;
    location = /50x.html {
      root /var/www/htdocs;
    }
    real_ip_header   X-Real-IP;
    set_real_ip_from 10.0.0.0/8;
    set_real_ip_from 172.16.0.0/12;
    set_real_ip_from 192.168.0.0/16;
    add_header X-Powered-By            "Inspired by ipinfo.tw/github" always;
    add_header Cache-Control           "no-store" always;
    add_header X-Frame-Options         "SAMEORIGIN" always;
    add_header X-XSS-Protection        "1; mode=block" always;
    add_header X-Content-Type-Options  "nosniff" always;
    add_header Referrer-Policy         "no-referrer-when-downgrade" always;
    add_header Content-Security-Policy "default-src 'none'; img-src 'self'" always;
    location = / {
      return 200 "Public IP:  $remote_addr
---
Continent:  $ip_continent_name
Country:    $ip_country_name ($ip_country_code)
Region:     $subdivision_name
City:       $city_name $city_code [$ip_loc_lat/$ip_loc_lon]
---
Provider:   $ip_aso (AS$ip_asn)
---
Timezone:   $ip_loc_tz
User-Agent: $http_user_agent";
    }
    location = /ip { return 200 "$remote_addr\n"; }
    location = /country { return 200 "$ip_country_name\n"; }
    location = /json {
      default_type application/json;
      return 200 "{\"ip\":\"$remote_addr\",\"continent_name\":\"$ip_continent_name\",\"country_name\":\"$ip_country_name\",\"country_code\":\"$ip_country_code\",\"region\":\"$subdivision_name\",\"city\":\"$city_name\",\"city_code\":\"$city_code\",\"latitude\":\"$ip_loc_lat\",\"longitude\":\"$ip_loc_lon\",\"provider\":\"$ip_aso\",\"asn\":\"$ip_asn\",\"timezone\":\"$ip_loc_tz\",\"user-agent\":\"$http_user_agent\"}";
    }
  }
}
# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

# rcctl enable nginx
# rcctl start nginx

Usage

Browse to the published URL using a Web browser or a command line to get the results.

# curl http://example.com
Public IP:  192.0.2.144
---
Continent:  Europe
Country:    France (FR)
Region:     Île-de-France
City:       Paris (75001) [48.864716/2.349014]
---
Provider:   Orange (AS3215)
---
Timezone:   Europe/Paris
User-Agent: curl/8.14.0

# curl http://example.com/json
{"ip":"192.0.2.144","continent_name":"Europe","country_name":"France","country_code":"FR","region":"Île-de-France","city":"Paris","city_code":"(75001)","latitude":"48.864716","longitude":"2.349014","provider":"Orange","asn":"3215","timezone":"Europe/Paris","user-agent":"curl/8.14.0"}

For those of you that trust people on the Internet, I’ve configured such service on noGoo.me. You can use https://ip.nogoo.me if you like to.