tools: Extend multiple-bgpd.sh to support BIRD, ExaBGP plus more configurables

* tools/multiple-bgpd.sh: Extend this script to support having
  multiple different implementations in the ring.  Add config
  generator and launch functions for BIRD and ExaBGP.

  Allow the proportion of other instances in the ring to peer with to
  be configurable as %age via PEERPROP. Allow number of v4 routes to
  advertise to be configurable via ADV. Allow "external", non-ring
  BGP peers to be defined via the EXPEER* arrays.
diff --git a/tools/multiple-bgpd.sh b/tools/multiple-bgpd.sh
index f6edae9..c5668e1 100644
--- a/tools/multiple-bgpd.sh
+++ b/tools/multiple-bgpd.sh
@@ -2,100 +2,457 @@
 
 # Public domain, not copyrighted..
 
-NUM=8
+set -u
+
+# number of bgpd instances, not more than 255 at this point.  At least 3 are
+# needed to connect in a ring.
+NUM=7
+
+# The NUM peers can be connected in a ring topology.
+#
+# This sets the proportion of other peers that each peer should be
+# configured to connect to E.g., 20 means each BGP instance will peer with
+# 20% of the other peers before and after it in the ring.  So 10% of the
+# peers prior to this instance in the ring, and 10% of the following peers. 
+# 100 should lead to a full-mesh, for an odd total number of peers.
+#
+# A value of 1 will result in each instance having at least 2 peers in the ring.
+#
+# A value of 0 will disable creating a ring, in which case the only peers 
+# configured will be those in the EXPEERS list.
+PEERPROP=100
+
+# number of routes each BGP instance should advertise
+ADV=10
+# First octet to use for the IPv4 advertisements.  The advertisements
+# will be /32s under this /8.  E.g.  ADVPREF=10 will mean
+# 10.x.y.z/32's are advertised.
+ADVPREF=10
+
+# Base VTY port to allocate Quagga telnet vtys from. VTYBASE+ID will be
+# the port.
 VTYBASE=2610
-ASBASE=64560
-BGPD=/path/to/bgpd
+# Base ASN to allocate ASNs to instances.
+ASBASE=64500
 PREFIX=192.168.145.
 #PREFIX=3ffe:123:456::
 ADDRPLEN=32
 CONFBASE=/tmp
 PIDBASE=/var/run/quagga
-CHOWNSTR=quagga:quagga
+USER=quagga
+GROUP=quagga
 
