#!/usr/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$ Border Gateway Protocol daemon
#$check$ TCP connections exist for all configured neighbours
#$ref$ bgpd(8), rtbh-init, KB/RemotelyTriggeredBlackHoleFiltering
#$author$ Rafal Rzeczkowski
#$version$ 0.6.0

level_check short

#CHANGELOG
#0.5.0	start initial development
#0.5.1	accept both local and peer 'bgp' port as indicator of a BGP session
#0.5.2	support FRR as well as Quagga
#0.5.3	refine startup help advice
#0.5.4	remove square brackets around IPv6 addresses reported by ss
#0.5.5	test for presence of all required route-map parameters in the configuration file
#0.5.6	drop support for Quagga Software Routing Suite
#0.5.7	check route-map parameters for the manual table
#0.5.8	ensure that the RTBH_MANUAL route-map is present
#0.6.0	detect BGP connections without positive pfxSnt counter

# FRRouting definitions
declare -r CONF='/etc/frr/frr.conf.sav'
declare -r PID_FILE='/run/frr/bgpd.pid'
declare -r START_HELP='service frr start'

if [[ ! -s $CONF ]]; then
	unknown 'BGP daemon not configured/activated'
	exit
fi

if [[ ! -s $PID_FILE ]]; then
	fail critical "daemon not running - PID file $PID_FILE is missing"
	helpmsg "start the service using {$START_HELP}"
	exit
fi

pid=$(<$PID_FILE)
if kill -0 "$pid"; then
	echodebug "BGP daemon $pid is running"
else
	fail critical 'daemon appears to have crashed'
	helpmsg "investigate the underlying problem and start the service using {$START_HELP}"
	exit
fi

mapfile -t neighbors < <(awk '{
if ($1=="neighbor" && $3=="peer-group" && length($4))
	neighbors[$2]=1
}
END {
	for (neighbor in neighbors)
		print neighbor
}' "$CONF")

for neighbor in "${neighbors[@]}"; do
	#State	Recv-Q	Send-Q	Local Address:Port							Peer Address:Port
	#ESTAB	0		0		184.94.176.209:51308						184.94.176.194:bgp
	#ESTAB	0		0		184.94.176.209:bgp							184.94.176.195:41528
	#ESTAB	0		0		2607:2900:0:198:216:3eff:fe11:a76e:34841	2607:2900:0:8000::1:bgp
	#ESTAB	0		0		2607:2900:0:198:216:3eff:fe11:a76e:60027	2607:2900:0:8000::2:bgp
	#ESTAB	0		0		[2606:bf00:0:a:216:3eff:fe37:e5ee]:bgp		[2606:bf00:0:a:5287:89ff:fe67:8576]:27855
	#ESTAB	0		0		[2606:bf00:0:a:216:3eff:fe37:e5ee]:55672	[2606:bf00:0:a:e2ac:f1ff:fe01:8f16]:bgp

	ss_output=$(ss --tcp)

	if awk --assign neighbor="$neighbor" '
	{
		if (NR==1)
			next

		state=$1
		recv_q=$2
		send_q=$3
		local_address_port=$4
		peer_address_port=$5

		local_address=local_address_port
		local_port=local_address_port
		sub(":[^:]+$","",local_address)
		gsub("^[[]|[]]$","",local_address)
		sub("^.+:","",local_port)

		peer_address=peer_address_port
		peer_port=peer_address_port
		sub(":[^:]+$","",peer_address)
		gsub("^[[]|[]]$","",peer_address)
		sub("^.+:","",peer_port)

		#print state "\t" recv_q "\t"send_q "\t" local_address "\t" local_port "\t" peer_address "\t" peer_port

		if (state=="ESTAB" && peer_address==neighbor && ( local_port=="bgp" || peer_port=="bgp" ) ) {
			found=1
			exit
		}
	} END {
		if (found)
			exit 0
		else
			exit 1
		}' <<< "$ss_output"; then
		echodebug "active BGP/TCP session to $neighbor"
	else
		fail warning "missing BGP/TCP session to $neighbor"
		helpmsg "$neighbor is defined in $CONF; check configuration on the router including MD5 password"
		##echo "$ss_output"
		exit
	fi
done

if missing_config=$(awk '
BEGIN {
	split("RTBH_HONEYPOT RTBH_SECURITY RTBH_BOGON RTBH_DDOS RTBH_MANUAL", route_map_types)
	split("community ip ipv6 local-preference", param_types)
}
	{
	if ($1=="!")
		route_map=""
	else if ($1=="route-map")
		route_map=$2
	else if (route_map)
		if ($1=="set" && $3) {
			param=$2
			config[route_map][param]=1
		}
}
END {
	for (route_map_type_index in route_map_types) {
		route_map_type=route_map_types[route_map_type_index]
		for (param_type_index in param_types) {
			param_type=param_types[param_type_index]
			if (!config[route_map_type][param_type]) {
				print route_map_type "\t" param_type
				exit 1
			}
		}
	}
}' $CONF); then
	echodebug "$CONF: found all the required route-map parameters"
else
	read -r section parameter <<< "$missing_config"
	fail warning "$CONF: $section route-map section is missing the $parameter parameter"
	exit
fi

bgp_summary_json=$(vtysh --command 'show bgp summary json')

declare -A -i peers
mapfile -t address_families < <(jq '. | keys | .[]' <<< "$bgp_summary_json")
for address_family in ${address_families[@]}; do
	address_family=${address_family#\"}
	address_family=${address_family%\"}
	idType=${address_family%Unicast}
	mapfile -t pfxSnt_array < <(jq ".$address_family.peers[] | select(.idType==\"$idType\") | .pfxSnt" <<< "$bgp_summary_json")
	for pfxSnt in ${pfxSnt_array[@]}; do
		if [[ ${pfxSnt} -eq 0 ]]; then
			fail warning "found an $address_family peer with zero sent prefix count"
			helpmsg 'examine {show bgp summary json} vtysh command output'
			exit
		fi
	done
	echodebug "$address_family peers pfxSnt=[${pfxSnt_array[*]}]"
	peers[$address_family]=${#pfxSnt_array[@]}
done

address_family='ipv6Unicast'
if [[ -z "${peers[$address_family]+xxx}" ]]; then
	fail caution "$address_family AF is not configured"
	exit
elif [[ ${peers[$address_family]} -eq 0 ]]; then
	fail caution "$address_family AF has zero peers"
	exit
else
	echodebug "$address_family AF is active"
fi

ok
