Wednesday, June 14, 2023

Using Dynamic IP Addresses in firewalld

ipset is your friend

If you want to use dynamic IP addresses (DynDNS) in your firewalld rules, you can use ipsets.
ipset allows you to efficiently manage large or frequently changing lists of IP addresses and use them directly in firewall rules.

The following script creates one ipset per IP-protocol for configured DynDNS hostnames. It automatically creates or updates all ipset entries with the currently resolved IP addresses of the given hostnames.
You can then use the ipset names in your firewalld rules - see the comment in the script.
New: Supports multiple hostnames.

Tested on Rocky 10.
Works on  RHEL, Rocky, Scientific, Fedora, CentOS


/usr/local/bin/firewall_ddns_update.sh:

#!/usr/bin/env bash # Bash script to query for current IP address of given Dynamic-DNS # hosts and create or update firewalld ipset(s). # You are then able to use the ipset in firewalld rules. # Obtain the current ipset(s) with: # firewall-cmd --permanent --get-ipsets # # This script supports IPv4 and IPv6. # # This script is intended to be run from a cron job with root privileges. # The script will only update the ipset if the IP address has changed. #
# Example for using ipset in firewalld rules:
# firewall-cmd --permanent --zone=public --add-source ipset:xyz_V4
# List of DNS names to include DNS_NAMES=("myexampledynhost1.example.com", "myexampledynhost2.example.com") # Zone base-name. Will be prepended with IP-protocol type (_V4/_V6) ZONE="my_ddns_ip_zone" # Single ipset names (shared across all DNS names) IPSET_V4="${ZONE}_4" IPSET_V6="${ZONE}_6" create_ipset() { local setname=$1 local type=$2 if ! firewall-cmd --permanent --get-ipsets | grep -qw "$setname"; then echo "Creating ipset: $setname ($type)" firewall-cmd --permanent --new-ipset="$setname" --type=hash:ip --option=family=$type firewall-cmd --permanent --zone=$ZONE --add-source=ipset:$setname firewall-cmd --reload fi } CHANGED=0 create_ipset "$IPSET_V4" "inet" create_ipset "$IPSET_V6" "inet6" # === resolve all IPs from all DNS names === IP_V4_ALL=() IP_V6_ALL=() for DNS_NAME in "${DNS_NAMES[@]}"; do v4_out=$(dig +short A "$DNS_NAME") if [ $? -eq 0 ]; then IP_V4_ALL+=($v4_out) fi v6_out=$(dig +short AAAA "$DNS_NAME") if [ $? -eq 0 ]; then IP_V6_ALL+=($v6_out) fi done # Remove duplicates and sort IP_V4=$(printf "%s\n" "${IP_V4_ALL[@]}" | sort -u) IP_V6=$(printf "%s\n" "${IP_V6_ALL[@]}" | sort -u) # === IPv4 handling === CURRENT_V4=$(firewall-cmd --permanent --ipset="$IPSET_V4" --get-entries 2>/dev/null) for ip in $CURRENT_V4; do if ! grep -qw "$ip" <<<"$IP_V4"; then echo "Removing outdated IPv4 from $IPSET_V4: $ip" firewall-cmd --permanent --ipset="$IPSET_V4" --remove-entry="$ip" >/dev/null 2>&1 CHANGED=1 fi done for ip in $IP_V4; do if ! grep -qw "$ip" <<<"$CURRENT_V4"; then echo "Adding new IPv4 to $IPSET_V4: $ip" firewall-cmd --permanent --ipset="$IPSET_V4" --add-entry="$ip" >/dev/null 2>&1 CHANGED=1 fi done # === IPv6 handling === CURRENT_V6=$(firewall-cmd --permanent --ipset="$IPSET_V6" --get-entries 2>/dev/null) for ip in $CURRENT_V6; do if ! grep -qw "$ip" <<<"$IP_V6"; then echo "Removing outdated IPv6 from $IPSET_V6: $ip" firewall-cmd --permanent --ipset="$IPSET_V6" --remove-entry="$ip" >/dev/null 2>&1 CHANGED=1 fi done for ip in $IP_V6; do if ! grep -qw "$ip" <<<"$CURRENT_V6"; then echo "Adding new IPv6 to $IPSET_V6: $ip" firewall-cmd --permanent --ipset="$IPSET_V6" --add-entry="$ip" >/dev/null 2>&1 CHANGED=1 fi done if [ $CHANGED -eq 1 ]; then echo "Reloading firewall..." firewall-cmd --reload >/dev/null 2>&1 fi


# end of script.



Add to cron

Create /etc/cron.d/firewall_update_ddns:
# Update DDNS host IPs in firewall ipset(s)

*/1 * * * * root /usr/local/bin/firewall_ddns_update.sh