#! /bin/bash
# vim: cindent:shiftwidth=4:tabstop=4:smarttab:textwidth=100

set -o posix
set -o errexit
set -o pipefail
set -o nounset
#set -o xtrace

#$title$ emulation of Microsoft NCSI (Network Connectivity Status Indicator)
#$check$ transfer of small file via HTTP (IPv4 or IPv6 as available), expected response from recursive DNS servers
#$ref$ https://learn.microsoft.com/en-us/windows-server/networking/ncsi/ncsi-frequently-asked-questions (Answers To common questions about NCSI)
#$author$ Rafal Rzeczkowski
#$version$ 0.6.2

level_check long

#0.5.0	initial
#0.5.1	added metadata headers and help messages
#0.5.2	ensure that the "host" command completed OK before using its results
#0.5.3	added timeout and retry limits to wget invocation
#0.5.4	override DNS lookup timeout to allow for failover of DNS resolvers
#0.6.0	synchronize check logic to Microsoft Windows 11
#0.6.1	force A RR lookup only for the DNS probe
#0.6.2	probe DNS A and AAAA RRs via BIND9 dig utility

declare -r URL_IPV4='http://www.msftconnecttest.com/connecttest.txt'
declare -r URL_IPV6='http://ipv6.msftconnecttest.com/connecttest.txt'
declare -r -a URL=(0 1 2 3 "$URL_IPV4" 5 "$URL_IPV6")
declare -r -a FAMILY=(0 1 2 3 'inet' 5 'inet6')
declare -r MSG_REQ='Microsoft Connect Test'

declare -r DNSHOST='dns.msftncsi.com'
declare -r ADDRESS_IPV4='131.107.255.255'
declare -r ADDRESS_IPv6='fd3e:4f5a:5b81::1'
declare -r -a ADDRESS=(0 1 2 3 "$ADDRESS_IPV4" 5 "$ADDRESS_IPv6")
declare -r -a DNS_RR=(0 1 2 3 'A' 5 'AAAA')

declare -r -i WGET_NETWORK_TIMEOUT=4
declare -r -i WGET_DNS_LOOKUP_TIMEOUT=12 # Debian jessie: 5 seconds to try nameserver #1, 3 seconds to try nameserver #2
declare -r -i WGET_TRIES=3

# Microsoft Windows 10/11
declare -i probe_ok=0
for protocol in 4 6; do
	ip_route_default=$(ip -oneline -family "${FAMILY[$protocol]}" route show |
	 	awk '{if ($1=="default"){print $1;exit}}')
	if [[ -z "$ip_route_default" ]]; then
		echodebug "missing default route for IPv$protocol packets"
		continue
	fi

	url=${URL[$protocol]}
	if wget_output=$(wget --inet${protocol}-only --quiet \
		--timeout=$WGET_NETWORK_TIMEOUT --dns-timeout=$WGET_DNS_LOOKUP_TIMEOUT --tries=$WGET_TRIES \
		--output-document=- "$url")
	then
		if [[ "$wget_output" = "$MSG_REQ" ]]; then
			echodebug "$url: $wget_output (OK)"
			probe_ok=1
		else
			fail warning "wrong message received from IPv$protocol $url: expected {$MSG_REQ}, received {$wget_output}"
			helpmsg 'fake DNS responses or content translation at application layer'
			exit
		fi
	else
		fail warning "no IPv$protocol HTTP connectivity to $url"
		helpmsg 'wrong default gateway set, gateway malfunction, incorrect edge ACLs'
		exit
	fi
done
if [[ $probe_ok -eq 0 ]]; then
	fail warning 'NCSI HTTP probes failed for all address families'
	exit
fi

# Microsoft Windows 10
for protocol in 4 6; do
	rr=${DNS_RR[$protocol]}
	address=$(dig +short "$DNSHOST." "$rr")

	if [[ -z "$address" ]]; then
		fail warning "failed DNS address lookup for $DNSHOST"
		helpmsg 'check {dns-resolver} plugin status for more information'
		exit
	elif [[ "$address" = "${ADDRESS[$protocol]}" ]]; then
		echodebug "$DNSHOST/$rr: $address (OK)"
	else
		fail warning "$DNSHOST/$rr resolves to $address instead the expected ${ADDRESS[$protocol]}"
		helpmsg 'dishonest DNS servers in use: recursive servers may not override/change authoritative data'
		exit
	fi
done

ok
exit
