What's new

Tutorial Forcing SafeSearch Tutorial

  • SNBForums Code of Conduct

    SNBForums is a community for everyone, no matter what their level of experience.

    Please be tolerant and patient of others, especially newcomers. We are all here to share and learn!

    The rules are simple: Be patient, be nice, be helpful or be gone!

SomeWhereOverTheRainBow

Part of the Furniture
There are many approaches to this topic and RMerlin's Wiki https://github.com/RMerl/asuswrt-merlin.ng/wiki/Enforce-Safesearch
-briefly delves on the concept using dnsmasq. Instead of reinventing the wheel over and over again with this topic, below is a small script and instructions for users that wish to explore this topic.
simply copy and paste the below script into SSH terminal + Press Enter and everything is taken care of. Before testing it is important to clear/refresh any browsers and/or cache on test devices.

To reduce risk IP changes this script is adapted for using fresh IP addresses:
Code:
touch enforcesafe.sh && cat > "enforcesafe.sh" <<'EOF'
#!/bin/sh
URL="https://www.google.com/supported_domains"
FILE="/jffs/configs/dnsmasq.conf.add"

[ ! -f "$FILE" ] && touch "$FILE"

f_nslookup() {
    local DOMAIN="$1"
    nslookup "${DOMAIN}" 1.1.1.1 2>/dev/null | awk '/^Address[[:space:]][0-9]*\:[[:space:]]/{if($3 ~ /((((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])\.){3}(25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\/(1?[0-9]|2?[0-9]|3?[0-2]))?)|(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:{2}(\/(1?[0-2][0-8]|[0-9][0-9]))?))/ && !/1\.1\.1\.1/)print $3}' | while read -r line; do { if [ "${line%%.*}" = "0" ] || [ -z "${line%%::*}" ]; then continue; elif [ "${line##*:}" = "${line}" ]; then printf "%s " "$line"; else printf "%s " "$line"; fi; }; done
}

