Useful DNS redirect firewall user script

I'm just in the process of upgrading my 15.05 system to 19.07 and copying across all my customisations.

I figured I'd post this script, since some of you may find it useful. It's a firewall user script that redirects any dns queries from internal hosts to the openwrt host. You'll find many android devices, for example, simply ignore the dns settings in the dhcp lease and just use Google's servers, for example.

This can pose a problem if you're using dnsmasq to do clever things like put an IP address into a ipset based on a canonical name DNS query match, which ipset then gets used to do policy based routing through a VPN. If your android devices are not using dnsmasq to resolve DNS queries, then your policy routing won't work.

I also find it undesirable for internal hosts to decide on their own DNS server.

So, to use this script, you'll need to add a line to your firewall config /etc/config/firewall thus:

config include
    option path '/etc/firewall.dns'

Then create the file /etc/firewall.dns and make it executable. Add the following to it

#!/bin/sh
  
# Redirect dns queries to deal with android devices ignoring DHCP DNS settings

IP=/sbin/ip
OPENWRTIPV4=192.168.1.1
SERVERIPV4=192.168.1.2
SERVERIPV6=
SERVERIPV6LL=fe80::aaaa:bbbb:cccc:dddd
OPENWRTIPV6LL=$(${IP} -6 addr show dev br-lan | grep link | grep -m 1 inet | awk '{print $2}' | cut -d/ -f1)

if [ ! -z "${SERVERIPV4}" ]; then

iptables -t nat -D prerouting_rule -i br-lan -s ${SERVERIPV4} -p udp --dport 53 -j RETURN 2> /dev/null
iptables -t nat -D prerouting_rule -i br-lan -s ${SERVERIPV4} -p tcp --dport 53 -j RETURN 2> /dev/null
iptables -t nat -I prerouting_rule 1 -i br-lan -s ${SERVERIPV4} -p udp --dport 53 -j RETURN
iptables -t nat -I prerouting_rule 1 -i br-lan -s ${SERVERIPV4} -p tcp --dport 53 -j RETURN

fi

if [ ! -z "${SERVERIPV6}" ]; then

ip6tables -t nat -D PREROUTING -i br-lan -s ${SERVERIPV6} -p udp --dport 53 -j RETURN 2> /dev/null
ip6tables -t nat -D PREROUTING -i br-lan -s ${SERVERIPV6} -p tcp --dport 53 -j RETURN 2> /dev/null
ip6tables -t nat -I PREROUTING 1 -i br-lan -s ${SERVERIPV6} -p udp --dport 53 -j RETURN
ip6tables -t nat -I PREROUTING 1 -i br-lan -s ${SERVERIPV6} -p tcp --dport 53 -j RETURN

fi

iptables -t nat -D prerouting_rule -i br-lan ! -d ${OPENWRTIPV4} -p udp --dport 53 -j DNAT --to-destination ${OPENWRTIPV4}:53 2> /dev/null
iptables -t nat -D prerouting_rule -i br-lan ! -d ${OPENWRTIPV4} -p tcp --dport 53 -j DNAT --to-destination ${OPENWRTIPV4}:53 2> /dev/null
iptables -t nat -A prerouting_rule -i br-lan ! -d ${OPENWRTIPV4} -p udp --dport 53 -j DNAT --to-destination ${OPENWRTIPV4}:53
iptables -t nat -A prerouting_rule -i br-lan ! -d ${OPENWRTIPV4} -p tcp --dport 53 -j DNAT --to-destination ${OPENWRTIPV4}:53

if [ ! -z "${OPENWRTIPV6LL}" ]; then

ip6tables -t nat -D PREROUTING -i br-lan ! -d fe80::/10 -p udp --dport 53 -j DNAT --to-destination [${OPENWRTIPV6LL}]:53 2> /dev/null
ip6tables -t nat -D PREROUTING -i br-lan ! -d fe80::/10 -p tcp --dport 53 -j DNAT --to-destination [${OPENWRTIPV6LL}]:53 2> /dev/null
ip6tables -t nat -A PREROUTING -i br-lan ! -d fe80::/10 -p udp --dport 53 -j DNAT --to-destination [${OPENWRTIPV6LL}]:53
ip6tables -t nat -A PREROUTING -i br-lan ! -d fe80::/10 -p tcp --dport 53 -j DNAT --to-destination [${OPENWRTIPV6LL}]:53

