Introduction

Fastly is a popular CDN based on the open-source Varnish. Since it supports VCL, a lot of custom “logic” to handle incoming requests can be added, right at the edge. It also provides a host of geolocation identification options.

In this post, we’ll build on the service we created in a previous post Fastly Meta Service to implement a simple API which returns details about a particular public IP.

This service can be nice value add, whether for use by machines (json output helps!) or by non-technical folks to gather useful info tech teams often need to solve/debug “Does not work from my home. I get an error, please fix!” issues.

Solution

The service will rely entirely on Fastly’s infrastructure and will not interact with the configured origin at all! Sadly, you cannot launch a service without any origin at this point, so you would need to add some dummy host. httpbin.org maybe a good choice here!

Such an isolated request flow is possible, thanks to the synthetic responses that can be served directly from Fastly using the vcl constructs.

The request flow will look like this (no requests are sent to the origin) - fastly-geo-service

Geolocation Database

Fastly uses 2 vendors to fetch the geolocation details - Maxmind and Digital Element. This Fastly docs page lists out the different variables that are available for use. Broadly these variables are available under 2 distinct namespaces, each provided by a different vendor. client.geo.* namespace variables are provided by Digital Element, while geoip.* namespace variables are provided by Maxmind.

Note that Fastly has deprecated Maxmind, so any future implementations should only be using the client.geo.* variables as outlined in this post.

API Endpoints

We will build the following endpoints

Request

GET /

Response

 1{
 2    "date": "Timestamp in GMT",
 3    "ipv6": "true|false",
 4    "public_ip": "public ip",
 5    "de_city": "city from DE database",
 6    "de_country": "country from DE database",
 7    "de_continent": "continent from DE database",
 8    "de_proxy" : "proxy type and description from DE database",
 9    "mx_city": "city from Maxmind database",
10    "mx_country": "city from Maxmind database",
11    "mx_continent": "countinent from Maxmind database"
12}

Request

GET /?ip=ip_v4_add or ip_v6_add

Response

 1{
 2    "date": "Timestamp in GMT",
 3    "ipv6": "true|false",
 4    "public_ip": "provided ip_v4_add or ip_v6_add",
 5    "de_city": "city from DE database",
 6    "de_country": "country from DE database",
 7    "de_continent": "continent from DE database",
 8    "de_proxy" : "proxy type and description from DE database",
 9    "mx_city": "city from Maxmind database",
10    "mx_country": "city from Maxmind database",
11    "mx_continent": "countinent from Maxmind database"
12}

Request

GET /random

Response

301 Redirect to /

VCL Snippets

We will modify the vcl snippets recv and error created previously to extend the service and provide more details.

Update the recv snippet with this code -

 1if (!req.url.path ~ "^/$" || !req.http.Fastly-SSL){
 2  error 618;
 3}
 4
 5set req.url = querystring.filter_except(req.url, "ip");
 6
 7if (fastly.ff.visits_this_service == 0 && req.restarts == 0) {
 8  set req.http.Fastly-Client-IP = client.ip;
 9  set req.http.ipv6 = "false";
10  if (req.is_ipv6){
11    set req.http.ipv6 = "true";
12  }
13}
14
15if (req.url.qs ~ "ip"){
16  set req.http.Fastly-Client-IP = querystring.get(req.url, "ip");
17  set client.geo.ip_override = req.http.Fastly-Client-IP;
18  set geoip.ip_override = req.http.Fastly-Client-IP;
19}
20
21error 620;

Update the error snippet with this code -

 1if (obj.status == 618) {
 2    set obj.status = 301;
 3    set obj.response = "Moved Permanently";
 4    set obj.http.Location = "https://" req.http.host "/";
 5    set obj.http.cache-control = "private, no-store, no-cache, max-age=0";
 6    return (deliver);
 7}
 8
 9if (obj.status == 620) {
10    set obj.status = 200;
11    set obj.response = "OK";
12    set obj.http.Content-Type = "application/json";
13    set obj.http.cache-control = "private, no-store, no-cache, max-age=0";
14    synthetic 
15{"{
16    "date": ""} now {"",
17    "ipv6": ""} req.http.ipv6 {"",
18    "public_ip": ""} req.http.Fastly-Client-IP {"",
19    "de_city": ""} client.geo.city {"",
20    "de_country": ""} client.geo.country_code {"",
21    "de_continent": ""} client.geo.continent_code {"",
22    "de_proxy" : ""} client.geo.proxy_type  " - " client.geo.proxy_description {"",
23    "mx_city": ""} geoip.city {"",
24    "mx_country": ""} geoip.country_code {"",
25    "mx_continent": ""} geoip.continent_code {""
26}"};
27    return (deliver);
28}