{
printf "%s\n" "# Enforced Safe Search:"
DOMAINS="$(curl $URL 2>/dev/null)"
for DOMAIN in $DOMAINS; do
    DOMAIN="$(echo $DOMAIN | cut -c 2-)"
    printf "%s\n" "local=/www.${DOMAIN}/" \
              "cname=www.${DOMAIN},forcesafesearch.google.com"
done
#this version uses restrictmoderate.youtube.com
for DOMAIN in youtube; do
    printf "%s\n" "local=/www.${DOMAIN}.com/" \
              "cname=www.${DOMAIN}.com,restrictmoderate.${DOMAIN}.com" \
              "local=/m.${DOMAIN}.com/" \
              "cname=m.${DOMAIN}.com,restrictmoderate.${DOMAIN}.com" \
              "local=/${DOMAIN}i.googleapis.com/" \
              "cname=${DOMAIN}i.googleapis.com,restrictmoderate.${DOMAIN}.com" \
              "local=/${DOMAIN}.googleapis.com/" \
              "cname=${DOMAIN}.googleapis.com,restrictmoderate.${DOMAIN}.com" \
              "local=/www.${DOMAIN}-nocookie.com/" \
              "cname=www.${DOMAIN}-nocookie.com,restrictmoderate.${DOMAIN}.com"
done
for DOMAIN in bing.com; do
    printf "%s\n" "local=/${DOMAIN}/www.${DOMAIN}/" \
              "cname=${DOMAIN},www.${DOMAIN},strict.${DOMAIN}"
done
for DOMAIN in pixabay.com; do
    printf "%s\n" "local=/${DOMAIN}/" \
              "cname=${DOMAIN},safesearch.${DOMAIN}"
done
for DOMAIN in duckduckgo.com; do
    printf "%s\n" "local=/${DOMAIN}/www.${DOMAIN}/" \
              "cname=${DOMAIN},www.${DOMAIN},start.${DOMAIN},safe.${DOMAIN}" \
              "local=/duck.com/www.duck.com/" \
              "cname=duck.com,www.duck.com,safe.${DOMAIN}"
done
for DOMAIN in qwant.com; do
    printf "%s\n" "local=/${DOMAIN}/www.${DOMAIN}/api.${DOMAIN}/" \
              "cname=${DOMAIN},www.${DOMAIN},api.${DOMAIN},safeapi.${DOMAIN}" \
              "local=/s1.${DOMAIN}/s2.${DOMAIN}/" \
              "cname=s1.${DOMAIN},s2.${DOMAIN},safeapi.${DOMAIN}"
done
YANDEX="com ru ua by kz"
for DOMAIN in $YANDEX; do
    printf "%s\n" "local=/yandex.${DOMAIN}/www.yandex.${DOMAIN}/" \
              "cname=yandex.${DOMAIN},www.yandex.${DOMAIN},familysearch.yandex.ru"
done
for DOMAIN in forcesafesearch.google.com safe.duckduckgo.com restrictmoderate.youtube.com strict.bing.com safesearch.pixabay.com safeapi.qwant.com familysearch.yandex.ru; do
    for IPS in $(f_nslookup $DOMAIN); do
        if [ "$DOMAIN" = "forcesafesearch.google.com" ]; then
            if [ "${IPS##*:}" = "${IPS}" ]; then
                printf "%s\n" "host-record=${DOMAIN},restrict.youtube.com,${IPS},::ffff:${IPS}"
            else
                printf "%s\n" "host-record=${DOMAIN},restrict.youtube.com,${IPS}"
            fi
        else
            if [ "${IPS##*:}" = "${IPS}" ]; then
                printf "%s\n" "host-record=${DOMAIN},${IPS},::ffff:${IPS}"
            else
                printf "%s\n" "host-record=${DOMAIN},${IPS}"
            fi
        fi
    done
done
printf "%s\n" "# End of Enforced Safe Search #"
} >> "${FILE}"
service restart_dnsmasq >/dev/null 2>&1
EOF
sh enforcesafe.sh && rm -rf enforcesafe.sh
On a side note for users that want a restrict.youtube.com instead of a lower level restrictmoderate.youtube.com:
Code:
touch enforcesafe.sh && cat > "enforcesafe.sh" <<'EOF'
#!/bin/sh
URL="https://www.google.com/supported_domains"
FILE="/jffs/configs/dnsmasq.conf.add"

[ ! -f "$FILE" ] && touch "$FILE"

f_nslookup() {
    local DOMAIN="$1"
    nslookup "${DOMAIN}" 1.1.1.1 2>/dev/null | awk '/^Address[[:space:]][0-9]*\:[[:space:]]/{if($3 ~ /((((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])\.){3}(25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\/(1?[0-9]|2?[0-9]|3?[0-2]))?)|(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:{2}(\/(1?[0-2][0-8]|[0-9][0-9]))?))/ && !/1\.1\.1\.1/)print $3}' | while read -r line; do { if [ "${line%%.*}" = "0" ] || [ -z "${line%%::*}" ]; then continue; elif [ "${line##*:}" = "${line}" ]; then printf "%s " "$line"; else printf "%s " "$line"; fi; }; done
}

