8

I want to limit access to a server to certain IPs using iptables but:

  • One of the IPs is dynamic, a normal ISP home connection which changes from time to time.
  • A subdomain e.g. dynamic.example.org is automatically updated when the IP changes using a similar service to dyndns.

Is it possible to have IPtables allow access to a port if dynamic.example.org resolves to that IP?

My current idea is to set up a systemd unit that periodically resolves dynamic.example.org and adjusts iptables accordingly. However, this also requires knowing the old IP address (so storing it somewhere) to remove it from the whitelist.

Is there a simpler way to do this already built in to iptables?

Tom B
  • 195

3 Answers3

7

The way I do this is:

  • Run a script every x minutes from crontab to update an "ipset"
  • Have IPtables use the ipset

Assuming you have only 1 IP address in this ipset, the following script would do:

#!/bin/bash
# Update ipset to let my dynamic IP in
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

set=whitelist host=myhost.dynamic.example.com

me=$(basename "$0")

ip=$(dig +short $host)

if [ -z "$ip" ]; then logger -t "$me" "IP for '$host' not found" exit 1 fi

make sure the set exists

ipset -exist create $set hash:ip

if ipset -q test $set $ip; then logger -t "$me" "IP '$ip' already in set '$set'." else logger -t "$me" "Adding IP '$ip' to set '$set'." ipset flush $set ipset add $set $ip fi

In crontab I call this script every 5 minutes :

*/5 * * * * root /usr/local/bin/ipset-update-dyn

In iptables, the rule using the ipset looks like this :

-A INPUT -p tcp -m set --match-set whitelist src -m state --state NEW -j ACCEPT
mivk
  • 4,924
4

iptables works on IP addresses, not on hostnames. You can use hostnames as arguments, but they will be resolved at the time the command is entered. Doing a DNS lookup for each passing packet would be much too slow.

Your idea to adjust the rules is therefore the only approach. This can be either at a regular schedule, controlled by a program like systemd or cron, or better if you can manage to receive a notification whenever the IP address changes.

You don't have to store the old address, just make an iptables chain for your rule and replace the rule. See the -R option to iptables. To have a rule to replace on the first check, just add a dummy rule so that there will be a rule to replace when the first check runs.

You can also avoid the extra chain and replace a rule at a specific position in INPUT or FORWARD, but that is much more work to maintain, as the position number changes whenever you add or delete rules.

RalfFriedl
  • 3,258
0

Even simpler using dig:

iptables -I INPUT -p tcp -s $(dig +short yourdomain.ddns.net) -m state --state NEW -m tcp -j ACCEPT

Explanation:

The dig +short yourdomain.ddns.net is launched in a subprocess returning your IPv4 IP. The output of this subprocess is passed on to the iptables binary adding a new rule.

Note that we used -I to prepend the rule to all others, if you want to append use -A

iptables -A INPUT -p tcp -s $(dig +short yourdomain.ddns.net) -m state --state NEW -m tcp -j ACCEPT

Update:

As MartinV states, if you keep on adding rules you could end up having a lot of duplicate rules and also reach the rule limit capacity.

The best approach would be to create a chain to hold your DDNS rules and just flush the custom chain before adding your new rule.

Create a custom chain if it doesn't exist, prepend your custom chain myDDNS to the INPUT chain to prevent being blocked by the final general REJECT in the INPUT chain and flush the chain:

iptables -N myDDNS 2>/dev/null;iptables -I INPUT -j myDDNS;iptables -F myDDNS

If the chain exists an error will be raised but the custom chain won't be duplicated. Redirecting the error to /dev/null will prevent the error from being thrown on STDIN.

Prepend your rule to your custom chain:

iptables -I myDDNS -p tcp -s $(dig +short yourdomain.ddns.net) -m state --state NEW -m tcp -j ACCEPT

Just put everything together in a one liner and call the script from the cron every N minutes.

iptables -N myDDNS 2>/dev/null;iptables -I INPUT -j myDDNS;iptables -F myDDNS && iptables -I myDDNS -p tcp -s $(dig +short yourdomain.ddns.net) -m state --state NEW -m tcp -j ACCEPT

Adding && between the iptables -F (flush) and the rule prepending will make sure that the rule is added only if the flush command returns 0

Daniel J.
  • 234