-for H in `seq 1 ${NUM}` ; do
-	CONF="${CONFBASE}"/bgpd${H}.conf
-	ADDR=${PREFIX}${H}
+# MRAI to specify, where an implementation supports it.
+MRAI=1
+# Connect retry timer
+CONNECTRETRY=1
+
+# The binary locations for BGP instances. 
+declare -A BGP_BINS=(
+	[quagga]=/usr/sbin/bgpd
+	[bird]=/usr/sbin/bird
+	[birdgit]=/home/paul/code/bird/bird
+	[quaggagit]=/home/paul/code/quagga/bgpd/bgpd
+	[exabgp]=/home/paul/code/exabgp/sbin/exabgp
+)
+
+# Configuration generation functions for the BGP instances.
+declare -A BGP_CONFIGGEN=(
+	[quagga]=quagga_config
+	[quaggagit]=quagga_config
+	[bird]=bird_config
+	[birdgit]=bird_config
+	[exabgp]=exabgp_config
+)
+
+# Launch functions for the BGP instances.
+declare -A BGP_LAUNCH=(
+	[quagga]=quagga_launch
+	[quaggagit]=quagga_launch
+	[bird]=bird_launch
+	[birdgit]=bird_launch
+	[quaggagit]=quagga_launch
+	[exabgp]=exabgp_launch
+)
+
+# the instances to run, in the order they should appear in the ring
+# (repeated over until there are $NUM instances).  The value must exist as a
+# key into the above two arrays.
+declare -a BGP_INSTANCES=(
+	quagga
+	bird
+	quaggagit
+	exabgp
+)
+
+# Peers to configure, that are external to this script. One list of IPs, with
+# corresponding list of their ASes.
+#
+# e.g.:
+#EXPEERS=(192.168.147.{1..10})
+#EXPEERASES=($(seq $((ASBASE+11)) $(($ASBASE+20))))
+
+EXPEERS=()
+EXPEERASES=()
+
+############################################################################
+# Can override any of the above from a supplied file with declarations
+CONFWRITE=Y
+if [ $# -gt 0 ] ; then
+	echo "multiple-bgpd.sh: sourcing config from $1"
+	[ -f "$1" ] && . "$1"
 	
-	if [ ! -e "$CONF" ] ; then
-		# This sets up a ring of bgpd peerings
-		NEXT=$(( ($H % ${NUM}) + 1 ))
-		PREV=$(( (($H + $NUM - 2) % ${NUM}) + 1 ))
-		NEXT2=$(( (($H+1) % ${NUM}) + 1 ))
-		PREV2=$(( (($H + $NUM - 3) % ${NUM}) + 1 ))
-		NEXTADDR="${PREFIX}${NEXT}"
-		NEXTAS=$((${ASBASE} + $NEXT))
-		PREVADDR="${PREFIX}${PREV}"
-		PREVAS=$((${ASBASE} + $PREV))
-		NEXT2ADDR="${PREFIX}${NEXT2}"
-		NEXT2AS=$((${ASBASE} + $NEXT2))
-		PREV2ADDR="${PREFIX}${PREV2}"
-		PREV2AS=$((${ASBASE} + $PREV2))
-		ASN=$((64560+${H}))
+	# keep config, if exists
+	[ $# -gt 1 ] && [ "$2" = "k" ] && CONFWRITE=N	
+fi
+
+############################################################################
+# Internal variables.
+
+# Number of peers for each instance to peer with
+PEERNUM=$(( ($NUM-1) * $PEERPROP / 100  ))
+[ "$PEERNUM" -gt $(($NUM-1)) ] && PEERNUM=$(($NUM-1))
+
+# the 'range', i.e.  how many of the previous and next peers in the ring to
+# connect to
+PEERRANGE=$(( $PEERNUM/2 ))
+[ "$PEERPROP" -gt 0 -a "$NUM" -ge 3 -a  "$PEERRANGE" -le 0 ] && PEERRANGE=1
+
+# and a convenience expansion
+PEEREXP=""
+if [ "$PEERRANGE" -gt 0 ]; then
+	PEEREXP=($(seq -${PEERRANGE} ${PEERRANGE}))
+	# dont need 0
+	unset PEEREXP[PEERRANGE]
+fi
+
+#echo ${PEEREXP[@]}
+
+############################################################################
+## helpers
+
+# translate instance ID to its address.
+id2addr () {
+	local ID=$1
+	echo ${PREFIX}${ID}
+}
+
+# return the ID of a peer, in terms of an offset on the given instance's ID.
+#
+# E.g., given an ID of 1 and an offset of -1, if there are 10 instances overall,
+# this will return 10.
+peeridoff () {
+	local ID=$1
+	local OFF=$2
+	echo $(( (($ID + $OFF - 1  + $NUM) % $NUM) + 1  ))
+}
+
+# return IPv4 address to advertise, for given instance ID and number.
+advipaddr () {
+	local ID=$1
+	local N=$2
+	echo "$ADVPREF.$(( ($N >> 16) %256 )).$(( ($N >> 8) % 256 )).$(( $N % 256  ))"
+}
+
+############################################################################
+# launch functions
+#
+# do not daemonise, so that all launched instances can be killed by killing
+# the script.
+#
+
+quagga_launch () {
+	local ID=$1
+	local ASN=$2
+	local ADDR=$3
+	local BIN=$4
+	local CONF=$5
+	${BIN} -i "${PIDBASE}"/bgpd${ID}.pid \
+		   -l ${ADDR} \
+		   -f "${CONF}" \
+		   -u $USER -g $GROUP \
+		   -P $((${VTYBASE}+${ID}))
+}
+
+exabgp_launch () {
+	local ID=$1
+	local ASN=$2
+	local ADDR=$3
+	local BIN=$4
+	local CONF=$5
+	
+	env exabgp.api.file="${PIDBASE}"/exabgp${ID}.ctl \
+	exabgp.daemon.pid="${PIDBASE}"/bgpd${ID}.pid \
+	exabgp.daemon.daemonize=false \
+	exabgp.tcp.bind=${ADDR} \
+	exabgp.log.enable=false \
+	exabgp.daemon.user=quagga \
+	${BIN} ${CONF}
+}
+
+bird_launch () {
+	local ID=$1
+	local ASN=$2
+	local ADDR=$3
+	local BIN=$4
+	local CONF=$5
+	${BIN} -P "${PIDBASE}"/bird${ID}.pid \
+		   -c "${CONF}" \
+		   -s "${PIDBASE}"/bird${ID}.ctl \
+		   -f
+}
+
+#######################################################################
+#
+# functions to write the configuration for instances
+#
+
+exabgp_config () {
+	local ID=$1
+	local ASN=$2
+	local ADDR=$3
+	
+	local N
+	local P
+	
+	cat <<- EOF
+		group default {
+		  local-address $ADDR;
+		  local-as $ASN;
+		  router-id $ADDR;
+		  
+		  capability {
+		    asn4 enable;
+		  }
+	EOF
+	
+	for N in $(seq 1 $ADV) ; do
+		echo "  static {"
+		echo "    route `advipaddr $ID $N`/32 {"
+		echo "      next-hop $ADDR;"
+		echo "    }"
+		echo "  }" 
+	done
+
+	for P in ${PEEREXP[@]};  do
+		[ "$P" -eq 0 ] && continue;
 		
-		# Edit config to suit.
-		cat > "$CONF" <<- EOF
-			password whatever
-			service advanced-vty
-			!
-			router bgp ${ASN}
-			 bgp router-id ${ADDR}
-			 maximum-paths 32
-			 bgp bestpath as-path multipath-relax
-			 network 10.${H}.1.0/24 pathlimit 1
-			 network 10.${H}.2.0/24 pathlimit 2
-			 network 10.${H}.3.0/24 pathlimit 3
-			 network 10.${H}.0.0/24
-			 neighbor default peer-group
-			 neighbor default update-source ${ADDR}
-			 neighbor default capability orf prefix-list both
-			 neighbor default soft-reconfiguration inbound
-			 neighbor default route-map test out
-			 neighbor ${NEXTADDR} remote-as ${NEXTAS}
-			 neighbor ${NEXTADDR} peer-group default
-			 neighbor ${PREVADDR} remote-as ${PREVAS}
-			 neighbor ${PREVADDR} peer-group default
-			 neighbor ${NEXT2ADDR} remote-as ${NEXT2AS}
-			 neighbor ${NEXT2ADDR} peer-group default
-			 neighbor ${PREV2ADDR} remote-as ${PREV2AS}
-			 neighbor ${PREV2ADDR} peer-group default
-			!
-			 address-family ipv6
-			 network 3ffe:${H}::/48
-			 network 3ffe:${H}:1::/48 pathlimit 1
-			 network 3ffe:${H}:2::/48 pathlimit 3
-			 network 3ffe:${H}:3::/48 pathlimit 3
-			 neighbor default activate
-			 neighbor default capability orf prefix-list both
-			 neighbor default default-originate
-			 neighbor default route-map test out
-			 neighbor ${NEXTADDR} peer-group default
-			 neighbor ${PREVADDR} peer-group default
-			 neighbor ${NEXT2ADDR} peer-group default
-			 neighbor ${PREV2ADDR} peer-group default
-			 exit-address-family
-			!
-			! bgpd still has problems with extcommunity rt/soo
-			route-map test permit 10
-			 set extcommunity rt ${ASN}:1
-			 set extcommunity soo ${ASN}:2
-			 set community ${ASN}:1
-			!
-			line vty
-			 exec-timeout 0 0
-			!
-			end
-		EOF
-		chown ${CHOWNSTR} "$CONF"
+		#local PID=$(( (($ID + $P - 1  + $NUM) % $NUM) + 1  ))
+		local PID=`peeridoff $ID $P`
+		#local PADDR="${PREFIX}${PID}"
+		local PADDR=`id2addr $PID`
+		local PAS=$((${ASBASE} + $PID))
+		
+		echo "  neighbor $PADDR {"
+		#echo "    local-address $ADDR;"
+		#echo "    local-as $ASN;"
+		#echo "    graceful-restart;"
+		#echo "    router-id $ADDR;"
+		echo "    peer-as $PAS;"
+		echo "  }"
+	done
+	
+	for P in ${!EXPEERS[@]};  do
+		echo "  neighbor ${EXPEERS[$P]} {"
+		echo "    peer-as ${EXPEERASES[$P]};"
+		echo "  }"
+	done
+	
+	cat <<- EOF
+		}
+	EOF
+}
+
+quagga_config () {
+	local ID=$1
+	local ASN=$2
+	local ADDR=$3
+	
+	local N
+	local P
+	
+	# Edit config to suit.
+	cat <<- EOF
+		password foo
+		service advanced-vty
+		!
+		router bgp ${ASN}
+		 bgp router-id ${ADDR}
+		 !maximum-paths 32
+		 !bgp bestpath as-path multipath-relax
+	EOF
+
+	for N in $(seq 1 $ADV) ; do
+		echo " network `advipaddr $ID $N`/32"
+	done
+
+	cat <<- EOF
+		 neighbor default peer-group
+		 neighbor default update-source ${ADDR}
+		 neighbor default capability orf prefix-list both
+		 !neighbor default soft-reconfiguration inbound
+		 neighbor default advertisement-interval $MRAI
+		 neighbor default timers connect $CONNECTRETRY
+		 neighbor default route-map test out
+	EOF
+
+	for P in ${PEEREXP[@]};  do
+		[ "$P" -eq 0 ] && continue;
+		
+		local PID=`peeridoff $ID $P`
+		local PADDR=`id2addr $PID`
+		local PAS=$((${ASBASE} + $PID))
+		echo " neighbor ${PADDR} remote-as ${PAS}"
+		echo " neighbor ${PADDR} peer-group default"
+	done
+
+	for P in ${!EXPEERS[@]};  do
+		echo " neighbor ${EXPEERS[$P]} remote-as ${EXPEERASES[$P]}"
+		echo " neighbor ${EXPEERS[$P]} peer-group default"
+	done
+
+	cat <<- EOF
+		!
+		 address-family ipv6
+		 network 3ffe:${ID}::/48
+		 network 3ffe:${ID}:1::/48 pathlimit 1
+		 network 3ffe:${ID}:2::/48 pathlimit 3
+		 network 3ffe:${ID}:3::/48 pathlimit 3
+		 neighbor default activate
+		 neighbor default capability orf prefix-list both
+		 neighbor default default-originate
+		 neighbor default route-map test out
+	EOF
+
+	for P in ${PEEREXP[@]};  do
+		[ "$P" -eq 0 ] && continue;
+		
+		local PID=`peeridoff $ID $P`
+		local PADDR=`id2addr $PID`
+		local PAS=$((${ASBASE} + $PID))
+		echo " neighbor ${PADDR} peer-group default"
+	done
+
+	cat <<- EOF
+		 exit-address-family
+		!
+		! bgpd still has problems with extcommunity rt/soo
+		route-map test permit 10
+		 set extcommunity rt ${ASN}:1
+		 set extcommunity soo ${ASN}:2
+		 set community ${ASN}:1
+		!
+		line vty
+		 exec-timeout 0 0
+		!
+		end
+	EOF
+}
+
+bird_config () {
+	local ID=$1
+	local ASN=$2
+	local ADDR=$3
+
+	cat <<- EOF
+		#log "/var/log/bird.log" all;
+		#debug protocols all;
+
+		# Override router ID
+		router id ${ADDR};
+		listen bgp address ${ADDR};
+		
+		protocol kernel { device routes; import all; }
+		protocol device { import all; }
+		
+		function avoid_martians()
+		prefix set martians;
+		{
+		  martians = [ 
+		  	       224.0.0.0/4+, 240.0.0.0/4+
+		  	     ];
+
+		  # Avoid RFC1918 and similar networks
+		  if net ~ martians then return false;
+		  return true;
+		}
+		
+		filter import_filter
+		{
+		  if ! (avoid_martians()) then reject;
+		  accept;
+		}
+		
+		filter set_comm
+		{
+		  bgp_community.add ((${ASN}, 1));
+		  accept;
+		}
+		
+		template bgp peer_conf {
+		  local as ${ASN};
+		  source address ${ADDR};
+		  import filter import_filter;
+		  export filter set_comm;
+		  multihop;
+		}
+	EOF
+	
+	local P;
+	
+	for P in ${PEEREXP[@]};  do
+		[ "$P" -eq 0 ] && continue;
+		
+		local PID=`peeridoff $ID $P`
+		local PADDR=`id2addr $PID`
+		local PAS=$((${ASBASE} + $PID))
+		echo "protocol bgp from peer_conf {"
+		echo " neighbor ${PADDR} as  ${PAS};"
+		echo "}"
+	done
+	
+	for P in ${!EXPEERS[@]};  do
+		echo "protocol bgp from peer_conf {"
+		echo " neighbor ${EXPEERS[$P]} as ${EXPEERASES[$P]};"
+		echo "}"
+	done
+	
+	
+	for N in $(seq 1 $ADV) ; do
+		echo " network `advipaddr $ID $N`/32"
+	done
+}
+
+#######################################################################
+
+for ID in $(seq 1 $NUM); do
+	BGP_INST=${BGP_INSTANCES[${ID} % ${#BGP_INSTANCES[@]}]}
+	BGPBIN=${BGP_BINS[$BGP_INST]}
+	CONF="${CONFBASE}"/${BGP_INST}_bgpd${ID}.conf
+	ASN=$(($ASBASE + ${ID}))
+	ADDR=`id2addr $ID`
+	
+	#if [ ! -e "$CONF" ] ; then
+	if [ ! -e "$CONF" -o "$CONFWRITE" = "Y" ] ; then 
+		${BGP_CONFIGGEN[$BGP_INST]} $ID $ASN $ADDR > "$CONF"
+		chown $USER:$GROUP "$CONF"
 	fi
 	# You may want to automatically add configure a local address
 	# on a loop interface.
 	#
 	# Solaris: ifconfig vni${H} plumb ${ADDR}/${ADDRPLEN} up
-	# Linux:   ip address add ${ADDR}/${ADDRPLEN} dev lo 2> /dev/null
-	${BGPD} -i "${PIDBASE}"/bgpd${H}.pid \
-		-l ${ADDR} \
-		-f "${CONF}" \
-		-P $((${VTYBASE}+${H})) \
-		-d
+	# Linux:
+	#ip address add ${ADDR}/${ADDRPLEN} dev lo 2> /dev/null
+	
+	ip link add dummy${ID} type dummy 2> /dev/null
+	ip link set dev dummy${ID} up
+	ip address add ${ADDR}/${ADDRPLEN} dev dummy${ID} 2> /dev/null
+	
+	${BGP_LAUNCH[$BGP_INST]} $ID $ASN $ADDR $BGPBIN $CONF &
+	
+	sleep 0.1
 done
+
+echo "multiple-bgpd.sh: waiting..."
+
+wait