{
printf "%s\n" "# Enforced Safe Search:"
DOMAINS="$(curl $URL 2>/dev/null)"
for DOMAIN in $DOMAINS; do
    DOMAIN="$(echo $DOMAIN | cut -c 2-)"
    printf "%s\n" "local=/www.${DOMAIN}/" \
              "cname=www.${DOMAIN},forcesafesearch.google.com"
done
#this version uses restrict.youtube.com
for DOMAIN in youtube; do
    printf "%s\n" "local=/www.${DOMAIN}.com/" \
              "cname=www.${DOMAIN}.com,restrict.${DOMAIN}.com" \
              "local=/m.${DOMAIN}.com/" \
              "cname=m.${DOMAIN}.com,restrict.${DOMAIN}.com" \
              "local=/${DOMAIN}i.googleapis.com/" \
              "cname=${DOMAIN}i.googleapis.com,restrict.${DOMAIN}.com" \
              "local=/${DOMAIN}.googleapis.com/" \
              "cname=${DOMAIN}.googleapis.com,restrict.${DOMAIN}.com" \
              "local=/www.${DOMAIN}-nocookie.com/" \
              "cname=www.${DOMAIN}-nocookie.com,restrict.${DOMAIN}.com"
done
for DOMAIN in bing.com; do
    printf "%s\n" "local=/${DOMAIN}/www.${DOMAIN}/" \
              "cname=${DOMAIN},www.${DOMAIN},strict.${DOMAIN}"
done
for DOMAIN in pixabay.com; do
    printf "%s\n" "local=/${DOMAIN}/" \
              "cname=${DOMAIN},safesearch.${DOMAIN}"
done
for DOMAIN in duckduckgo.com; do
    printf "%s\n" "local=/${DOMAIN}/www.${DOMAIN}/" \
              "cname=${DOMAIN},www.${DOMAIN},start.${DOMAIN},safe.${DOMAIN}" \
              "local=/duck.com/www.duck.com/" \
              "cname=duck.com,www.duck.com,safe.${DOMAIN}"
done
for DOMAIN in qwant.com; do
    printf "%s\n" "local=/${DOMAIN}/www.${DOMAIN}/api.${DOMAIN}/" \
              "cname=${DOMAIN},www.${DOMAIN},api.${DOMAIN},safeapi.${DOMAIN}" \
              "local=/s1.${DOMAIN}/s2.${DOMAIN}/" \
              "cname=s1.${DOMAIN},s2.${DOMAIN},safeapi.${DOMAIN}"
done
YANDEX="com ru ua by kz"
for DOMAIN in $YANDEX; do
    printf "%s\n" "local=/yandex.${DOMAIN}/www.yandex.${DOMAIN}/" \
              "cname=yandex.${DOMAIN},www.yandex.${DOMAIN},familysearch.yandex.ru"
done
for DOMAIN in forcesafesearch.google.com safe.duckduckgo.com restrictmoderate.youtube.com strict.bing.com safesearch.pixabay.com safeapi.qwant.com familysearch.yandex.ru; do
    for IPS in $(f_nslookup $DOMAIN); do
        if [ "$DOMAIN" = "forcesafesearch.google.com" ]; then
            if [ "${IPS##*:}" = "${IPS}" ]; then
                printf "%s\n" "host-record=${DOMAIN},restrict.youtube.com,${IPS},::ffff:${IPS}"
            else
                printf "%s\n" "host-record=${DOMAIN},restrict.youtube.com,${IPS}"
            fi
        else
            if [ "${IPS##*:}" = "${IPS}" ]; then
                printf "%s\n" "host-record=${DOMAIN},${IPS},::ffff:${IPS}"
            else
                printf "%s\n" "host-record=${DOMAIN},${IPS}"
            fi
        fi
    done
done
printf "%s\n" "# End of Enforced Safe Search #"
} >> "${FILE}"
service restart_dnsmasq >/dev/null 2>&1
EOF
sh enforcesafe.sh && rm -rf enforcesafe.sh
Results:
Code:
#########################################################
/tmp/home/root# dig www.google.com @192.168.1.1

; <<>> DiG 9.16.8 <<>> www.google.com @192.168.1.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26812
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1280
;; QUESTION SECTION:
;www.google.com.                        IN      A

;; ANSWER SECTION:
www.google.com.         0       IN      CNAME   forcesafesearch.google.com.
forcesafesearch.google.com. 0   IN      A       216.239.38.120

;; Query time: 1 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: Sun Jan 31 21:32:53 EST 2021
;; MSG SIZE  rcvd: 99
#########################################################
 
