#!/bin/sh

set -e
#set -x

# Setup a few variables for later use #
ipv4_reg='^([0-9]){1,3}\.([0-9]){1,3}\.([0-9]){1,3}\.([0-9]){1,3}$'
ipv6_reg='^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$'
hash=$(openssl rand -hex 8)
RED='\033[0;31m'
GREEN='\033[0;34m'
CYAN='\033[0;36m'
YELLOW='\033[4;33m'
COLOR_OFF='\033[0m'
BLINK='\033[5m'

########################
### Input parameters ###
########################
usage() {
	my_name=$(basename $0)
	echo "Tool to connectivity of a virtual machine.
Usage:
	${my_name} -i <ipv4/ipv6/vm-hostname> [-u <ping-host-user>] -p <ping-host-IPv4/ping-host-IPv6>
If using a hostname, ${my_name} will attempt to resolve the
VM to find its IP address."
}

while getopts "i:hdp:u" o; do
	case "${o}" in
	i)
		instance_name=${OPTARG}
	;;
	u)
		ping_host_user=${OPTARG}
	;;
	p)
		ping_host_ip=${OPTARG}
	;;
	d)
		debug="set -exuo pipefail"
	;;
	h)
		usage
		exit 0
	;;
	esac
done
shift $((OPTIND-1))

if [ -z "${instance_name}" ] ; then
	usage
	exit 1
fi

ping_host_ip_is_v4=no
ping_host_ip_is_v6=no
if echo ${ping_host_ip} | grep -q -E ${ipv4_reg} ; then
	ping_host_ip_is_v4=yes
fi
if echo ${ping_host_ip} | grep -q -E ${ipv6_reg} ; then
	ping_host_ip_is_v6=yes
fi


# Check if we've been given an IP address or a hostname.
# If a hostname, attempt to resolve it.
version="4"
if echo ${instance_name} | grep -q -E ${ipv4_reg} ; then
	IP=${instance_name}
elif echo ${instance_name} | grep -q -E ${ipv6_reg} ; then
	IP=${instance_name}
	version="6"
else
	if host ${instance_name} 1>/dev/null 2>/dev/null ; then
		IP=$(host ${instance_name} | grep 'has address' | awk '{print $4}')
	else
		echo "Cannot resolv the IP address for the VM: ${instance_name}."
		exit 1
	fi
fi

if [ "${ping_host_ip_is_v4}" = "no" ] && [ "${version}" = "4" ] ; then
	echo "IP of your ping host must be IPv4 if testing the IPv4 connectivity."
	exit 1
fi
if [ "${ping_host_ip_is_v6}" = "no" ] && [ "${version}" = "6" ] ; then
	echo "IP of your ping host must be IPv6 if testing the IPv6 connectivity."
	exit 1
fi

find_compute_node_hostname() {
	echo "Searching port ID..."
	port=$(openstack port list --fixed-ip ip-address=${IP} -f value -c ID)
	echo "Searching port device owner..."
	device_owner=$(openstack port show ${port} -f value -c device_owner)
	if [ "${device_owner}" = "network:floatingip" ] ; then
		float_id=$(openstack floating ip list --floating-ip-address ${IP} -f value -c ID)
		uuid=$(openstack floating ip show ${float_id} -f json -c port_details | jq '.["port_details"]' -r | tr ' ' '\n' | grep device_id | cut -d "'" -f2)
	else
		uuid=$(openstack port show ${port} -f value -c device_id)
	fi
	echo "Searching compute hostname..."
	compute=$(openstack server show ${uuid} -f json -c OS-EXT-SRV-ATTR:hypervisor_hostname -c  OS-EXT-SRV-ATTR:instance_name)
}

