Blocking ads in OpenWrt

Posted April 10, 2018

Last updated October 23, 2021 | b36809d


7 minute read

Here I have collected few scripts to block/manage ads at the source on my travel OpenWrt router, the GL-iNet AR300M. I use this on the travel router to save bandwidth, mostly while the VPN is active so I can save CPU cycles for the OpenVPN process.

All credit goes to tablespoon on GitHub/Tblspn on Reddit. I have just repackaged that script (with a couple of slight modifications to the servers it pulls from and the update schedule) with some other helpful scripts that have come in handy that I thought I’d share on GitLab, and write a bit about them.

The scripts 🔗

adblocker.sh 🔗

First is the script itself to add the core adblocking capabilities. You will want to save this into a file named adblocker.sh. The location will be grabbed when the script is first run, so place it wherever you will be able to keep it for good. I have mine in the ~ directory for root. Highlighted lines are what I have changed from the default.

If you choose to leave in the extra lists that I have added, you will want to make sure you’re able to use SSL/TLS in wget, which I have also posted about.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/bin/sh

# adblocker.sh - by Todd Stein (toddbstein@gmail.com), Saturday, October 25, 2014
# for use on routers running OpenWRT firmware
# updated Monday, August 14, 2017

# Periodically download lists of known ad and malware servers, and prevents traffic from being sent to them.
# This is a complete rewrite of a script originally written by teffalump (https://gist.github.com/teffalump/7227752).

# Forked and customized by oct8l (www.oct8l.com), Tuesday, April 10, 2018

HOST_LISTS="
	http://www.malwaredomainlist.com/hostslist/hosts.txt
    http://malwaredomains.lehigh.edu/files/immortal_domains.txt
	http://www.mvps.org/winhelp2002/hosts.txt
	http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&startdate%5Bday%5D=&startdate%5Bmonth%5D=&star
	https://hosts-file.net/ad_servers.txt
	https://adaway.org/hosts.txt
	https://easylist.to/easylist/fanboy-social.txt
	https://easylist.to/easylist/easyprivacy.txt
	https://easylist.to/easylist/easylist.txt
	https://easylist.to/easylist/fanboy-annoyance.txt
	https://raw.githubusercontent.com/hoshsadiq/adblock-nocoin-list/master/hosts.txt
"

BLOCKLIST=/tmp/adblocker_hostlist
BLACKLIST=/etc/adblocker_blacklist
WHITELIST=/etc/adblocker_whitelist
LOCKFILE=/tmp/adblocker.lock


# ensure this is the only instance running
if ! ln -s $$ "$LOCKFILE" 2>/dev/null; then
	# if the old instance is still running, exit
	former_pid=$(readlink "$LOCKFILE")
	if [ -e "/proc/$former_pid" ]; then
		exit
	else
		# otherwise, update the symlink
		ln -sf $$ "$LOCKFILE"
	fi
fi


# get script's absolute path and quote spaces for safety
cd "${0%/*}"
SCRIPT_NAME="$PWD/${0##*/}"
SCRIPT_NAME="${SCRIPT_NAME// /' '}"
cd "$OLDPWD"


# await internet connectivity before proceeding (in case rc.local executes this script before connectivity is achieved)
until ping -c1 -w3 google.com || ping -c1 -w3 yahoo.com; do
	sleep 5
done &>/dev/null


# grab list of bad domains from the internet - written with the intent of using the least memory possible
IP_REGEX='([0-9]{1,3}\.){3}[0-9]{1,3}'
temp_file1=/tmp/adblocker.tmp1
temp_file2=/tmp/adblocker.tmp2
>"$temp_file1"
# download host lists individually so the rest can still be grabbed if one goes offline
for i in $HOST_LISTS; do
	# download and immediately sanitize to prevent possibility of redirection attack
	wget -qO- $i | awk "/^$IP_REGEX\W/"'{ print "0.0.0.0",$2 }' >>"$temp_file1"
done
# sort by domain
sort -uk2 "$temp_file1" >"$temp_file2"
rm -f "$temp_file1"


# if the download succeeded, overwrite blocklist
if [ -s "$temp_file2" ]; then
	mv -f "$temp_file2" "$BLOCKLIST"
else
	rm -f "$temp_file2"
fi


# add blacklisted domains if any have been specified, ensuring no duplicates are added
if [ -s "$BLACKLIST" ]; then
	# create a pipe-delimited list of all non-commented words in blacklist and remove them from the block list
	black_listed_regex='\W('"$(grep -o '^[^#]\+' "$BLACKLIST" | xargs | tr ' ' '|')"')$'
	sed -ri "/${black_listed_regex//./\.}/d" "$BLOCKLIST"

	# add blacklisted domains to block list
	awk '/^[^#]/ { print "0.0.0.0",$1 }' "$BLACKLIST" >>"$BLOCKLIST"
