Blog Tenya.me

some blog : )

IP geolocation / GeoIP with Varnish

| Comments

Article history

  • 2013-10-25 :: Creation
  • 2014-04-24 :: Update
    • typo fix
  • 2015-07-02 :: Huge update
    • Added complete installation steps of Varnish (3 and 4 branches) and GeoIP VMOD for popular Linux distros (Debian/Ubuntu/CentOS)
    • VCL examples adopted for Varnish 3.x and 4.x branches)

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

Installation

Varnish 3

click to show

Varnish 4

click to show

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.

1
import geoip;

Examples

Getting country code

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

Varnish 3

1
2
3
4
5
sub vcl_recv {
  ...
  set req.http.X-Country-Code = geoip.client_country_code();
  ...
}

Varnish 4

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

Storing different cache objects per country

Varnish 3

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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-set cookie value

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

Varnish 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 : )

Comments