# Hey Emacs, this is a -*- shell-script -*- !!!  :-)

# Thanks/blame to Stephen Rothwell for suggesting that this can be
# done in the shell.  ;-)
ipv6_to_hex ()
{
    local addr="$1"

    # Replace "::" by something special.
    local foo="${addr/::/:@:}"

    # Join the groups of digits together, 0-padding each group of
    # digits out to 4 digits, and count the number of (non-@) groups
    local out=""
    local count=0
    local i
    for i in $(IFS=":" ; echo $foo ) ; do
	if [ "$i" = "@" ] ; then
	    out="${out}@"
	else
	    out="${out}$(printf '%04x' 0x${i})"
	    count=$(($count + 4))
	fi
    done

    # Replace '@' with correct number of zeroes
    local zeroes=$(printf "%0$((32 - $count))x" 0)
    echo "${out/@/${zeroes}}"
}

#######################################

get_src_socket ()
{
    local proto="$1"
    local dst_socket="$2"
    local pid="$3"
    local prog="$4"

    local pat="^${proto}6?[[:space:]]+[[:digit:]]+[[:space:]]+[[:digit:]]+[[:space:]]+[^[:space:]]+[[:space:]]+${dst_socket//./\\.}[[:space:]]+ESTABLISHED[[:space:]]+${pid}/${prog}[[:space:]]*\$"
    out=$(netstat -tanp |
	egrep "$pat" |
	awk '{ print $4 }')

    [ -n "$out" ]
}

wait_until_get_src_socket ()
{
    local proto="$1"
    local dst_socket="$2"
    local pid="$3"
    local prog="$4"

    echo "Waiting for ${prog} to establish connection to ${dst_socket}..."

    wait_until 5 get_src_socket "$@"
}

#######################################

check_tickles ()
{
    local node="$1"
    local test_ip="$2"
    local test_port="$3"
    local src_socket="$4"
    try_command_on_node $node ctdb gettickles $test_ip $test_port
    # SRC: 10.0.2.45:49091   DST: 10.0.2.143:445
    [ "${out/SRC: ${src_socket} /}" != "$out" ]
}

check_tickles_all ()
{
    local numnodes="$1"
    local test_ip="$2"
    local test_port="$3"
    local src_socket="$4"

    try_command_on_node all ctdb gettickles $test_ip $test_port
    # SRC: 10.0.2.45:49091   DST: 10.0.2.143:445
    local t="${src_socket//./\\.}"
    local count=$(grep -E -c "SRC: ${t} " <<<"$out" || true)
    [ $count -eq $numnodes ]
}



#######################################

# filename will be in $tcpdump_filename, pid in $tcpdump_pid
tcpdump_start ()
{
    tcpdump_filter="$1" # global

    echo "Running tcpdump..."
    tcpdump_filename=$(mktemp)
    ctdb_test_exit_hook_add "rm -f $tcpdump_filename"

    # The only way of being sure that tcpdump is listening is to send
    # some packets that it will see.  So we use dummy pings - the -U
    # option to tcpdump ensures that packets are flushed to the file
    # as they are captured.
    local dummy_addr="127.3.2.1"
    local dummy="icmp and dst host ${dummy_addr} and icmp[icmptype] == icmp-echo"
    tcpdump -n -p -s 0 -e -U -w $tcpdump_filename -i any "($tcpdump_filter) or ($dummy)" &
    ctdb_test_exit_hook_add "kill $! >/dev/null 2>&1"

    echo "Waiting for tcpdump output file to be ready..."
    ping -q "$dummy_addr" >/dev/null 2>&1 &
    ctdb_test_exit_hook_add "kill $! >/dev/null 2>&1"

    tcpdump_listen_for_dummy ()
    {
	tcpdump -n -r $tcpdump_filename -c 1 "$dummy" >/dev/null 2>&1
    }

    wait_until 10 tcpdump_listen_for_dummy
}

# By default, wait for 1 matching packet.
tcpdump_wait ()
{
	local count="${1:-1}"
	local filter="${2:-${tcpdump_filter}}"

	tcpdump_check ()
	{
		# It would be much nicer to add "ether src
		# $releasing_mac" to the filter.  However, tcpdump
		# does not allow MAC filtering unless an ethernet
		# interface is specified with -i.  It doesn't work
		# with "-i any" and it doesn't work when reading from
		# a file.  :-(
		local found
		if [ -n "$releasing_mac" ] ; then
			found=$(tcpdump -n -e -r "$tcpdump_filename" \
					"$filter" 2>/dev/null |
					       grep -c "In ${releasing_mac}")
		else
			found=$(tcpdump -n -e -r "$tcpdump_filename" \
					"$filter" 2>/dev/null |
					       wc -l)
		fi

		[ $found -ge $count ]
	}

	echo "Waiting for tcpdump to capture some packets..."
	if ! wait_until 30 tcpdump_check ; then
		echo "DEBUG AT $(date '+%F %T'):"
		local i
		for i in "onnode -q 0 $CTDB status" \
				 "netstat -tanp" \
				 "tcpdump -n -e -r $tcpdump_filename" ; do
			echo "$i"
			$i || true
		done
		return 1
	fi
}

