Skip to content

Instantly share code, notes, and snippets.

@will
Created April 14, 2023 09:04
Show Gist options
  • Select an option

  • Save will/d98519cd7e3d26e4f549994cf0b2e636 to your computer and use it in GitHub Desktop.

Select an option

Save will/d98519cd7e3d26e4f549994cf0b2e636 to your computer and use it in GitHub Desktop.

Revisions

  1. will created this gist Apr 14, 2023.
    134 changes: 134 additions & 0 deletions iptables.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,134 @@
    #!/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