fi

if [ ! -z "${SERVERIPV6LL}" ]; then

ip6tables -t nat -D PREROUTING -i br-lan -s ${SERVERIPV6LL} -p udp --dport 53 -j RETURN 2> /dev/null
ip6tables -t nat -D PREROUTING -i br-lan -s ${SERVERIPV6LL} -p tcp --dport 53 -j RETURN 2> /dev/null
ip6tables -t nat -I PREROUTING 1 -i br-lan -s ${SERVERIPV6LL} -p udp --dport 53 -j RETURN
ip6tables -t nat -I PREROUTING 1 -i br-lan -s ${SERVERIPV6LL} -p tcp --dport 53 -j RETURN

Some notes:

This script will allow you to have an internal DNS server distinct from the Openwrt dnsmasq. I have mine setup to serve private DNS records and my domain IPs and also to be a fast caching server.

My Openwrt dnsmasq uses my internal server as its primary forwarder. So the rules above exclude redirection of any DNS queries from this host's IPs.

If you don't have an internal DNS server then leave the SERVERIPVx variables blank.

3 Likes

If you want, you can change the OPENWRTIPV4 variable to the following

OPENWRTIPV4=$(uci get network.lan.ipaddr)

which is probably a little more portable

Here's another script for you. This one is my /etc/firewall.badip. It blocks a list of hosts with an ipset. These are typically lists I download of known SSH port probers. I also export them from my elasticsearch logs and update the badip file regularly.

WANIF=$(uci get network.wan.ifname)
RULE=input_wan_rule

[ -f /etc/badip ] && {

    iptables -D ${RULE} -i ${WANIF} -m set --match-set badip src,dst -j DROP 2> /dev/null

    ipset flush badip 2> /dev/null
    ipset destroy badip 2> /dev/null
    ipset create badip hash:net

    for ip in $(cat /etc/badip); do
        ipset add badip $ip
    done

    iptables -I ${RULE} 1 -i ${WANIF} -m set --match-set badip src,dst -j DROP
}

What about something like this?

. /lib/functions/network.sh

IPT=$(which iptables)
IPT6=$(which ip6tables)

###
### DNS Redirect
###

# Redirect DNS Traffic for the following interface(s)
INTERFACES="lan"

# Create custom chain (IPv4)
"${IPT}" -N DNSREDIRECT -t nat > /dev/null 2>&1 \
  || "${IPT}" -F DNSREDIRECT -t nat

"${IPT}" -A DNSREDIRECT -t nat -p udp --dport 53 -j REDIRECT --to-ports 53
"${IPT}" -A DNSREDIRECT -t nat -p tcp --dport 53 -j REDIRECT --to-ports 53
"${IPT}" -A DNSREDIRECT -t nat -j RETURN

# Create custom chain (IPv6)
"${IPT6}" -N DNSREDIRECT -t nat > /dev/null 2>&1 \
  || "${IPT6}" -F DNSREDIRECT -t nat

"${IPT6}" -A DNSREDIRECT -t nat -p udp --dport 53 -j REDIRECT --to-ports 53
"${IPT6}" -A DNSREDIRECT -t nat -p tcp --dport 53 -j REDIRECT --to-ports 53
"${IPT6}" -A DNSREDIRECT -t nat -j RETURN

