thegitfather
10/30/2013 - 5:53 AM

OpenWRT adblock implementation

OpenWRT adblock implementation

#/etc/white.list

#Add whitelisted addresses, when appropriate, etc.
a248.e.akamai.net
#/etc/sysupgrade.conf
#This file is a list of files that should be preserved through upgrades
#OPTIONAL!!!!!
/etc/passwd
/etc/shadow
...
...
/etc/adblock.sh #ADD THIS LINE
/etc/white.list #AND THIS ONE
/etc/block.hosts #AND THIS ONE
/etc/black.list #AND THIS ONE
#/etc/black.list

#add some server that the list doesn't block
example1.block.com
#!/bin/sh
#Put in /etc/adblock.sh
#Block ads, malware, etc.

#### CONFIG SECTION ####

# Only block wireless ads? Y/N
ONLY_WIRELESS="N"

# IPv6 support? Y/N
IPV6="N"

# Need SSL websites?
SSL="N"

# Try to transparently serve pixel response?
#   If enabled, understand the consequences and mechanics of this setup
TRANS="N"

# Exempt an ip range
EXEMPT="N"
START_RANGE="192.168.1.0"
END_RANGE="192.168.1.255"

# Redirect endpoint
ENDPOINT_IP4="0.0.0.0"
ENDPOINT_IP6="::"

#Change the cron command to what is comfortable, or leave as is
CRON="0 4 * * 0,3 sh /etc/adblock.sh"

#### END CONFIG ####

#### FUNCTIONS ####

cleanup()
{
    #Delete files used to build list to free up the limited space
    echo 'Cleaning up...'
    rm -f /tmp/block.build.list
    rm -f /tmp/block.build.before
}

install_dependencies()
{
    #Need iptables-mod-nat-extra installed
    if opkg list-installed | grep -q iptables-mod-nat-extra
    then
        echo 'iptables-mod-nat-extra is installed!'
    else
        echo 'Updating package list...'
        opkg update > /dev/null
        echo 'Installing iptables-mod-nat-extra...'
        opkg install iptables-mod-nat-extra > /dev/null
    fi

    #Need iptable-mod-iprange for exemption
    if [ "$EXEMPT" = "Y" ]
    then 
        if opkg list-installed | grep -q iptables-mod-iprange
        then
            echo 'iptables-mod-iprange installed'
        else
            echo 'Updating package list...'
            opkg update > /dev/null
            echo 'Installing iptables-mod-iprange...'
            opkg install iptables-mod-iprange > /dev/null
        fi
    fi

    #Need wget for https websites
    if [ "$SSL" = "Y" ]
    then
        if opkg list-installed wget | grep -q wget
        then
            if wget --version | grep -q +ssl
            then
                echo 'wget (with ssl) found'
            else
                # wget without ssl, need to reinstall full wget
                opkg update > /dev/null
                opkg install wget --force-reinstall > /dev/null
            fi
        else
            echo 'Updating package list...'
            opkg update > /dev/null
            echo 'Installing wget (with ssl)...'
            opkg install wget > /dev/null
        fi
    fi
}

