some blog : )

IP geolocation / GeoIP with Varnish

Posted at — Oct 25, 2013

Overview

Often we need to determine clients IP location and process some actions before sending request to backend with application.

Varnish itself has no built-in functionality for this task, but can be added with VMOD.

Thanks to Open Source community - GeoIP functionality has been already realized by various people in different GeoIP VMODs.

This post will explain how to build one of the GeoIP VMODs and show few usage examples

Article history

Installation

Varnish 3

click to show

Varnish 4

click to show

VMOD installation could be verified with build of simple VCL

Put this code into ~/src/test.vcl

vcl 4.0;
backend default {
  .host = "127.0.0.1";
  .port = "8080";
}
import geoip;

Run command

varnishd -C -f ~/src/test.vcl

If no error raised, then you successfully installed GeoIP VMOD

Usage

You will be able to use VMOD functions after libvmod-geoip installation, such as determining client country code or country name

Description of functions available on libvmod-geoip GitHub page https://github.com/varnish/libvmod-geoip

You need to import module to be able to use its functions Insert import line to your vcl file before include and subroutines declarations.

import geoip;

Examples

Getting country code

Add custom header with two letter country code returned by VMOD function

Varnish 3

sub vcl_recv {
	...
	set req.http.X-Country-Code = geoip.client_country_code();
	...
}

Varnish 4

sub vcl_recv {
	...
	set req.http.X-Country-Code = geoip.country_code("" + client.ip);
	...
}

Storing different cache objects per country

Varnish 3

import std;

sub vcl_recv {
	...
	set req.http.X-Country-Code = geoip.client_country_code();
	...
}

sub vcl_hash {
	...
	hash_data(req.http.X-Country-Code);
	...
}

Varnish 4

import std;

sub vcl_recv {
	...
	set req.http.X-Country-Code = geoip.country_code("" + client.ip);
	...
}

sub vcl_hash {
	...
	hash_data(req.http.X-Country-Code);
	...
}

Or, for example, separate caches for Europe countries and all other world

Here I’m using regular expression to determine Europe country

import std;

sub vcl_hash {
	...
	if (req.http.X-Country-Code ~ "(AL|AD|AZ|AT|AM|BE|BA|BG|BY|HR|CY|CZ|DK|EE|FO|FI|AX|FR|GE|DE|GI|GR|VA|HU|IS|IE|IT|KZ|LV|LI|LT|LU|MT|MC|MD|ME|NL|NO|PL|PT|RO|RU|SM|RS|SK|SI|ES|SJ|SE|CH|TR|UA|MK|GB|GG|JE|IM)")
	{
		hash_data("europe");
	}
	...
}

Redirect users to separate two-letter language domain

GeoIP returns two letters only and redirect to domains with more letters will require more complicated logic

Requires import of libvmod-std (built-in Varnish VMOD)

Varnish 3

import std;
import geoip;

sub vcl_recv {
...
	# Extract country code
	set req.http.X-Country-Code = geoip.client_country_code();
	# Normalize country code to lower case
	set req.http.X-Country-Code = std.tolower(req.http.X-Country-Code);

	# do redirect for www.example.com host and French, German and Russian people
	if (req.http.host == "www.example.com" && req.http.X-Country-Code ~ "(fr|de|ru)" ) {
		# use dummy error code for our redirect
		error 750 "Moved Temporarily";
	}
...
}

sub vcl_error {
...
	# Describe what Varnish should do with this dummy code
	if (obj.status == 750) {
		# Send location header to browser with new country host
		set obj.http.Location = "http://" + req.http.X-Country-Code + ".example.com";
		set obj.status = 302;
		return(deliver);
	}
...
}

Varnish 4

import std;
import geoip;

sub vcl_recv {
...
	# Extract country code
	set req.http.X-Country-Code = geoip.country_code("" + client.ip);
	# Normalize country code to lower case
	set req.http.X-Country-Code = std.tolower(req.http.X-Country-Code);

	# do redirect for www.example.com host and French, German and Russian people
	if (req.http.host == "www.example.com" && req.http.X-Country-Code ~ "(fr|de|ru)" ) {
		# use dummy error code for our redirect
		return(synth(750, "Moved Temporarily"));
	}
...
}

sub vcl_synth {
...
	# Describe what Varnish should do with this dummy code
	if (resp.status == 750) {
		# Send location header to browser with new country host
		set resp.http.Location = "http://" + req.http.X-Country-Code + ".example.com";
		set resp.status = 302;
		return(deliver);
	}
...
}

Pre-setting cookie value may be used to defining some default values on the page for users visiting your site first time

Varnish 3

import geoip;

sub vcl_recv {
...

	# Check if our magic cookie not present in request
	if (req.http.Cookie !~ "MagicCookie") {
  		# Extract country code
  		set req.http.X-Country-Code = geoip.client_country_code();

  		# Set some value for United Kingdom and Ireland
  		if (req.http.X-Country-Code ~ "(GB|IE)") {
  			set req.http.Cookie = "MagicCookie=XXX;" + req.http.Cookie;
  		}
  		# Some other value for Denmark, Finland, Norway and Sweden
  		elsif (req.http.X-Country-Code ~ "(DK|FI|NO|SE)") {
  			set req.http.Cookie = "MagicCookie=YYY;" + req.http.Cookie;
  		}
  		# And something completely different for all other world
  		else {
  			set req.http.Cookie = "MagicCookie=ZZZ;" + req.http.Cookie;
  		}
	}

...
}

Varnish 4

import geoip;

sub vcl_recv {
...

	# Check if our magic cookie not present in request
	if (req.http.Cookie !~ "MagicCookie") {
  		# Extract country code
  		set req.http.X-Country-Code = geoip.country_code("" + client.ip);

  		# Set some value for United Kingdom and Ireland
  		if (req.http.X-Country-Code ~ "(GB|IE)") {
  			set req.http.Cookie = "MagicCookie=XXX;" + req.http.Cookie;
  		}
  		# Some other value for Denmark, Finland, Norway and Sweden
  		elsif (req.http.X-Country-Code ~ "(DK|FI|NO|SE)") {
  			set req.http.Cookie = "MagicCookie=YYY;" + req.http.Cookie;
  		}
  		# And something completely different for all other world
  		else {
  			set req.http.Cookie = "MagicCookie=ZZZ;" + req.http.Cookie;
  		}
	}

...
}

Country codes may be taken from MaxMind site http://dev.maxmind.com/geoip/legacy/codes/iso3166/

If you know some other example, write few words to me and I will add it to article : )