add_iptables_rules_for_icmp_on_vm(){
	ssh root@${compute_name} /bin/bash <<-EOF
$debug
int_virsh=\$(virsh domiflist $instance_virsh | awk '{print \$1}' | grep tap | cut -c 4-)
incom_rule=\$(iptables-save | grep \$int_virsh | grep -o '\neutron-openvswi-i\w*-\w*' | sort -u)
if [ "$version" -eq 4 ]; then
	iptables -I \$incom_rule -s $ping_host_ip -j DROP -p icmp --icmp-type echo-request
	iptables -I \$incom_rule -s $ping_host_ip -p icmp --icmp-type echo-request -j LOG --log-prefix $hash
else
	ip6tables -I \$incom_rule -s $ping_host_ip -j DROP -p ipv6-icmp
	ip6tables -I \$incom_rule -s $ping_host_ip -p icmpv6 -j LOG --log-prefix $hash
fi
EOF
}

perform_ping_from_external_host() {
	if [ -n "${ping_host_user}" ] ; then
		ping_host_user="${ping_host_user}@"
	else
		ping_host_user=""
	fi
	if [ "${version}" -eq 4 ]; then
		if ! timeout 10 ssh ${ping_host_user}${ping_host_ip} "ping -4 -w 1 -c 2 ${IP} >/dev/null 2>&1 || true" ; then
			echo "${CYAN}Failed to perform ping from ${ping_host_ip} ...${COLOR_OFF}"
			cleanup_iptables_rules_for_icmp_on_vm
			exit 1
		fi
		sleep 2
	else
		if ! timeout 10 ssh ${ping_host_user}${ping_host_ip} "ping -6 -w 1 -c 2 ${IP} >/dev/null 2>&1 || true" ; then
			echo "${CYAN}Failed to perform ping from ${ping_host_ip} ...${COLOR_OFF}"
			exit 1
		fi
		sleep 2
	fi
}

check_compute_syslog_entry() {
	ssh root@${compute_name} /bin/bash <<-EOF
$debug
if grep -q $hash /var/log/syslog; then
	echo -e "${GREEN}No network problem found in our side for: $instance_name"
else
	echo -e "${RED}Network problem found check compute network and/or router namespace. You can use neutron-ha-tool to debug it."
fi
EOF
}

cleanup_iptables_rules_for_icmp_on_vm() {
	ssh root@${compute_name} /bin/bash <<-EOF
$debug
int_virsh=\$(virsh domiflist $instance_virsh | awk '{print \$1}' | grep tap | cut -c 4-)
incom_rule=\$(iptables-save | grep \$int_virsh | grep -o '\neutron-openvswi-i\w*-\w*' | sort -u)
if [ "$version" -eq 4 ]; then
	iptables -D \$incom_rule -s $ping_host_ip -j DROP -p icmp --icmp-type echo-request
	iptables -D \$incom_rule -s $ping_host_ip -p icmp --icmp-type echo-request -j LOG --log-prefix $hash
else
	ip6tables -D \$incom_rule -s $ping_host_ip -j DROP -p ipv6-icmp
	ip6tables -D \$incom_rule -s $ping_host_ip -p icmpv6 -j LOG --log-prefix $hash
fi
EOF
}


echo "${CYAN}===> Searching for compute node hostname and virsh instance name...${COLOR_OFF}"
find_compute_node_hostname
compute_name=$(echo ${compute} | jq --raw-output '. | ."OS-EXT-SRV-ATTR:hypervisor_hostname"')
instance_virsh=$(echo ${compute} | jq --raw-output '. | ."OS-EXT-SRV-ATTR:instance_name"')

echo "${CYAN}===> Adding iptables rules in comput ${compute_name} with IP version: ${version}...${COLOR_OFF}"
add_iptables_rules_for_icmp_on_vm
echo "${CYAN}===> Performing ping from external VM on ${ping_host_ip} ...${COLOR_OFF}"
perform_ping_from_external_host
echo "${CYAN}===> Checking compute node syslog entry...${COLOR_OFF}"
check_compute_syslog_entry
echo "${CYAN}===> Iptables cleanup on ${compute_name}...${COLOR_OFF}"
cleanup_iptables_rules_for_icmp_on_vm
echo "${YELLOW}${BLINK}All done for test ipv${version}${COLOR_OFF}"