You can see/test this flow in a fiddle here

Snippets also committed to the repo

Deployment

The service, configurations and vcls can be deployed to Fastly using Terraform. The required Terraform code is similar to what we used in the last post and is available here

Outputs

Once the service is ready, a simple request via curl or browser will return the details like below. I am using the test domain, hence need to use the -k flag (for more uses of curl, you can check this post). For a production level deployment, you would obviously map a custom domain to the service and also use SSL (Fastly provided or self procured).

Let’s check a few curl responses.

Proton Public IP

If you are connected to the ProtonVPN free NL server, this is what you should see. Note the difference of output between the Maxmind and DE outputs. Maxmind says it’s a US IP!

 1$ curl -k https://meta.dane-example.com.global.prod.fastly.net/
 2
 3{
 4    "date": "Fri, 05 Mar 2021 16:23:53 GMT",
 5    "ipv6": "false",
 6    "public_ip": "34.66.60.237",
 7    "de_city": "HUISSEN",
 8    "de_country": "NL",
 9    "de_continent": "EU",
10    "de_proxy" : "HOSTING - VPN",
11    "mx_city": "COUNCIL BLUFFS",
12    "mx_country": "US",
13    "mx_continent": "NA"
14}

Google Public IP

Let’s inspect a public IP, maybe the Google DNS? This time both vendors show the correct country, but Maxmind is off the mark on city.

 1$ curl -k https://meta.dane-example.com.global.prod.fastly.net/?ip=8.8.8.8 
 2
 3{
 4    "date": "Fri, 05 Mar 2021 16:23:53 GMT",
 5    "ipv6": "false",
 6    "public_ip": "8.8.8.8",
 7    "de_city": "MOUNTAIN VIEW",
 8    "de_country": "US",
 9    "de_continent": "NA",
10    "de_proxy" : "ANONYMOUS - ?",
11    "mx_city": "NEW YORK",
12    "mx_country": "US",
13    "mx_continent": "NA"
14}

Public IPv6

Let’s try the IPv6 address now, if that works? Google DNS again. Maxmind doesn’t have any details about IPv6, DE does.

 1$ curl -k https://meta.dane-example.com.global.prod.fastly.net/?ip=2001:4860:4860::8888
 2
 3{
 4    "date": "Fri, 05 Mar 2021 16:23:53 GMT",
 5    "ipv6": "false",
 6    "public_ip": "2001:4860:4860::8888",
 7    "de_city": "MOUNTAIN VIEW",
 8    "de_country": "US",
 9    "de_continent": "NA",
10    "de_proxy" : "? - ?",
11    "mx_city": "",
12    "mx_country": "",
13    "mx_continent": ""
14}

Private IP

What will it return for a private IP? Maxmind is trumped again, however, DE does tell you it’s a private IP. Nice.

 1$ curl -k https://meta.dane-example.com.global.prod.fastly.net/?ip=192.168.1.1
 2
 3{
 4    "date": "Fri, 05 Mar 2021 16:23:53 GMT",
 5    "ipv6": "false",
 6    "public_ip": "192.168.1.1",
 7    "de_city": "PRIVATE",
 8    "de_country": "**",
 9    "de_continent": "**",
10    "de_proxy" : "? - ?",
11    "mx_city": "",
12    "mx_country": "",
13    "mx_continent": ""
14}

What if someone hits a random path?

Since the body doesn’t return anything for redirects, we will either have to follow redirect using -L or inspect the headers to confirm the response.

Let’s do the latter.

1$ curl -k -I https://meta.dane-example.com.global.prod.fastly.net/random
2
3HTTP/2 301 Moved Permanently
4accept-ranges: bytes
5cache-control: private, no-store, no-cache, max-age=0
6content-length: 0
7date: Fri, 05 Mar 2021 16:23:53 GMT
8location: https://meta.dane-example.com.global.prod.fastly.net/
9retry-after: 0

Conclusion

We enhanced the previous basic version considerably, by returning output from both vendors as well as making the response more machine friendly. It is possible to further enhance this service to put out even more information, as needed. It removes any reliance on 3rd party services which may or may not be hosted in secure environments.

The simpler service with only txt output is desribed in a previous post - Fastly Meta Service.

Note:  Code mentioned above is here 

References (1)

  1. Geolocation