add_config()
{
    if [ "$ONLY_WIRELESS" = "Y" ]
    then
        echo 'Wireless only blocking!'
        if [ "$EXEMPT" = "Y" ]
        then
            echo 'Exempting some ips...'
            FW1="iptables -t nat -I PREROUTING -m iprange ! --src-range $START_RANGE-$END_RANGE -i wlan+ -p tcp --dport 53 -j REDIRECT --to-ports 53"
            FW2="iptables -t nat -I PREROUTING -m iprange ! --src-range $START_RANGE-$END_RANGE -i wlan+ -p udp --dport 53 -j REDIRECT --to-ports 53"
        else
            FW1="iptables -t nat -I PREROUTING -i wlan+ -p tcp --dport 53 -j REDIRECT --to-ports 53"
            FW2="iptables -t nat -I PREROUTING -i wlan+ -p udp --dport 53 -j REDIRECT --to-ports 53"
        fi
    else
        if [ "$EXEMPT" = "Y" ]
        then
            echo "Exempting some ips..."
            FW1="iptables -t nat -I PREROUTING -m iprange ! --src-range $START_RANGE-$END_RANGE -p tcp --dport 53 -j REDIRECT --to-ports 53"
            FW2="iptables -t nat -I PREROUTING -m iprange ! --src-range $START_RANGE-$END_RANGE -p udp --dport 53 -j REDIRECT --to-ports 53"
        else
            FW1="iptables -t nat -I PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 53"
            FW2="iptables -t nat -I PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 53"
        fi
    fi

    echo 'Updating config...'

    #Update DHCP config
    uci add_list dhcp.@dnsmasq[0].addnhosts=/etc/block.hosts > /dev/null 2>&1 && uci commit

    #Add to crontab
    echo "$CRON" >> /etc/crontabs/root

    #Update dnsmasq config for Tor
    TOR=`uci get tor.global.enabled 2> /dev/null`
    if [ "$TOR" == "1" ]
    then
        TORPORT=`uci get tor.client.dns_port`
        TORIP="127.0.0.1:$TORPORT"
        uci set dhcp.@dnsmasq[0].noresolv='1' > /dev/null &2>1 && uci commit
        uci add_list dhcp.@dnsmasq[0].server="$TORIP" > /dev/null &2>1 && uci commit
    fi

    # Add firewall rules
    echo "$FW1" >> /etc/firewall.user
    echo "$FW2" >> /etc/firewall.user

    # Provide hint if localservice is 1
    LS=`uci get dhcp.@dnsmasq[0].localservice 2> /dev/null`
    if [ "$LS" == "1" ]
    then
        echo "HINT: localservice is set to 1"
        echo "    Adblocking (and router DNS) over a VPN may not work"
        echo "    To allow VPN router DNS, manually set localservice to 0"
    fi


    # Determining uhttpd/httpd_gargoyle for transparent pixel support
    if [ "$TRANS" = "Y" ]
    then
        if [ ! -e "/www/1.gif" ]
        then
            /usr/bin/wget -O /www/1.gif http://upload.wikimedia.org/wikipedia/commons/c/ce/Transparent.gif  > /dev/null
        fi
        if [ -s "/usr/sbin/uhttpd" ]
        then
            #The default is none, so I don't want to check for it, so just write it
            echo "uhttpd found..."
            echo "updating server error page to return transparent pixel..."
            uci set uhttpd.main.error_page="/1.gif" && uci commit
        elif [ -s "/usr/sbin/httpd_gargoyle" ]
        then
            # Write without testing
            echo "httpd_gargoyle found..."
            echo "updating server error page to return transparent pixel..."
            uci set httpd_gargoyle.server.page_not_found_file="1.gif" && uci commit
        else
            echo "Cannot find supported web server..."
        fi
    fi
}