fi


if [ -s "$BLOCKLIST" ]; then
	# remove any private net IP addresses (just in case)
	# this variable contains a regex which will be used to prevent the blocking of hosts on 192.168.0.0 and 10.0.0.0 networks
	PROTECTED_RANGES='\W(192\.168(\.[0-9]{1,3}){2}|10(\.[0-9]{1,3}){3})$'
	sed -ri "/$PROTECTED_RANGES/d" "$BLOCKLIST"


	# remove any whitelisted domains from the block list
	if [ -s "$WHITELIST" ]; then
		# create a pipe-delimited list of all non-commented words in whitelist and remove them from the block list
		white_listed_regex='\W('"$(grep -Eo '^[^#]+' "$WHITELIST" | xargs | tr ' ' '|')"')$'
		sed -ri "/${white_listed_regex//./\.}/d" "$BLOCKLIST"
	fi


	# add block list to dnsmasq config if it's not already there
	if ! uci -q get dhcp.@dnsmasq[0].addnhosts | grep -q "$BLOCKLIST"; then
		uci add_list dhcp.@dnsmasq[0].addnhosts="$BLOCKLIST" && uci commit
	fi


	# restart dnsmasq service
	/etc/init.d/dnsmasq restart
fi


# carefully add script to /etc/rc.local if it's not already there
if ! grep -Fq "$SCRIPT_NAME" /etc/rc.local; then
	# using awk and cat ensures that no symlinks (if any exist) are clobbered by BusyBox's feature-poor sed.
	awk -v command="$SCRIPT_NAME" '
		! /^exit( 0)?$/ {
			print $0
		}
		/^exit( 0)?$/ {
			print command "\n" $0
			entry_added=1
		}
		END {
			if (entry_added != 1) {
				print command
			}
		}' /etc/rc.local >/tmp/rc.local.new
	cat /tmp/rc.local.new >/etc/rc.local
	rm -f /tmp/rc.local.new
fi


# add script to root's crontab if it's not already there
if ! grep -Fq "$SCRIPT_NAME" /etc/crontabs/root 2>/dev/null; then
	# uses a random minute between 1-59 to prevent undue load on the webservers hosting the lists we pull from
	# unfortunately, there's no $RANDOM in this shell, so:
	DELAY=$(head /dev/urandom | wc -c | /usr/bin/awk "{ print \$1 % 59 }")
	cat >>/etc/crontabs/root <<-:EOF:
		# Download updated ad and malware server lists every day at 8:$(printf "%02d" "$DELAY") PM
		$DELAY 20 * * * $SCRIPT_NAME
	:EOF:
fi

# clean up
rm -f "$LOCKFILE"

Grab from ash with: wget https://gitlab.com/oct8l/openwrt-adblock/raw/7e58680/adblocker.sh

After running this script, you should now be good to go for your router to download blocklists on boot, and every day at a random time between 8:01 PM and 8:59 PM. The reason behind grabbing the list nightly is because I am most likely only going to have it on for a couple days at a time at most, and I want to make sure it is still grabbing new releases even if it isn’t on every Tuesday morning (when the script originally had it updating).

Now for a couple of other scripts I’ve found to come in handy with this travel router, un-ad-block.sh and re-ad-block.sh. Both of these scripts are very basic chains of just a couple ash commands. The first, un-ad-block.sh, will rename the blocklist and force dnsmasq to reload, thus clearing out any blacklisted domains (if there are any issues). re-ad-block.sh will then re-rename the blocklist and reloads dnsmasq so that its contents will be re-added to start blocking again.

For a fun fact, I wrote these on my phone’s SSH client in vi while trying to troubleshoot an issue with connectivity while I was on the go, so that should tell you how basic they are.

un-ad-block.sh 🔗

1
2
3
4
5
6
7
#!/bin/sh

mv /tmp/adblocker_hostlist /tmp/adblocker_hostlist2
/etc/init.d/dnsmasq restart
echo "-------------"
echo "Hosts removed"
echo "-------------"

Grab from ash with: wget https://gitlab.com/oct8l/openwrt-adblock/raw/78edb04/un-ad-block.sh

re-ad-block.sh 🔗

1
2
3
4
5
6
7
#!/bin/sh

mv /tmp/adblocker_hostlist2 /tmp/adblocker_hostlist
/etc/init.d/dnsmasq restart
echo "--------------"
echo "Hosts re-added"
echo "--------------"

Grab from ash with: wget https://gitlab.com/oct8l/openwrt-adblock/raw/78edb04/re-ad-block.sh

Now you should be armed with a mobile adblocking router to take with wherever you go!