tcpdump_show ()
{
    local filter="${1:-${tcpdump_filter}}"

    tcpdump -n -e -vv -XX -r $tcpdump_filename  "$filter" 2>/dev/null
}

tcp4tickle_sniff_start ()
{
    local src="$1"
    local dst="$2"

    local in="src host ${dst%:*} and tcp src port ${dst##*:} and dst host ${src%:*} and tcp dst port ${src##*:}"
    local out="src host ${src%:*} and tcp src port ${src##*:} and dst host ${dst%:*} and tcp dst port ${dst##*:}"
    local tickle_ack="${in} and (tcp[tcpflags] & tcp-ack != 0) and (tcp[14:2] == 1234)" # win == 1234
    local ack_ack="${out} and (tcp[tcpflags] & tcp-ack != 0)"
    tcptickle_reset="${in} and tcp[tcpflags] & tcp-rst != 0"
    local filter="(${tickle_ack}) or (${ack_ack}) or (${tcptickle_reset})"

    tcpdump_start "$filter"
}

# tcp[] does not work for IPv6 (in some versions of tcpdump)
tcp6tickle_sniff_start ()
{
    local src="$1"
    local dst="$2"

    local in="src host ${dst%:*} and tcp src port ${dst##*:} and dst host ${src%:*} and tcp dst port ${src##*:}"
    local out="src host ${src%:*} and tcp src port ${src##*:} and dst host ${dst%:*} and tcp dst port ${dst##*:}"
    local tickle_ack="${in} and (ip6[53] & tcp-ack != 0) and (ip6[54:2] == 1234)" # win == 1234
    local ack_ack="${out} and (ip6[53] & tcp-ack != 0)"
    tcptickle_reset="${in} and ip6[53] & tcp-rst != 0"
    local filter="(${tickle_ack}) or (${ack_ack}) or (${tcptickle_reset})"

    tcpdump_start "$filter"
}

tcptickle_sniff_start ()
{
    local src="$1"
    local dst="$2"

    case "${dst%:*}" in
	*:*) tcp6tickle_sniff_start "$src" "$dst" ;;
	*)   tcp4tickle_sniff_start "$src" "$dst" ;;
    esac
}

tcptickle_sniff_wait_show ()
{
	local releasing_mac="$1"  # optional, used by tcpdump_wait()

	tcpdump_wait 1 "$tcptickle_reset"

	echo "GOOD: here are some TCP tickle packets:"
	tcpdump_show
}

gratarp4_sniff_start ()
{
    tcpdump_start "arp host ${test_ip}"
}

gratarp6_sniff_start ()
{
    local neighbor_advertisement="icmp6 and ip6[40] == 136"
    local hex=$(ipv6_to_hex "$test_ip")
    local match_target="ip6[48:4] == 0x${hex:0:8} and ip6[52:4] == 0x${hex:8:8} and ip6[56:4] == 0x${hex:16:8} and ip6[60:4] == 0x${hex:24:8}"

    tcpdump_start "${neighbor_advertisement} and ${match_target}"
}

gratarp_sniff_start ()
{
    case "$test_ip" in
	*:*) gratarp6_sniff_start ;;
	*)   gratarp4_sniff_start ;;
    esac
}

gratarp_sniff_wait_show ()
{
    tcpdump_wait 2

    echo "GOOD: this should be the some gratuitous ARPs:"
    tcpdump_show
}


ctdb_test_check_real_cluster ()
{
    [ -z "$TEST_LOCAL_DAEMONS" ] || \
	die "ERROR: This test must be run against a real/virtual cluster, not local daemons."

    local h=$(hostname)

    local i
    for i in $(onnode -q all hostname) ; do
	[ "$h" != "$i" ] || \
	    die "ERROR: This test must not be run from a cluster node."
    done
}

ping_wrapper ()
{
    case "$*" in
	*:*) ping6 "$@"   ;;
	*)   ping  "$@"   ;;
    esac
}
