Routeable Loopback Addresses

Today we will learn about loopback addresses that can be reached from the outside via routing. This is useful for running services on a router

In a previous post, I talked about the loopback interface and how we can locally bind services to any address in the range 127.0.0.1-127.255.255.254. This is useful if 127.0.0.1 is already in use on a particular port.

The main advantages of loopback addresses are:

  • Adding additional addresses without the need to add extra interfaces
  • Better availability of services, as long as you have a route to the loopback address, the service will stay up.
  • Can hide the presence of services to an attacker probing the addresses of connected interfaces.

Adding a loopback address

sudo ip address add 10.32.16.8 dev lo

Accessing it from the outside

We can't yet ping it from the outside. Why? Because none of the outside hosts have a route to it.

So let's connect to my router and add a route

sudo ip route add to 10.32.16.8 via $DESKTOP_IP

Now any host on my network should be able to reach 10.32.16.8 providing it uses the above router as its default gateway.

Restricting Access

On my router, we can add iptables rules to restrict forwarding to the address.

A simple example will be that only hosts on the eth1 interface can access the loopback address.

sudo iptables -I FORWARD ! -i eth1 -d 10.32.16.8 -j DROP

We can also do this by source address if desired.

What about locking it down on my desktop?

There are a number of ways we may wish to lock it down on my desktop (the host on which I created the loopback address)

First let's create a chain for it.

sudo iptables -N LOOPBACK_ADDR
sudo iptables -I INPUT --dst 10.32.16.8 -j LOOPBACK_ADDR

Give a default action for the chain

sudo iptables -A LOOPBACK_ADDR -j DROP

We can add rules to whitelist traffic to the loopback address

Restrict to eth1 interface

sudo iptables -I LOOPBACK_ADDR -i eth0 -j ACCEPT

Restrict access on the loopback address only to a particular port

sudo iptables -I LOOPBACK_ADDR -p tcp --dport 80 -j ACCEPT

Allow ICMP ping

sudo iptables -I LOOPBACK_ADDR -p icmp --icmp-type echo-request -j ACCEPT

NAT + Docker

I frequently use loopback addresses as the external endpoint for services running inside Docker containers

For example, we want a Ghost blog to be accessible on 10.32.16.8:80

We can use the following command

$ docker run -p 10.32.16.8:80:2368 docker.io/ghost:latest 

We don't want any services on the host itself to be accessible on that address. The rules for redirecting traffic to the docker container are in the nat iptable, which is evaluated before the filter table (which as the name suggests, is for filtering traffic to the host).;

So if we run

$ sudo iptables -I INPUT -d 10.32.16.8 -j DROP 

We will still be able to access the Ghost blog at that address, but won't, for example, be able to access the hosts ssh service on that address.

Better availability of services

Suppose we have a Router with the following interfaces:

lo      127.0.0.1/8
lo      10.53.53.53/32
eth0    10.10.0.1/24
eth1    10.10.1.1/24

We want to run a DNS server on the Router. Normally, we would have to do one of the following:

  • Bind the DNS server to 0.0.0.0 (all interfaces)
  • Have two listening sockets for eth0 and eth1 respectively

Both are kind of disadvantageous. With the first, it's generally bad security to bind to all interfaces, and with the second, with DHCP we will need to give different dns server addresses for each interface.

Instead, we bind the DNS server to the loopback address. Now as long as we are able to access the router, we will be able to access the DNS server at the address 10.53.53.53

There is an additional security benefit here, we can make our DNS server somewhat hidden. A rogue host on the lan may try probing 10.10.0.1 as its the default gateway address, but they won't find the DNS server because it's listening on the loopback address. There is basically no way to know there's a DNS server running unless you know to try 10.53.53.53

Another neat trick

Keeping with the example of the LAN DNS. Suppose we have a DNS server running on a LAN host, as well as a couple of additional DNS servers to serve as backup.

All the DNS servers have a loopback address of 10.53.53.53

We have as follows:

10.10.0.53 - primary dns server
10.10.1.53 - backup dns server

On the Router we do the following

sudo ip route add to 10.53.53.53 via 10.10.0.53 metric 1
sudo ip route add to 10.53.53.53 via 10.10.1.53 metric 2

I wish to perform maintenance on the primary server and thus need to fallback to the backup.

We run the following command on the router

sudo ip route del to 10.53.53.53 via 10.10.0.53 metric 1

Now connections to 10.53.53.53 will route to 10.10.1.53 (the backup server) rather than the primary 10.10.0.53.

The cool thing is I can change the DNS server for my lan without having to tell everyone to change their DNS server

Loopback source addresses

Remember, any destinations you connect to need to have the loopback address inside their routing table.

For this example, we will work on my Router because that is the default gateway on my network (therefore any hosts on my network will already have a route to any loopback address on the router)

Let's create a new loopback address on the router

sudo ip address add 192.0.2.69/32 dev lo

Now we run the following command

sudo ip route change 10.10.0.0/24 src 192.0.2.69

Now whenever the router connects to anything in the network 10.10.0.0/24 its source address will show up as 192.0.2.69

To prove it, on the router, let's connect to the https server on my raspberry pi

telnet 10.10.0.80 443

At the same time, on the RasPi we look at our socket statistics

$ ss -lt -o state established
Recv-Q Send-Q          Local Address:Port     Peer Address:Port   
0      0               10.10.0.80:https       192.0.2.69:51556