From 01e091fd170229c98d33adf96a8c207717cc16ea Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 2 Jun 2017 23:01:48 +0200 Subject: [PATCH] Prefer ULA over GUA addresses [IPv6] (#1508) * On installs with GUA and ULA's we should prefer ULA's as it's been demonstrated that GUA's can and often are rotated by ISPs. Fixes #1473 * Add test for link-local address detection * Add ULA-only and GUA-only tests * Add test_IPv6_GUA_ULA_test and test_IPv6_ULA_GUA_test * Add "" * Add mock_command_2 command that can mock a command with more than one argument (as "ip -6 address") and result multiple lines of results * Make mock_command_2 more similar to the original mock_command * Correct comments * Fixed remaining comments * Fixed one last comment... * Fixed a comment... --- automated install/basic-install.sh | 34 +++++++++++-- test/test_automated_install.py | 76 ++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/automated install/basic-install.sh b/automated install/basic-install.sh index 42e06bdf..b8b44550 100755 --- a/automated install/basic-install.sh +++ b/automated install/basic-install.sh @@ -327,16 +327,44 @@ chooseInterface() { fi } +# See https://github.com/pi-hole/pi-hole/issues/1473#issuecomment-301745953 +testIPv6() { + first="$(cut -f1 -d":" <<< "$1")" + value1=$(((0x$first)/256)) + value2=$(((0x$first)%256)) + ((($value1&254)==252)) && echo "ULA" || true + ((($value1&112)==32)) && echo "GUA" || true + ((($value1==254) && (($value2&192)==128))) && echo "Link-local" || true +} + useIPv6dialog() { - # Show the IPv6 address used for blocking - IPV6_ADDRESS=$(ip -6 route get 2001:4860:4860::8888 | grep -v "unreachable" | awk -F " " '{ for(i=1;i<=NF;i++) if ($i == "src") print $(i+1) }') + # Determine the IPv6 address used for blocking + IPV6_ADDRESSES=($(ip -6 address | grep 'scope global' | awk '{print $2}')) + + # Determine type of found IPv6 addresses + for i in "${IPV6_ADDRESSES[@]}"; do + result=$(testIPv6 "$i") + [[ "${result}" == "ULA" ]] && ULA_ADDRESS="$i" + [[ "${result}" == "GUA" ]] && GUA_ADDRESS="$i" + done + + # Determine which address to be used: Prefer ULA over GUA or don't use any if none found + if [[ ! -z "${ULA_ADDRESS}" ]]; then + IPV6_ADDRESS="${ULA_ADDRESS}" + echo "::: Found IPv6 ULA address, using it for blocking IPv6 ads" + elif [[ ! -z "${GUA_ADDRESS}" ]]; then + echo "::: Found IPv6 GUA address, using it for blocking IPv6 ads" + IPV6_ADDRESS="${GUA_ADDRESS}" + else + echo "::: Found neither IPv6 ULA nor GUA address, blocking IPv6 ads will not be enabled" + IPV6_ADDRESS="" + fi if [[ ! -z "${IPV6_ADDRESS}" ]]; then whiptail --msgbox --backtitle "IPv6..." --title "IPv6 Supported" "$IPV6_ADDRESS will be used to block ads." ${r} ${c} fi } - use4andor6() { local useIPv4 local useIPv6 diff --git a/test/test_automated_install.py b/test/test_automated_install.py index 8e36fc96..60772625 100644 --- a/test/test_automated_install.py +++ b/test/test_automated_install.py @@ -402,6 +402,61 @@ def test_FTL_binary_installed_and_responsive_no_errors(Pihole): # assert '644 /run/pihole-FTL.pid' in support_files.stdout # assert '644 /var/log/pihole-FTL.log' in support_files.stdout +def test_IPv6_only_link_local(Pihole): + ''' confirms IPv6 blocking is disabled for Link-local address ''' + # mock ip -6 address to return Link-local address + mock_command_2('ip', {'-6 address':('inet6 fe80::d210:52fa:fe00:7ad7/64 scope link', '0')}, Pihole) + detectPlatform = Pihole.run(''' + source /opt/pihole/basic-install.sh + useIPv6dialog + ''') + expected_stdout = 'Found neither IPv6 ULA nor GUA address, blocking IPv6 ads will not be enabled' + assert expected_stdout in detectPlatform.stdout + +def test_IPv6_only_ULA(Pihole): + ''' confirms IPv6 blocking is enabled for ULA addresses ''' + # mock ip -6 address to return ULA address + mock_command_2('ip', {'-6 address':('inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global', '0')}, Pihole) + detectPlatform = Pihole.run(''' + source /opt/pihole/basic-install.sh + useIPv6dialog + ''') + expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads' + assert expected_stdout in detectPlatform.stdout + +def test_IPv6_only_GUA(Pihole): + ''' confirms IPv6 blocking is enabled for GUA addresses ''' + # mock ip -6 address to return GUA address + mock_command_2('ip', {'-6 address':('inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global', '0')}, Pihole) + detectPlatform = Pihole.run(''' + source /opt/pihole/basic-install.sh + useIPv6dialog + ''') + expected_stdout = 'Found IPv6 GUA address, using it for blocking IPv6 ads' + assert expected_stdout in detectPlatform.stdout + +def test_IPv6_GUA_ULA_test(Pihole): + ''' confirms IPv6 blocking is enabled for GUA and ULA addresses ''' + # mock ip -6 address to return GUA and ULA addresses + mock_command_2('ip', {'-6 address':('inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global\ninet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global', '0')}, Pihole) + detectPlatform = Pihole.run(''' + source /opt/pihole/basic-install.sh + useIPv6dialog + ''') + expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads' + assert expected_stdout in detectPlatform.stdout + +def test_IPv6_ULA_GUA_test(Pihole): + ''' confirms IPv6 blocking is enabled for GUA and ULA addresses ''' + # mock ip -6 address to return ULA and GUA addresses + mock_command_2('ip', {'-6 address':('inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global\ninet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global', '0')}, Pihole) + detectPlatform = Pihole.run(''' + source /opt/pihole/basic-install.sh + useIPv6dialog + ''') + expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads' + assert expected_stdout in detectPlatform.stdout + # Helper functions def mock_command(script, args, container): ''' Allows for setup of commands we don't really want to have to run for real in unit tests ''' @@ -424,6 +479,27 @@ def mock_command(script, args, container): chmod +x {script} rm -f /var/log/{scriptlog}'''.format(script=full_script_path, content=mock_script, scriptlog=script)) +def mock_command_2(script, args, container): + ''' Allows for setup of commands we don't really want to have to run for real in unit tests ''' + full_script_path = '/usr/local/bin/{}'.format(script) + mock_script = dedent('''\ + #!/bin/bash -e + echo "\$0 \$@" >> /var/log/{script} + case "\$1 \$2" in'''.format(script=script)) + for k, v in args.iteritems(): + case = dedent(''' + \"{arg}\") + echo \"{res}\" + exit {retcode} + ;;'''.format(arg=k, res=v[0], retcode=v[1])) + mock_script += case + mock_script += dedent(''' + esac''') + container.run(''' + cat < {script}\n{content}\nEOF + chmod +x {script} + rm -f /var/log/{scriptlog}'''.format(script=full_script_path, content=mock_script, scriptlog=script)) + def run_script(Pihole, script): result = Pihole.run(script) assert result.rc == 0