update_blocklist()
{
    #Delete the old block.hosts to make room for the updates
    rm -f /etc/block.hosts

    # Correct endpoint for transparent pixel response
    if [ "$TRANS" = "Y" ] && [ -e "/www/1.gif" ] && ([ -s "/usr/sbin/uhttpd" ] || [ -s "/usr/sbin/httpd_gargoyle" ])
    then
        ENDPOINT_IP4=$(uci get network.lan.ipaddr)
        if [ "$IPV6" = "Y" ]
        then
            ENDPOINT_IP6=$(uci get network.lan6.ipaddr)
        fi
    fi
    
    echo 'Downloading hosts lists...'
    #Download and process the files needed to make the lists (enable/add more, if you want)
    wget -qO- http://www.mvps.org/winhelp2002/hosts.txt| awk -v r="$ENDPOINT_IP4" '{sub(/^0.0.0.0/, r)} $0 ~ "^"r' > /tmp/block.build.list
    wget -qO- "http://adaway.org/hosts.txt"|awk -v r="$ENDPOINT_IP4" '{sub(/^127.0.0.1/, r)} $0 ~ "^"r' >> /tmp/block.build.list
    #wget -qO- http://www.malwaredomainlist.com/hostslist/hosts.txt|awk -v r="$ENDPOINT_IP4" '{sub(/^127.0.0.1/, r)} $0 ~ "^"r' >> /tmp/block.build.list
    #wget -qO- "http://hosts-file.net/.\ad_servers.txt"|awk -v r="$ENDPOINT_IP4" '{sub(/^127.0.0.1/, r)} $0 ~ "^"r' >> /tmp/block.build.list

    #Add black list, if non-empty
    if [ -s "/etc/black.list" ]
    then
        echo 'Adding blacklist...'
        awk -v r="$ENDPOINT_IP4" '/^[^#]/ { print r,$1 }' /etc/black.list >> /tmp/block.build.list
    fi

    echo 'Sorting lists...'

    #Sort the download/black lists
    awk '{sub(/\r$/,"");print $1,$2}' /tmp/block.build.list|sort -u > /tmp/block.build.before

    #Filter (if applicable)
    if [ -s "/etc/white.list" ]
    then
        #Filter the blacklist, supressing whitelist matches
        #  This is relatively slow =-(
        echo 'Filtering white list...'
        egrep -v "^[[:space:]]*$" /etc/white.list | awk '/^[^#]/ {sub(/\r$/,"");print $1}' | grep -vf - /tmp/block.build.before > /etc/block.hosts
    else
        cat /tmp/block.build.before > /etc/block.hosts
    fi

    if [ "$IPV6" = "Y" ]
    then
        safe_pattern=$(printf '%s\n' "$ENDPOINT_IP4" | sed 's/[[\.*^$(){}?+|/]/\\&/g')
        safe_addition=$(printf '%s\n' "$ENDPOINT_IP6" | sed 's/[\&/]/\\&/g')
        echo 'Adding ipv6 support...'
        sed -i -re "s/^(${safe_pattern}) (.*)$/\1 \2\n${safe_addition} \2/g" /etc/block.hosts
    fi
}

restart_firewall()
{
    echo 'Restarting firewall...'
    if [ -s "/usr/lib/gargoyle/restart_firewall.sh" ]
    then
        /usr/lib/gargoyle/restart_firewall.sh > /dev/null 2>&1
    else
        /etc/init.d/firewall restart > /dev/null 2>&1
    fi
}

restart_dnsmasq()
{
    if [ "$1" -eq "0" ]
    then
        echo 'Re-reading blocklist'
        killall -HUP dnsmasq
    else
        echo 'Restarting dnsmasq...'
        /etc/init.d/dnsmasq restart
    fi
}

restart_http()
{
    if [ -s "/usr/sbin/uhttpd" ]
    then
        echo 'Restarting uhttpd...'
        /etc/init.d/uhttpd restart
    elif [ -s "/usr/sbin/httpd_gargoyle" ]
    then
        echo 'Restarting httpd_gargoyle...'
        /etc/init.d/httpd_gargoyle restart
    fi
}
restart_cron()
{
    echo 'Restarting cron...'
    /etc/init.d/cron restart > /dev/null 2>&1
}

remove_config()
{
    echo 'Reverting config...'

    # Remove addnhosts
    uci del_list dhcp.@dnsmasq[0].addnhosts=/etc/block.hosts > /dev/null 2>&1 && uci commit

    # Remove cron entry
    sed -i '/adblock/d' /etc/crontabs/root

    # Remove firewall rules
    sed -i '/--to-ports 53/d' /etc/firewall.user
    
    # Remove Tor workarounds
    uci del_list dhcp.@dnsmasq[0].server > /dev/null 2>&1 && uci commit
    uci set dhcp.@dnsmasq[0].noresolv='0' > /dev/null 2>&1 && uci commit

    # Remove proxying
    uci delete uhttpd.main.error_page > /dev/null 2>&1 && uci commit
    uci set httpd_gargoyle.server.page_not_found_file="login.sh" > /dev/null 2>&1 && uci commit
}