# Get IPv4 and IPv6 ULA address of each specified interface
for interface in ${INTERFACES}; do
  # Get L3 Device of interface
  network_get_device DEVICE "${interface}"
  [ -z "${DEVICE}" ] && break

  # Get IPv4 Address of interface
  network_get_ipaddr IPV4_ADDR "${interface}"
  # Get all IPv6 Addresses of interface
  network_get_ipaddrs6 IPV6_ADDRS "${interface}"

  # Get ULA IPv6 address of interface
  for ipv6addr in ${IPV6_ADDRS}; do
    if echo "${ipv6addr}" | grep -Eq '^f[cd]'; then
      IPV6_ADDR="${ipv6addr}"
      break
    fi
  done

  if [ -n "${IPV4_ADDR}" ]; then
    "${IPT}" -C PREROUTING -t nat -i "${DEVICE}" ! -d "${IPV4_ADDR}"/32 -j DNSREDIRECT -m comment --comment "Redirect DNS Requests to Router" > /dev/null 2>&1 \
      || "${IPT}" -I PREROUTING 1 -t nat -i "${DEVICE}" ! -d "${IPV4_ADDR}"/32 -j DNSREDIRECT -m comment --comment "Redirect DNS Requests to Router"
  fi

  if [ -n "${IPV6_ADDR}" ]; then
    "${IPT6}" -C PREROUTING -t nat -i "${DEVICE}" ! -d "${IPV6_ADDR}"/128 -j DNSREDIRECT -m comment --comment "Redirect DNS Requests to Router" > /dev/null 2>&1 \
      || "${IPT6}" -I PREROUTING 1 -t nat -i "${DEVICE}" ! -d "${IPV6_ADDR}"/128 -j DNSREDIRECT -m comment --comment "Redirect DNS Requests to Router"
  fi

done

The only problem is, after reboot, it does not work.
But when the firewall is restarted after boot up, it works. :neutral_face:

Yeah, redirect works too. It's really a special case of DNAT where you don't have to enter the interface address of the Openwrt br-lan interface. It's maybe a little more flexible in that it will accomodate an interface IP change unless you put in the uci call as per my second post.

This not getting loaded at boot happens to me as well. It's a bug where fw3 doesn't seem to call the user scripts on a fw3 start. I modified /etc/init.d/firewall to call fw3 restart directly after the call to fw3 start

IPv6 DNAT doesn't work that well for me.

Good to know, thank you!

//edit
Hmm...
But when a firewall restart is triggered via hotplug the rules are also not applied.
I wonder why this is the case?
Manual restart always loads the rules fine.
I tried to add the option reload '1' into /etc/config/firewall but it makes no difference.

//edit
nevermind option reload '1', seems to work fine

hi guys

here's my config
IPV4 : working
PIhole1=10.10.10.9
iptables -t nat -A PREROUTING -i br-lan ! -s $PIhole1 -p tcp --dport 53 -j DNAT --to $PIhole1
iptables -t nat -A PREROUTING -i br-lan ! -s $PIhole1 -p udp --dport 53 -j DNAT --to $PIhole1
iptables -t nat -A POSTROUTING -o br-lan -d $PIhole1 -p tcp --dport 53 -j MASQUERADE
iptables -t nat -A POSTROUTING -o br-lan -d $PIhole1 -p udp --dport 53 -j MASQUERADE

trying to get the same for IPV6 doesn't seem to work
any ideas?

running rpi-4_snapshot_1.9.15-29_r14315
PIhole1IPV6=2a02:a03f:d56f:600:ba27:ebff:fe93:def0
ip6tables -t nat -A PREROUTING -i br-lan ! -s $PIhole1IPV6 -p udp --dport 53 -j DNAT --to $PIhole1IPV6
ip6tables -t nat -A PREROUTING -i br-lan ! -s $PIhole1IPV6 -p tcp --dport 53 -j DNAT --to $PIhole1IPV6
ip6tables -t nat -A POSTROUTING -o br-lan -d $PIhole1IPV6 -p tcp --dport 53 -j MASQUERADE
ip6tables -t nat -A POSTROUTING -o br-lan -d $PIhole1IPV6 -p udp --dport 53 -j M
tx

You want to use ULA and not your GUA as you want to reach a local target.

1 Like

The adblock package has an option to force all DNS queries to the local resolver using iptables rules. It looks quite similar to this, but i wonder if there are any things which could be added to the adblock script. @hnyman, wdyt?

These are all dirty hacks until the main issue is properly fixed:
https://bugs.openwrt.org/index.php?do=details&task_id=500

Moreover, Adblock relies on the locally running resolver which is simpler to redirect to:
https://openwrt.org/docs/guide-user/firewall/fw3_configurations/intercept_dns

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.