Last edited:
There are many approaches to this topic and RMerlin's Wiki https://github.com/RMerl/asuswrt-merlin.ng/wiki/Enforce-Safesearch
-briefly delves on the concept using dnsmasq. Instead of reinventing the wheel over and over again with this topic, below is a small script and instructions for users that wish to explore this topic.
simply copy and paste the below script into SSH terminal + Press Enter and everything is taken care of.

Thanks, that seems to work flawlessly. Will this survive a reboot or even upgrade, without factory reset, or does it need to by applied again after any of these events?
 
Actually, something is wrong. DNS doesn't work anymore after I executed your script, dnsmasq seems to fail so there's no resolving being done anymore.

From the logs:

Code:
Jan 29 19:20:51 RT-AC86U dnsmasq[10753]: bad address at line 266 of /etc/dnsmasq.conf
Jan 29 19:20:51 RT-AC86U dnsmasq[10753]: FAILED to start up[/ICODE]

line 266 of /etc/dnsmasq.conf shows
[CODE]address=/duckduckgo.com/safe.duckduckgo.com[/ICODE]

Seems like your script has broken dnsmasqs config and I don't have the knowledge to see how.

I made a backup of settings and off jffs prior to running your script.

What should I do to fix this?
 
Last edited by a moderator:
I restored settings and jffs after I connected to the routers IP and gaining access to the WebUI. I think your script might need some more tinkering...
 
Last edited by a moderator:
Thanks, that seems to work flawlessly. Will this survive a reboot or even upgrade, without factory reset, or does it need to by applied again after any of these events?
Only way it needs reapply is if you wipe jffs partition. Or somehow delete dnsmasq.conf.add
 
  • Like
Reactions: MvW
Thanks for the confirmation. However, as mentioned above I had to overwrite your modifications by restoring my backups which I made prior to running your script, as dnsmasq fails to start after the modifications. See details above. If you need more info let me know, I'd love to get this working, but in its currents state it's breaks dnsmasq. Thanks for the effort though!
 
Actually, something is wrong. DNS doesn't work anymore after I executed your script, dnsmasq seems to fail so there's no resolving being done anymore.

From the logs:

Jan 29 19:20:51 RT-AC86U dnsmasq[10753]: bad address at line 266 of /etc/dnsmasq.conf Jan 29 19:20:51 RT-AC86U dnsmasq[10753]: FAILED to start up

line 266 of /etc/dnsmasq.conf shows
address=/duckduckgo.com/safe.duckduckgo.com

Seems like your script has broken dnsmasqs config and I don't have the knowledge to see how.

I made a backup of settings and off jffs prior to running your script.

What should I do to fix this?
Well without knowing what address was at line 266 of your dnsmasq.conf idk what was causing the issue. I removed the mock ipv4 to ipv6 addresses thinking it might have been that, you can give the script a try again if you like nvm it is the duckduckgo. I got a fix
 
Well without knowing what address was at line 266 of your dnsmasq.conf idk what was causing the issue. I removed the mock ipv4 to ipv6 addresses thinking it might have been that, you can give the script a try again if you like nvm it is the duckduckgo. I got a fix

From my post above:

From the logs:

Code:
Jan 29 19:20:51 RT-AC86U dnsmasq[10753]: bad address at line 266 of /etc/dnsmasq.conf
Jan 29 19:20:51 RT-AC86U dnsmasq[10753]: FAILED to start up[/ICODE]

line 266 of /etc/dnsmasq.conf shows
[CODE]address=/duckduckgo.com/safe.duckduckgo.com[/ICODE]
[/QUOTE]
 
Last edited by a moderator:
it is fixed
should work now.

I switched DuckDuckgo to cname method just keep in mind that the IP address for safe.duckduckgo has been subject to change overtime.
Just reran the edited script, now google.com and google.nl etc. can't be found anymore. Any suggestions before I restore the freshly made backup again?

