Self-hosted public IP API

Updated
Tagged
self-hosted

There are various sites and services around the interwebs you can use to find out your public IP address. Both Google and DuckDuckGo (and probably others) return your address when you search for it (try it) but there are times when you may want to find it out via an API, without using a browser, or just for fun.

Two parts are required, DNS resolution, and the API endpoint.

API endpoint

To serve up the response I am using an Nginx service with the following server entry:

server {
	server_name ip.example.com ip6.example.com ip4.example.com;

	location / {
		add_header Cache-Control no-store;

		if ($http_accept = 'application/json') {
			add_header Content-Type application/json;
			return 200 "{ \"ip\": \"$remote_addr\" }";
		}

		default_type text/plain;
		return 200 $remote_addr;
	}

}

The server_name ensures that we can answer requests to the 3 subdomains, one for generic entries which will return IP6 or IP4, and then one for each IP version that will return an appropriate response.

The location block also allows for both a JSON and a plain text response which means a browser will see simple text but a programmatic client can extract JSON. Very handy. Either way the response grabs the built in Nginx variable $remote_addr which will be the public IP address we are after, either IP4 or IP6.

DNS resolution

To resolve the endpoints above we want an entry for each of the 3 server names: ip.example.com, ip4.example.com & ip6.example.com.

The IP version endpoints will need a corresponding A or AAAA record for IP4 and IP6 respectively:

;; BIND zone file extract
ip		CNAME	example.com ;; 
ip4		A	x.x.x.x ;; IP4 address of nginx
ip6		AAAA	x:x:x:x:x:x:x:x ;; IP6 address of nginx

This enables any client to use the generic endpoint ip.example.com and dual-stack clients to use an endpoint other than their default resolution bias. This works because CNAME records are used by both IP4 & IP6 and the ip4.example.com and ip6.example.com records can only be resolved by IP4 and IP6 capably clients respectively.

So now the following is possible (from a dual-stack network):

$ ifconfig |grep inet
	inet6 ::1 prefixlen 128
	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
	inet 127.0.0.1 netmask 0xff000000
	inet6 fe80::2a16:adff:fe76:a485%lagg0 prefixlen 64 scopeid 0x4
	inet6 2001:44b8:dead:beef:2a16:adff:fe76:a485 prefixlen 64 autoconf
	inet 10.0.1.197 netmask 0xffffff00 broadcast 10.0.1.255

$ curl -4 ip.example.com
14.201.108.199

$ curl -6 ip.example.com
2001:44b8:dead:beef:2a16:adff:fe76:a485

$ curl ip4.example.com
14.201.108.199

$ curl -H 'Accept: application/json' ip.example.com
{ "ip": "2001:44b8:dead:beef:2a16:adff:fe76:a485" }