In this tutorial, we’ll talk about procedures for restricting traffic from our system to a single domain.
2. iptables Rules for Traffic Filtering
Firstly, we’ll use the tool iptables because it is widely available. We’ll cover how to implement filtering to allow connections only to a single domain, named domain.com.
2.1. Limitations of the Approach
In general, filtering by domain names is discouraged, and filtering with IP addresses is the preferred approach. In fact, even if we can use iptables filtering based on host or domain names, iptables resolves and converts these domain names to IP addresses (which is what iptables cares about) using a reverse DNS lookup.
iptables doesn’t perform the reverse DNS lookup every time it transfers a package. The reverse DNS lookup is normally done only once before submitting the rule to the kernel and during startup. The underlying reason for not doing the lookup every time is performance: Reverse DNS lookups are expensive operations.
Even if supported, domain filtering is not recommended either by the manual. We can see in the -s parameter section a reference to hostnames: “Please note that specifying any name to be resolved with a remote query such as DNS is a really bad idea”. Let’s consider that the IP of domain.com changes. iptables will block traffic to the IP resolved for domain.com when we set the rule. However, it won’t block traffic to the current IP address of domain.com. If the IP address changes are common, this procedure will fail.
Although it is generally recommended, filtering with IP addresses might also cause trouble with host headers. These headers are complementary to the IP address and port and uniquely identify a web domain. A single server (with a single IP address) can host multiple web domains, and blocking its IP blocks access to all the domains of the server.
Finally, iptables only filters the traffic concerning IPv4. This is not a limitation, since ip6tables exists. However, we should replicate the procedure for both versions of IP.
2.2. Common Problems
There are some common problems that we need to discuss before presenting the procedure. These errors are common and might produce unwanted behavior if we don’t plan accordingly.
First of all, the order in which we add iptables rules matters. For example, to allow traffic only to a single domain, we might start by creating and applying a rule to drop all the packets. Then, we need to retrieve the IP of domain.com with a reverse DNS lookup. However, since there’s a rule to drop all the packets, we won’t be able to perform the lookup or connect to domain.com.
Moreover, iptables rules are applied immediately when working from the command line — although this doesn’t apply if we’re working from scripts. But we should be careful if we type and press the return key in a prompt when connected to a host, through SSH for example. If we instruct a rule to drop the packets, we’ll lose our connection to the host!
Finally, depending on the setup of iptables and our Linux distribution, we might experience problems that cause us to lose our tables. We should always save our new iprules rules so that they are reapplied during booting.
The first step is to allow loopback routing. We should insert (-I) a rule in the INPUT chain located in the first place (with the 1 index). This rule concerns the loopback interface (-i lo) and it should accept all packets matching the rule (-j ACCEPT):
iptables -I INPUT 1 -i lo -j ACCEPT
Then, we need to allow the reverse DNS lookup to get the IP address of the domain. We’ll append (-A) the rule to the OUTPUT chain. We use the User Datagram Protocol (-p udp) with a destination port 53 (–dport 53) since DNS lookups usually occur with this default configuration. In this second rule, we also want to accept all packets matching the rule (-j ACCEPT):
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
The third step is appending (-A) a rule to the OUTPUT chain to accept (-j ACCEPT) packets towards the domain named domain.com (-d domain.com). If we expect that the connections will come through HTTP, we can include common parameters such as the Transmission Control Protocol (-p tcp) and the destination port 80 (–dport 80):
iptables -A OUTPUT -d domain.com -p tcp --dport 80 -j ACCEPT
The fourth rule is appended (-A) to the INPUT chain to allow for incoming packets using connection tracking (-m conntrack). Connection tracking allows us to identify the packets belonging to each network connection. The connection tracking state (–ctstate) is ESTABLISHED and RELATED, meaning that we’ll track packets from connections that have seen packets in both directions and packets starting a connection but associated with an existing connection. We need to accept (-j ACCEPT) these packets:
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
Finally, we should set up the policy to drop packets in both the INPUT and OUTPUT chains. We want to drop all remaining packets for both chains that are outside the rules we’ve just created. Thus, we need to use DROP as the target for both chains:
iptables -P INPUT DROP
iptables -P OUTPUT DROP
2.4. iptables Summary
We can use the -S option to request a summary that contains all the iptables rules we’ve defined. For the INPUT chain, we obtain:
$ iptables -S INPUT
-P INPUT DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
And for the OUTPUT chain, we get:
$ iptables -S OUTPUT
-P OUTPUT DROP
-A OUTPUT -p udp -m udp --dport 53 -j ACCEPT
-A OUTPUT -d 11.15.256.1/32 -p tcp -j ACCEPT
-A OUTPUT -d 11.15.257.1/32 -p tcp -j ACCEPT
Even if we specified domain.com in the arguments of the command call, iptables converts it into IP addresses. Depending on the reverse DNS lookup, we might end up with multiple IP addresses.
3. Alternative Approaches to iptables
In the same way that we’ve used iptables to allow traffic by performing a reverse DNS lookup, we can use a firewall that allows IP filtering rules, like ufw. We can filter all the traffic but the one from our system to a given IP address corresponding to the domain we want to allow the connection to:
$ ufw allow out from any to <domain.ip>
$ ufw reload
We might need to run the previous commands with sudo.
However, as we already discussed, restricting access to certain domains based on the IP address is not optimal — and even less optimal if iptables has to infer this IP address from a reverse DNS lookup. When combined with adequate filtering settings, a web proxy is a more robust approach. We can set up a transparent proxy at the application level, which would better handle domain filtering. This requires a more involved procedure that is beyond the scope of this article, but let’s briefly mention some keywords and tools.
squid is a tool that we can use to set up the proxy with an Access Control List (ACL) to limit the system access to certain sites. We’ll need to force all the web traffic with a Destination Network Address Translation (DNAT) rule to our proxy.
Finally, and depending on the system we’re managing, we can also use a web content filter, such as dansguardian. We would still need DNAT rules to redirect all the web traffic through the filter in a similar way as we did for the proxy.
In this article, we’ve talked about ways to allow traffic from our system to only a single domain. We use the domain name instead of the IP address. We covered iptables, which can handle the task, even though it’s a packet manager. Moreover, we briefly mentioned other tools to achieve the same objective.