dnsmasq.log shows
Code:
Jan 29 21:29:10 dnsmasq[1434]: query[A] google.com from 10.0.12.21
21:29:10 dnsmasq[1434]: query[A] google.com from 10.0.12.21
Jan 29 21:29:10 dnsmasq[1434]: config google.com is <CNAME>
21:29:10 dnsmasq[1434]: config google.com is <CNAME>
Jan 29 21:29:11 dnsmasq[1434]: query[A] google.com from 10.0.12.21
21:29:11 dnsmasq[1434]: query[A] google.com from 10.0.12.21
Jan 29 21:29:11 dnsmasq[1434]: config google.com is <CNAME>
21:29:11 dnsmasq[1434]: config google.com is <CNAME>
Jan 29 21:29:11 dnsmasq[1434]: query[A] google.com from 10.0.12.21
21:29:11 dnsmasq[1434]: query[A] google.com from 10.0.12.21
Jan 29 21:29:11 dnsmasq[1434]: config google.com is <CNAME>
21:29:11 dnsmasq[1434]: config google.com is <CNAME>
 
Last edited by a moderator:
if you can use your broswers i wouldn't worry about it.

Just reran the edited script, now google.com and google.nl etc. can't be found anymore.

As mentioned above, after your last modifications, none of the google domains are being resolved anymore. So, basically google can't be reached, but times out with a DNS error in any browserf. I just restored the last backup and google can be reached again, so I fear you broke something while fixing the duckduckgo issue (which is perfectly reachable now).
 
I hate so say it, but when I ran your script again just this morning, before the rest of the house woke up, suddenly duckduckgo isn't reachable anymore. Which is my default search engine on all computers, phones and tablets in the house. Google works fine now, everything runs through safesearch, but apparently duckduckgo is broken now. Could please have another look at the script, something else has gone broken, because after restoring my backup duckduckgo is available again, so it must be in the modifications your script makes in the dnsmasq config. Thanks again for your effort.
 
I hate so say it, but when I ran your script again just this morning, before the rest of the house woke up, suddenly duckduckgo isn't reachable anymore. Which is my default search engine on all computers, phones and tablets in the house. Google works fine now, everything runs through safesearch, but apparently duckduckgo is broken now. Could please have another look at the script, something else has gone broken, because after restoring my backup duckduckgo is available again, so it must be in the modifications your script makes in the dnsmasq config. Thanks again for your effort.
I just ran the current script i have posted, every line comes out perfectly
no issues with dnsmasq either.
 
Strange. I'll have another look tonight when the rest sleeps and run the same commands you posted above.

It's not like I have to reboot the router or anything alike after installing your script? Nothing in your first post pointed at that, so I have just pasted the script, executed the generated command at the end of the script and tested it's functionality.
 
Strange. I'll have another look tonight when the rest sleeps and run the same commands you posted above.

It's not like I have to reboot the router or anything alike after installing your script? Nothing in your first post pointed at that, so I have just pasted the script, executed the generated command at the end of the script and tested it's functionality.
just copy and paste the coded section into your SSH terminal. it appends the lines to /jffs/configs/dnsmasq.conf.add . then restarts dnsmasq. keep in mind you may want to refresh your web browsers or clear any cache on your testing devices.
 
I re-applied your script and now everything is reachable. I cleared all history and caches to be sure, but even before that duckduckgo.com was reachable, so I'm not sure whether you changed something in the mean time. There's one thing I noticed though: safesearch settings on duckduckgo show 'Moderate' instead of 'Strict'. I noticed the IP-adress in my dig output below differs from yours. This could just be regional or load balancing, but could you check on duckduckgo.com > settings > whether safe search is set to Moderate or strict? Moderate still shows some pretty graphic results which I'd like to block.

Your help is muchly appreciated.

Code:
dig www.duckduckgo.com @10.0.12.1