toggle()
{
    # Check for cron as test for on/off
    if grep -q "adblock" /etc/crontabs/root
    then
        # Turn off
        echo 'Turning off!'
        remove_config
    else
        # Turn on
        echo 'Turning on!'
        add_config
    fi

    # Restart services
    restart_firewall
    restart_dnsmasq 1
    restart_http
    restart_cron
}

#### END FUNCTIONS ####

### Options parsing ####

case "$1" in
    # Toggle on/off
    "-t")
        toggle
        ;;
    #First time run
    "-f")
        install_dependencies
        add_config
        update_blocklist
        restart_firewall
        restart_dnsmasq 1
        restart_http
        restart_cron
        cleanup
        ;;
    #Reinstall
    "-r")
        remove_config
        install_dependencies
        add_config
        update_blocklist
        restart_firewall
        restart_dnsmasq 1
        restart_http
        restart_cron
        cleanup
        ;;
    #Default updates blocklist only
    *)
        update_blocklist
        restart_dnsmasq 0
        cleanup
        ;;
esac

#### END OPTIONS ####

exit 0

Others have recently developed packages for this same functionality, and done it better than anything I could do. Use the packages instead of this script:

Description

In its basic usage, this script will modify the router such that blocked addresses are null routed and unreachable. Since the address blocklist is full of advertising, malware, and tracking servers, this setup is generally a good thing. In addition, the router will update the blocklist weekly. However, the blocking is leaky, so do not expect everything to be blocked.

Setup

The script must be copied to an OpenWRT router (gargoyle firmware works fine, too).

For example, if the router is located at 192.168.1.1:

# scp adblock.sh root@192.168.1.1:/etc/adblock.sh

Make the script executable:

# chmod +x /etc/adblock.sh

Basic usage

If you are running the script for the first time:

# sh /etc/adblock.sh -f

There should be status updates in the output, but there should be no errors. If these commands complete without errors, the adblocking is active. You can test it by looking up, say, google analytics.

Whitelists and blacklists

The script supports defining whitelisted urls. That is, urls that will be filtered out of the downloaded blocklists. To whitelist urls, place them (one per line) in /etc/white.list.

Similarly, the script supports defining blacklisted urls - urls that will be added to the downloaded blocklists. To blacklist urls, place them (one per line) in /etc/black.list.

NOTE: The whitelist support is pretty stupid, so don't expect smart filtering (e.g., domain extrapolation). I've found it tedious, but worthwhile, to find the offending url in /etc/block.hosts and copy it to /etc/white.list.

Advanced usage

Toggle on and off

To toggle the blocking on and off, run the script with the -t switch:

# sh /etc/adblock.sh -t

Note: This does not delete the blocklist, whitelist, or blacklist.

Manually update blocklist

To manually update the blocklist, run the script without switches:

# sh /etc/adblock.sh

Full reinstall

To reinstall the current implementation:

# sh /etc/adblock.sh -r

Configuration

The config section of the script has some variables that alter the behaviour of the script.

For example, if you change:

ONLY_WIRELESS="N"

to

ONLY_WIRELESS="Y"

Then only the wireless interface of the router will filter the blocklist.

To change the configuration of an already active installation, I would toggle the adblocking off first, change the script, then toggle it back on. That is,

# sh /etc/adblock.sh -t # turn off
...modify variables...
# sh /etc/adblock.sh -t # turn on

However, if you change certain variables, you must re-update the blocklist because the redirection values will have changed. Other variables require a full restart because they must install or verify dependencies.

Configurable variables:

  • ONLY_WIRELESS (Y/N): Only filter on wireless interface
  • EXEMPT# (Y/N): Exempt ip range from filtering (between START_ RANGE and END_RANGE)
  • IPV6*: (Y/N): Add IPv6 support
  • SSL# (Y/N): Install wget with ssl support (only needed for ssl websites)
  • TRANS#: (Y/N): Modify router web server to server transparent pixel responses for blocked websites
  • ENDPOINT_IP4/IP6*: Define the IP to return for blocked hostnames (IPv4 and IPv6)
  • CRON: The cron line to put in the crontab

*: Requires blocklist update.

: Requires full reinstall