Varnish Cache Plus

Geolocation (geoip/mmdb)

Two vmods exist to associate IP addresses to geolocation data: vmod_geoip and vmod_mmdb. They use different libraries to read different database formats but with the deprecation of libgeoip, it is now recommended to use vmod_mmdb.

vmod_mmdb

Varnish 6.0

This vmod leverages libmaxminddb to find data associated to an IP and requires a database containing said information. While the libmaxminddb linux packages usually don’t include a database, you can easily find some on the internet (maxmind.com offers some for free, notably).

Example

vcl 4.0;

import mmdb;

backend default { .host = "192.0.2.11"; .port = "8080"; }

# create a database object
sub vcl_init {
	new geodb = mmdb.init("/path/to/db");
}

sub vcl_recv {
	# retrieve the name of the request's origin
	set req.http.Country-Name = geodb.country_name(client.ip);

	# if the country doesn't come from Germany or Belgium, deny access
	if (req.http.Country-Name != "Germany" ||
		req.http.Country-Name != "Belgium") {
		return (synth(403, "Sorry, only available in Germany and Belgium"));
	}

	# for logging purposes, log the geoname_id field of the country
	set req.http.geoname_id = geodb.lookup(client.ip, "country/geoname_id");
}

API

mmdb.init(STRING file)

Create a new database object to search into, using the mmdb database located at file.

STRING .lookup(IP ip, STRING path[, STRING fallback])

Look for ip in the database object. If ip is found, and there’s data associated with it corresponding to path, a string representing that data will be returned. Otherwise, the fallback string is returned (NULL is not specified).

path will depend both on the specific item you are looking for, but also on the particular database you are using as they don’t all contain the same kind of information. For example MaxMind offers three different kinds: City, Countries and ASN.

Here’s a table with the common paths for various items:

data path
country code country/iso_code
country name country/names/en
region code subdivisions/0/iso_code
longitude location/longitude
latitude location/latitude
city name city/names/en
AS Number autonomous_system_number
AS Organization autonomous_system_organization

STRING .country_code(IP ip[, STRING fallback])

This is an alias to call .lookup() with country/iso_code as path.

STRING .country_name(IP ip[, STRING fallback])

This is an alias to call .lookup() with country/names/en as path.

Understanding the path parameter

mmdb databases link IP addresses and CIDR blocks to JSON-like objects, allowing to store diverse information without modifying the database schema. In practice, this means that you can retrieve almost any information in the database if you know its “path”.

A very valuable tool to explore an mmdb file is mmdblookup:

$ mmdblookup -f GeoLite2-Country.mmdb -i 8.8.8.8

  {
    "country":
      {
        "geoname_id":
          6252001 <uint32>
        "iso_code":
          "US" <utf8_string>
        "names":
          {
            "de":
              "USA" <utf8_string>
            "en":
              "United States" <utf8_string>
            "es":
              "Estados Unidos" <utf8_string>
            "fr":
              "États-Unis" <utf8_string>
            "ja":
              "アメリカ合衆国" <utf8_string>
            "pt-BR":
              "Estados Unidos" <utf8_string>
            "ru":
              "США" <utf8_string>
            "zh-CN":
              "美国" <utf8_string>
          }
      }
    "registered_country":
      {
        "geoname_id":
          6252001 <uint32>
        "iso_code":
          "US" <utf8_string>
      }
  }

In this example, the path corresponding to the Russian name of the country would be ould be country/names/ru and would return США.

vmod_geoip

Varnish 4.1 Varnish 6.0

Based on the now deprecated libgeoip, this vmod only offers access to the country name and code associated with an IP since the GeoIP databases lack the flexibility of the MaxMindDB ones. It is however available on older Varnish releases.

Example

vcl 4.0;

import geoip;

backend default { .host = "192.0.2.11"; .port = "8080"; }

sub vcl_recv {
	# retrieve the name of the request's origin
	set req.http.X-Country-Name = geoip.country_name(client.ip);

	# if the country doesn't come from Germany or Belgium, deny access
	if (req.http.X-Country-Name != "Germany" ||
		req.http.X-Country-Name != "Belgium") {
		return (synth(403, "Sorry, only available in Germany and Belgium"));
	}
}

API

STRING country_code(STRING)

Given a IP string, return the corresponding two-letter code of the relevant country.

STRING country_name(STRING)

Same as country_code, but returns the English country name instead of its code.

Availability

vmod_geoip is available in the varnish-plus-vmods-extra package on all 4.1 and 6.0 releases.

Upgrading from vmod_geoip to vmod_mmdb

Switching to vmod_mmdb is very easy, you just need a database in the mmdb format and to modify your VCL like this:

 vcl 4.0;

-import geoip;
+import mmdb;

 backend default { .host = "192.0.2.11"; .port = "8080"; }

+sub vcl_init {
+	# stealing the geoip "namespace"
+	new geoip = mmdb.init("path/to/file.mmdb");
+}
+

 sub vcl_recv {
	# retrieve the name of the request's origin
	set req.http.X-Country-Name = geoip.country_name(client.ip);

	# if the country doesn't come from Germany or Belgium, deny access
	if (req.http.X-Country-Name != "Germany" ||
		req.http.X-Country-Name != "Belgium") {
		return (synth(403, "Sorry, only available in Germany and Belgium"));
	}
}

Since the two API share the .geoip.country_name() and .geoip.country_code() functions, it’s only a matter of naming the mmdb object like the previous vmod’s namespace.