; <<>> DiG 9.16.8 <<>> www.duckduckgo.com @10.0.12.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8917
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1280
;; QUESTION SECTION:
;www.duckduckgo.com.            IN      A

;; ANSWER SECTION:
www.duckduckgo.com.     0       IN      CNAME   safe.duckduckgo.com.
safe.duckduckgo.com.    0       IN      A       52.142.126.100

;; Query time: 10 msec
;; SERVER: 10.0.12.1#53(10.0.12.1)
;; WHEN: Sat Jan 30 18:59:58 CET 2021
;; MSG SIZE  rcvd: 96

One last thing: in a previous post you warned that the IP-address for safe.duckduckgo is know to change frequently. How to keep up with the changes? Just can I just edit the up in your script and re-run it, or should I should I do something else?
 
I just discovered that when going to Settings on safe.duckduckgo.com Safe Search is set to Moderate. So I've sent them a message asking to clarify. Redirecting towards safe.duckduckgo.com is pretty useless this way, as Moderate is the default setting...
 
I just discovered that when going to Settings on safe.duckduckgo.com Safe Search is set to Moderate. So I've sent them a message asking to clarify. Redirecting towards safe.duckduckgo.com is pretty useless this way, as Moderate is the default setting...
Your settings won't get changed your search results are directed at the dns level.
 
Your settings won't get changed your search results are directed at the dns lev

Thanks for your reply, but I think you're missing my point. I understand how the redirection at DNS level works, but safe.duckduckgo.com isn't working as it is supposed to. From their support pages:

By using safe.duckduckgo.com instead. Searches from safe.duckduckgo.com always have safe search set to "strict".
Source: https://help.duckduckgo.com/duckduckgo-help-pages/features/safe-search/

According to the settings at safe.duckduckgo it's set to Moderate search results and when testing I do get explicit search results, which would like to protect my kid from seeing.

So your script is working fine now, but apparently safe.duckduckgo.com isn't.
 
Thanks for your reply, but I think you're missing my point. I understand how the redirection at DNS level works, but safe.duckduckgo.com isn't working as it is supposed to. From their support pages:


Source: https://help.duckduckgo.com/duckduckgo-help-pages/features/safe-search/

According to the settings at safe.duckduckgo it's set to Moderate search results and when testing I do get explicit search results, which would like to protect my kid from seeing.

So your script is working fine now, but apparently safe.duckduckgo.com isn't.
It appears the issue was not coming from the router but the actual devices builtin cache. And the answer to your issue is that some domains refresh quicker than others, duckduckgo seems to take a bit long to refresh with in the cache, while other domains did not. This required you to purge the cache on the devices as it was not getting new information from the routers dns.
 
It appears the issue was not coming from the router but the actual devices builtin cache. And the answer to your issue is that some domains refresh quicker than others, duckduckgo seems to take a bit long to refresh with in the cache, while other domains did not. This required you to purge the cache on the devices as it was not getting new information from the routers dns.
It appears you are right. As I said your script seems to work fine after the modifications you've made, so this was in no way an attack on your effort, so please don't feel offended.

Nevertheless I still think it's strange that safe.duckduckgo, shows 'Moderate' Safe Search settings, instead of Strict (at least, that's the option shown at their settings page), regardless of what the search results are showing, but that now seems to be completely beyond the scope of this thread. That's odd, or is am I the only one thinking that they're completely missing the point they're trying to achieve with safe.duckduckgo. When visiting Google it clearly shows 'Safesearch on' and there's no (obvious) way around it. If and when I'll receive a reply from Duckduckgo, I'll share it.
 
Similar threads
Thread starter Title Forum Replies Date
gatorback Immediately forcing DDNS update Asuswrt-Merlin 3

Similar threads

Support SNBForums w/ Amazon

If you'd like to support SNBForums, just use this link and buy anything on Amazon. Thanks!

Sign Up For SNBForums Daily Digest

Get an update of what's new every day delivered to your mailbox. Sign up here!
Top