Skip to content

Instantly share code, notes, and snippets.

@will
Created April 14, 2023 09:04
Show Gist options
  • Save will/d98519cd7e3d26e4f549994cf0b2e636 to your computer and use it in GitHub Desktop.
Save will/d98519cd7e3d26e4f549994cf0b2e636 to your computer and use it in GitHub Desktop.
iptable rule maker
#!/bin/sh
### Generate a list of iptables rules to redirect port 53 traffic (tcp+udp) and echo to console.
### This version of the script does NOT apply anything to the iptables on the machine it is run on.
### IPv4 DNS traffic is redirected to $PRIMARYDNS_IPV4 on port 53
### IPv6 DNS traffic is redirected to $PRIMARYDNS_IPV6 on port 53
# This is the DNS that you want clients to be forced to use on your network
# If you have a secondary DNS, add it to the whitelist below and it will still work.
PRIMARYDNS_IPV4="192.168.1.12"
# Add ALL your whitelisted DNS servers to this variable, separated by a space
# These are any name servers that will not be rerouted to the primary DNS.
# Recommend you include your router address so that DNS for local machines will still work.
# if left blank (""), PRIMARYDNS_IPV4 will still be whitelisted.
# e.g. "192.168.1.1 192.168.1.2" etc.
DNSWHITELIST_IPV4="192.168.1.12 192.168.1.1"
# Leave this blank if you don't use IPv6.
PRIMARYDNS_IPV6=""
# Add ALL your whitelisted DNS servers to this variable, separated by a space.
# In my experience it is best practice to include every ipv6 address that each
# DNS server uses. I have observed packets sent to my pi-hole by all 3 ipv6 addresses it obtains
# even though only one is listed in DHCP.
# e.g. "ffff::1234 ffff:ffff:ffff:ffff::5678" etc.
DNSWHITELIST_IPV6=""
# Set this to add exception mac addresses that will not be forced to use the primary DNS server
# Separate MAC addresses with spaces.
EXCEPTION_MACS=""
# Set this to the interfaces you want to force through the DNS IP.
# Separate interfaces with spaces.
# on most routers this is br0.
# if you have multiple subnets you might need to add more bridge interfaces to this list.
# e.g. "br0" or "br0 br1" etc.
FORCED_INTFC="br0"
# Enable this if your pihole is on the same subnet as the devices/interfaces
# you are forwarding. This will allow the return traffic to work on the same
# subnet, but will make the pihole think the requests are coming from the router.
# You will lose client information in the pihole dashboard if you enable this.
# It is preferable to put the pihole on a different subnet and disable this.
ENABLE_MASQUERADE=1
###IPV4 SECTION###
#using ipsets to make whitelists lets us limit wasteful rerouting and reduce potential for DNS loops
# create dnsv4 ipset
echo "ipset create dnsv4 hash:ip family inet"
echo "ipset add dnsv4 ${PRIMARYDNS_IPV4}"
if [ -n "${DNSWHITELIST_IPV4}" ]; then
for ip in ${DNSWHITELIST_IPV4}; do
echo "ipset add dnsv4 ${ip}"
done
fi
#create new DNSCONTROL chain with exceptions
echo "iptables -t nat -N DNSCONTROL"
#add exceptions that won't get forcibly routed - their DNS requests get routed based on their internal settings.
if [ -n "${EXCEPTION_MACS}" ]; then
for mac in ${EXCEPTION_MACS}; do
dnscontrol_rule="DNSCONTROL -m mac --mac-source ${mac} -j RETURN"
echo "iptables -t nat -A ${dnscontrol_rule}"
done
fi
#Everything else is DNAT'd into the pihole.
for proto in tcp udp; do
dnscontrol_rule="DNSCONTROL -p ${proto} -j DNAT --to ${PRIMARYDNS_IPV4}:53"
echo "iptables -t nat -A ${dnscontrol_rule}"
done
# rules to push DNS traffic into DNSCONTROL chain
for intfc in ${FORCED_INTFC}; do
for proto in tcp udp; do
prerouting_rule="PREROUTING -i ${intfc} -p ${proto} --dport 53 -m set ! --match-set dnsv4 src -m set ! --match-set dnsv4 dst -j DNSCONTROL"
echo "iptables -t nat -A ${prerouting_rule}"
done
done
#MASQUERADE rules for ipv4
if [ "$ENABLE_MASQUERADE" = "1" ]; then
for proto in udp tcp; do
postrouting_rule="POSTROUTING -p ${proto} --dport 53 -m set ! --match-set dnsv4 src -m set --match-set dnsv4 dst -j MASQUERADE"
echo "iptables -t nat -A ${postrouting_rule}"
done
fi
###IPV6 SECTION###
# This section is skipped entirely if PRIMARYDNS_IPV6 is not set!
if [ -n "${PRIMARYDNS_IPV6}" ]; then
# create dnsv6 ipset
echo "ipset create dnsv6 hash:ip family inet6"
echo "ipset add dnsv6 ${PRIMARYDNS_IPV6}"
if [ -n "${DNSWHITELIST_IPV6}" ]; then
for ip in ${DNSWHITELIST_IPV6}; do
echo "ipset add dnsv6 ${ip}"
done
fi
# create new DNSCONTROL chain with exceptions
echo "ip6tables -t nat -N DNSCONTROL"
# add exceptions that won't get forcibly routed - their DNS requests get routed based on their internal settings.
if [ -n "${EXCEPTION_MACS}" ]; then
for mac in ${EXCEPTION_MACS}; do
dnscontrol_rule="DNSCONTROL -m mac --mac-source ${mac} -j RETURN"
echo "ip6tables -t nat -A ${dnscontrol_rule}"
done
fi
# Everything else is DNAT'd into the pihole.
for proto in tcp udp; do
dnscontrol_rule="DNSCONTROL -p ${proto} -j DNAT --to [${PRIMARYDNS_IPV6}]:53"
echo "ip6tables -t nat -A ${dnscontrol_rule}"
done
# rules to push DNS traffic into DNSCONTROL chain
for intfc in ${FORCED_INTFC}; do
if [ -d "/sys/class/net/${intfc}" ]; then
for proto in tcp udp; do
prerouting_rule="PREROUTING -i ${intfc} -p ${proto} --dport 53 -m set ! --match-set dnsv6 src -m set ! --match-set dnsv6 dst -j DNSCONTROL"
echo "ip6tables -t nat -A ${prerouting_rule}"
done
fi
done
# MASQUERADE rules for ipv4
if [ "$ENABLE_MASQUERADE" = "1" ]; then
for proto in udp tcp; do
postrouting_rule="POSTROUTING -p ${proto} --dport 53 -m set ! --match-set dnsv6 src -m set --match-set dnsv6 dst -j MASQUERADE"
echo "ip6tables -t nat -A ${postrouting_rule}"
done
fi
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment