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
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
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;
Add custom header with two letter country code returned by VMOD function
sub vcl_recv {
...
set req.http.X-Country-Code = geoip.client_country_code();
...
}
sub vcl_recv {
...
set req.http.X-Country-Code = geoip.country_code("" + client.ip);
...
}
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);
...
}
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");
}
...
}
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)
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);
}
...
}
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
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;
}
}
...
}
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 : )