WTH-36 BGP

BGP

Estuve mirando el otro día el what the hack 36 y voy a poner un poco paso a paso lo que tuve que hacer

El código está sacado del hack 36 pero yo lo dividí en pequeñas partes. Los comandos se ejecutan en el bash de Azure

Defino las variables para tenerlas a mano

#!/bin/bash
## Define "Global" script variables
nva_publisher=cisco
nva_offer=cisco-c8000v-byol
nva_sku=17_15_01a-byol 

default_username=labadmin

test_vm_image_urn=Ubuntu2404

routers=("1:vng:65501" "2:csr:65502")
connections=("1:2")
rg=bgp
location=westeurope
psk=Microsoft123!

function accept_csr_terms () {
    #USES NVA GLOBALS
    nva_version=$(az vm image list -p $nva_publisher -f $nva_offer -s $nva_sku --all --query '[0].version' -o tsv)
    if [[ -z "$nva_version" ]]
    then
        echo "Could not locate an image version for ${nva_publisher}:${nva_offer}:${nva_sku}, double-check Publisher, offer, and SKU"
        exit 1
    fi
    # Accept terms
    echo "Accepting image terms for ${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version}..."
    az vm image terms accept --urn "${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version}" -o none
    # Verify
    status=$(az vm image terms show --urn "${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version}" --query accepted -o tsv)
    if [[ "$status" != "true" ]]
    then
        echo "Marketplace image terms for ${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version} could not be accepted, do you have access at the subscription level?"
        exit 1
    fi
}

accept_csr_terms
echo "- $nva_version -"

Compruebo los prerequisitos

#!/bin/bash
## Define "Global" script variables
nva_publisher=cisco
nva_offer=cisco-c8000v-byol
nva_sku=17_15_01a-byol 

# Variables
default_username=labadmin

# Define Test VM linux image
test_vm_image_urn=Ubuntu2404
function perform_system_checks () {
    # Verify software dependencies
    for binary in "ssh" "jq" "az" "awk"
    do
        binary_path=$(which "$binary")
        if [[ -z "$binary_path" ]]
        then
            echo "It seems that $binary is not installed in the system. Please install it before trying this script again"
            exit
        fi
    done
    echo "All dependencies checked successfully"

    # Verify az is logged in
    subscription_name=$(az account show --query name -o tsv)
    if [[ -z "$subscription_name" ]]
    then
        echo "It seems you are not logged into Azure with the Azure CLI. Please use \"az login\" before trying this script again"
        exit
    fi

    # Verify required az extensions installed
    for extension_name in "log-analytics"
    do
        az extension add --upgrade --yes --name $extension_name -o none
        extension_version=$(az extension show -n $extension_name --query version -o tsv)
        if [[ -z "$extension_version" ]]
        then
            echo "It seems that the Azure CLI extension \"$extension_name\" is not installed. Please install it with \"az extension add -n $extension_name\" before trying this script again"
            exit
        else
            echo "Azure CLI extension \"$extension_name\" found with version $extension_version"
        fi
    done
}
# Create lab variable from arguments, or use default
if [ -n "$BASH_VERSION" ]; then
    echo "Running on BASH"
elif [ -n "$ZSH_VERSION" ]; then
    echo "Running on ZSH"
fi

Creo el grupo de recursos con una configuración de un Virtual Network Gateway

#!/bin/bash
## Define "Global" script variables
nva_publisher=cisco
nva_offer=cisco-c8000v-byol
nva_sku=17_15_01a-byol 

default_username=labadmin

test_vm_image_urn=Ubuntu2404

routers=("1:vng:65501" "2:csr:65502")
connections=("1:2")
rg=mylab
location=westeurope
psk=Microsoft123!

# Create resource group
echo "Creating resource group \"$rg\" in subscription \"$subscription_name\"..."
az group create -n "$rg" -l "$location" -o none

Creo los recursos

#!/bin/bash
## Define "Global" script variables
nva_publisher=cisco
nva_offer=cisco-c8000v-byol
nva_sku=17_15_01a-byol 
#####
default_username=labadmin

test_vm_image_urn=Ubuntu2404
 
routers=("1:vng:65001" "2:csr:65002" "3:csr:65100")
connections=("1:2" "1:3")
rg=mylab
location=westeurope
psk=Microsoft123!

# Waits until a resource finishes provisioning
# Example: wait_until_finished <resource_id> 
function wait_until_finished () {
     wait_interval=60
     resource_id=$1
     resource_name=$(echo "$resource_id" | cut -d/ -f 9)
     echo "Waiting for resource $resource_name to finish provisioning..."
     start_time=$(date +%s)
     state=$(az resource show --id "$resource_id" --query properties.provisioningState -o tsv 2>/dev/null)
     until [[ "$state" == "Succeeded" ]] || [[ "$state" == "Failed" ]] || [[ -z "$state" ]]
     do
        sleep $wait_interval
        state=$(az resource show --id "$resource_id" --query properties.provisioningState -o tsv 2>/dev/null)
     done
     if [[ -z "$state" ]]
     then
        echo "Something really bad happened..."
     else
        run_time=$(("$(date +%s)" - "$start_time"))
        ((minutes=run_time/60))
        ((seconds=run_time%60))
        echo "Resource $resource_name provisioning state is $state, wait time $minutes minutes and $seconds seconds"
     fi
}

# Remove special characters and empty lines from string
# Used in case the SSH session to IOS returns invalid characters (like CR aka 0x0d), but that doesnt seem to be the case,
#  so left to remove empty strings for the time being
function clean_string () {
    output=$1
    # output=$(echo $output | tr '\r' '\n')                   # Replace \r with \n
    output=$(echo "$output" | tr -d '\r')                   # Delete \r
    # output=$(echo $output | tr -dc '[:alnum:]\ \.\,\n')     # Remove special characters
    output=$(echo "$output" | awk NF)                       # Remove empty lines
    echo "$output"
}

# Wait until a public IP address answers via SSH
# The only thing CSR-specific is the command sent
function wait_until_csr_available () {
    wait_interval=15
    csr_id=$1
    csr_ip=$(az network public-ip show -n "csr${csr_id}-pip" -g "$rg" --query ipAddress -o tsv)
    echo "Waiting for CSR${csr_id} with IP address $csr_ip to answer over SSH..."
    start_time=$(date +%s)
    ssh_command="show version | include uptime"  # 'show version' contains VM name and uptime
    ssh_output=$(ssh -n -o ConnectTimeout=60 -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" "$ssh_command" 2>/dev/null)
    until [[ -n "$ssh_output" ]]
    do
        sleep $wait_interval
        fix_all_nsgs # possible my NSGs are broken?
        ssh_output=$(ssh -n -o ConnectTimeout=60 -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" "$ssh_command" 2>/dev/null)
    done
    run_time=$(("$(date +%s)" - "$start_time"))
    ((minutes=run_time/60))
    ((seconds=run_time%60))
    echo "IP address $csr_ip is available (wait time $minutes minutes and $seconds seconds). Answer to SSH command \"$ssh_command\": $(clean_string "$ssh_output")"
}

# Wait until all VNGs in the router list finish provisioning
function wait_for_csrs_finished () {
    for router in "${routers[@]}"
    do
        type=$(get_router_type  "$router")
        id=$(get_router_id  "$router")
        if [[ "$type" == "csr" ]]
        then
            wait_until_csr_available "$id"
        fi
    done
}

# Add required rules to an NSG to allow traffic between the vnets (RFC1918)
function fix_nsg () {
    nsg_name=$1
    if [[ -z "$2" ]]
    then
        # Grab my client IP for SSH admin rule
        myip=$(curl -s4 "https://api.ipify.org/")
        declare -i i="0"
        until [[ $myip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ $i -gt 5 ]]
        do
            sleep 5
            myip=$(curl -s4 "https://api.ipify.org/")
            ((i++))
        done

        # I tried to get my IP many times. Maybe something bad happened?
        if [[ $i -gt 5 ]]
        then
            echo "Something is wrong with retrieving my public IP from the remote API service at \"https://api.ipify.org/\". Response from service: $myip"
            exit 1
        fi
    else
        # Called by something else that already did the work for me
        myip=$2
    fi

    #echo "Adding SSH permit for ${myip} to NSG ${nsg_name}..."    
    az network nsg rule create --nsg-name "$nsg_name" -g "$rg" -n ssh-inbound-allow --priority 1000 \
        --source-address-prefixes "$myip" --destination-port-ranges 22 --access Allow --protocol Tcp \
        --description "Allow ssh inbound"  -o none

    #echo "Adding RFC1918 prefixes to NSG ${nsg_name}..."
    az network nsg rule create --nsg-name "$nsg_name" -g "$rg" -n Allow_Inbound_RFC1918 --priority 2000 \
        --access Allow --protocol '*' --source-address-prefixes '10.0.0.0/8' '172.16.0.0/12' '192.168.0.0/16' --direction Inbound \
        --destination-address-prefixes '10.0.0.0/8' '172.16.0.0/12' '192.168.0.0/16' --destination-port-ranges '*' -o none
    az network nsg rule create --nsg-name "$nsg_name" -g "$rg" -n Allow_Outbound_RFC1918 --priority 2000 \
        --access Allow --protocol '*' --source-address-prefixes '10.0.0.0/8' '172.16.0.0/12' '192.168.0.0/16' --direction Outbound \
        --destination-address-prefixes '10.0.0.0/8' '172.16.0.0/12' '192.168.0.0/16' --destination-port-ranges '*' -o none
}

# Add permit rules to all nsgs in the RG
function fix_all_nsgs () {
    nsg_list=$(az network nsg list -g "$rg" --query '[].name' -o tsv)

    # Grab my client IP for SSH admin rule
    myip=$(curl -s4 "https://api.ipify.org/")
    declare -i i="0"
    until [[ $myip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ $i -gt 5 ]]
    do
        sleep 5
        myip=$(curl -s4 "https://api.ipify.org/")
        ((i++))
    done

    # I tried to get my IP many times. Maybe something bad happened?
    if [[ $i -gt 5 ]]
    then
        echo "Something is wrong with retrieving my public IP from the remote API service at \"https://api.ipify.org/\". Response from service: $myip"
        exit 1
    fi

    echo "$nsg_list" | while read nsg
    do
        fix_nsg "$nsg" "$myip"
    done
}

# Creates BGP-enabled VNG
# ASN as parameter is optional
function create_vng () {
    id=$1
    asn=$2
    if [[ -n "$2" ]]
    then
        asn=$2
    else
        asn=$(get_router_asn_from_id "${id}")
    fi
    vnet_name=vng${id}
    vnet_prefix=10.${id}.0.0/16
    subnet_prefix=10.${id}.0.0/24
    test_vm_name=testvm${id}
    test_vm_nsg_name="${test_vm_name}-NSG"
    test_vm_nic_name="${test_vm_name}-nic"
    test_vm_pip_name="${test_vm_name}-pip"
    test_vm_size=Standard_B1s
    test_vm_subnet_prefix=10.${id}.1.0/24
    type=$(get_router_type_from_id "$id")
    # Create vnet
    echo "Creating vnet $vnet_name..."
    az network vnet create -g "$rg" -n "$vnet_name" --address-prefix "$vnet_prefix" --subnet-name GatewaySubnet --subnet-prefix "$subnet_prefix" -o none
    # Create test VM (to be able to see effective routes)
    # Not possible to create a test VM while a gateway is Updating, therefore starting the VM creation before the gateway
    test_vm_id=$(az vm show -n "$test_vm_name" -g "$rg" --query id -o tsv 2>/dev/null)
    if [[ -z "$test_vm_id" ]]
    then
        echo "Creating test virtual machine $test_vm_name in vnet $vnet_name in new subnet $test_vm_subnet_prefix..."
        az network nsg create -n "$test_vm_nsg_name" -g "$rg" -l "$location" -o none
        fix_nsg "$test_vm_nsg_name"

        az network vnet subnet create --vnet-name "$vnet_name" -g "$rg" -n testvm --address-prefixes "$test_vm_subnet_prefix" \
            --nsg "$test_vm_nsg_name" -o none

        az network public-ip create -n "$test_vm_pip_name" -g "$rg" -l "$location" --sku standard -o none

        az network nic create -n "$test_vm_nic_name" -g "$rg" -l "$location" --vnet-name "$vnet_name" --subnet testvm \
            --private-ip-address "10.${id}.1.4" --public-ip-address "$test_vm_pip_name" -o none

        # Not using $psk as password because it might not fulfill the password requirements for Azure VMs
        az vm create -n "$test_vm_name" -g "$rg" -l "$location" --image "$test_vm_image_urn" --size "$test_vm_size" \
            --generate-ssh-keys --authentication-type all --admin-username "$default_username" --admin-password "$psk" \
            --nics "$test_vm_nic_name" --no-wait -o none
    else
        echo "Virtual machine $test_vm_name already exists"
    fi
    # Create PIPs for gateway (this gives time as well for the test VM NICs to be created and attached to the subnet)
    echo "Creating public IP addresses for gateway vng${id}..."
    az network public-ip create -g "$rg" -n "vng${id}a" --sku standard -o none
    az network public-ip create -g "$rg" -n "vng${id}b" --sku standard -o none
    # Create VNG
    vng_id=$(az network vnet-gateway show -n "vng${id}" -g "$rg" --query id -o tsv 2>/dev/null)
    if [[ -z "${vng_id}" ]]
    then
        if [[ "$type" ==  "vng" ]] || [[ "$type" ==  "vng2" ]]
        then
            echo "Creating VNG vng${id} in active/active mode..."
            az network vnet-gateway create -g "$rg" --sku VpnGw2 --vpn-gateway-generation Generation2 --gateway-type Vpn --vpn-type RouteBased \
            --vnet "$vnet_name" -n "vng${id}" --asn "$asn" --public-ip-address "vng${id}a" "vng${id}b" --no-wait
        elif [[ "$type" ==  "vng1" ]]
        then
            echo "Creating VNG vng${id} in active/passive mode..."
            az network vnet-gateway create -g "$rg" --sku VpnGw2 --vpn-gateway-generation Generation2 --gateway-type Vpn --vpn-type RouteBased \
            --vnet "$vnet_name" -n "vng${id}" --asn "$asn" --public-ip-address "vng${id}a" --no-wait
        else
            echo "Sorry, I do not understand the VNG type $type"
        fi
    else
        echo "VNG vng${id} already exists"
    fi
}

# Connect 2 VPN gateways to each other
function connect_gws () {
    gw1_id=$1
    gw2_id=$2
    cx_type=$3
    gw1_type=$(get_router_type_from_id "$gw1_id")
    gw2_type=$(get_router_type_from_id "$gw2_id")
    echo "Connecting vng${gw1_id} and vng${gw2_id}. Finding out information about the gateways..." 

    # Using Vnet-to-Vnet connections (no BGP supported)
    # az network vpn-connection create -g $rg -n ${gw1_id}to${gw2_id} \
    #   --enable-bgp --shared-key $psk \
    #   --vnet-gateway1 vng${gw1_id} --vnet-gateway2 vng${gw2_id}

    # Using local gws
    # Create Local Gateways for vpngw1
    vpngw1_name=vng${gw1_id}
    vpngw1_bgp_json=$(az network vnet-gateway show -n "$vpngw1_name" -g "$rg" --query 'bgpSettings' -o json )
    vpngw1_asn=$(echo "$vpngw1_bgp_json" | jq -r '.asn')
    vpngw1_gw0_pip=$(echo "$vpngw1_bgp_json" | jq -r '.bgpPeeringAddresses[0].tunnelIpAddresses[0]')
    vpngw1_gw0_bgp_ip=$(echo "$vpngw1_bgp_json" | jq -r '.bgpPeeringAddresses[0].defaultBgpIpAddresses[0]')
    if [[ ${gw1_type} == "vng" ]] || [[ ${gw1_type} == "vng2" ]]
    then
        vpngw1_gw1_pip=$(echo "$vpngw1_bgp_json" | jq -r '.bgpPeeringAddresses[1].tunnelIpAddresses[0]')
        vpngw1_gw1_bgp_ip=$(echo "$vpngw1_bgp_json" | jq -r '.bgpPeeringAddresses[1].defaultBgpIpAddresses[0]')    
        echo "Extracted info for vpngw1: ASN $vpngw1_asn, GW0 $vpngw1_gw0_pip, $vpngw1_gw0_bgp_ip. GW1 $vpngw1_gw1_pip, $vpngw1_gw1_bgp_ip."
    elif [[ "$gw1_type" ==  "vng1" ]]
    then
        echo "Extracted info for vpngw1: ASN $vpngw1_asn, $vpngw1_gw0_pip, $vpngw1_gw0_bgp_ip."
    else
        echo "Sorry, I do not understand the VNG type $gw1_type"
    fi

    echo "Creating local network gateways for vng${gw1_id}..."
    local_gw_id=$(az network local-gateway show -g "$rg" -n "${vpngw1_name}a" --query id -o tsv 2>/dev/null)
    if [[ -z "$local_gw_id" ]]
    then
        az network local-gateway create -g "$rg" -n "${vpngw1_name}a" --gateway-ip-address "$vpngw1_gw0_pip" \
            --local-address-prefixes "${vpngw1_gw0_bgp_ip}/32" --asn "$vpngw1_asn" --bgp-peering-address "$vpngw1_gw0_bgp_ip" --peer-weight 0 -o none
    else
        echo "Local gateway ${vpngw1_name}a already exists"
    fi
    # Create second local gateway only if the type is "vng" (default) or "vng2"
    if [[ ${gw1_type} == "vng" ]] || [[ ${gw1_type} == "vng2" ]]
    then
        local_gw_id=$(az network local-gateway show -g "$rg" -n "${vpngw1_name}b" --query id -o tsv 2>/dev/null)
        if [[ -z "$local_gw_id" ]]
        then
            az network local-gateway create -g "$rg" -n "${vpngw1_name}b" --gateway-ip-address "$vpngw1_gw1_pip" \
                --local-address-prefixes "${vpngw1_gw1_bgp_ip}/32" --asn "$vpngw1_asn" --bgp-peering-address "$vpngw1_gw1_bgp_ip" --peer-weight 0 -o none
        else
            echo "Local gateway ${vpngw1_name}b already exists"
        fi
    fi
    # Create Local Gateways for vpngw2
    vpngw2_name=vng${gw2_id}
    vpngw2_bgp_json=$(az network vnet-gateway show -n "$vpngw2_name" -g "$rg" -o json --query 'bgpSettings')
    vpngw2_asn=$(echo "$vpngw2_bgp_json" | jq -r '.asn')
    vpngw2_gw0_pip=$(echo "$vpngw2_bgp_json" | jq -r '.bgpPeeringAddresses[0].tunnelIpAddresses[0]')
    vpngw2_gw0_bgp_ip=$(echo "$vpngw2_bgp_json" | jq -r '.bgpPeeringAddresses[0].defaultBgpIpAddresses[0]')
    echo "Creating local network gateways for vng${gw2_id}..."
    local_gw_id=$(az network local-gateway show -g "$rg" -n "${vpngw2_name}a" --query id -o tsv 2>/dev/null)
    if [[ ${gw2_type} == "vng" ]] || [[ ${gw2_type} == "vng2" ]]
    then
        vpngw2_gw1_pip=$(echo "$vpngw2_bgp_json" | jq -r '.bgpPeeringAddresses[1].tunnelIpAddresses[0]')
        vpngw2_gw1_bgp_ip=$(echo "$vpngw2_bgp_json" | jq -r '.bgpPeeringAddresses[1].defaultBgpIpAddresses[0]')
        echo "Extracted info for vpngw2: ASN $vpngw2_asn GW0 $vpngw2_gw0_pip, $vpngw2_gw0_bgp_ip. GW1 $vpngw2_gw1_pip, $vpngw2_gw1_bgp_ip."
    elif [[ "$gw2_type" ==  "vng1" ]]
    then
        echo "Extracted info for vpngw1: ASN $vpngw2_asn, $vpngw2_gw0_pip, $vpngw2_gw0_bgp_ip."
    else
        echo "Sorry, I do not understand the VNG type $gw2_type"
    fi
    if [[ -z "$local_gw_id" ]]
    then
        az network local-gateway create -g "$rg" -n "${vpngw2_name}a" --gateway-ip-address "$vpngw2_gw0_pip" \
            --local-address-prefixes "${vpngw2_gw0_bgp_ip}/32" --asn "$vpngw2_asn" --bgp-peering-address "$vpngw2_gw0_bgp_ip" --peer-weight 0 -o none
    else
        echo "Local gateway ${vpngw2_name}a already exists"
    fi
    # Create second local gateway only if the type is "vng" (default) or "vng2"
    if [[ ${gw2_type} == "vng" ]] || [[ ${gw2_type} == "vng2" ]]
    then
        local_gw_id=$(az network local-gateway show -g "$rg" -n "${vpngw2_name}b" --query id -o tsv 2>/dev/null)
        if [[ -z "$local_gw_id" ]]
        then
            az network local-gateway create -g "$rg" -n "${vpngw2_name}b" --gateway-ip-address "$vpngw2_gw1_pip" \
                --local-address-prefixes "${vpngw2_gw1_bgp_ip}/32" --asn "$vpngw2_asn" --bgp-peering-address "$vpngw2_gw1_bgp_ip" --peer-weight 0 -o none
        else
            echo "Local gateway ${vpngw2_name}b already exists"
        fi
    fi
    # Create connections
    echo "Connecting vng${gw1_id} to local gateways for vng${gw2_id}..."
    if [[ "$cx_type" == "nobgp" ]]
    then
        bgp_option=""
    else
        bgp_option="--enable-bgp"
    fi
    # 1->2A
    az network vpn-connection create -n "vng${gw1_id}tovng${gw2_id}a" -g "$rg" --vnet-gateway1 "vng${gw1_id}" \
        --shared-key "$psk" --local-gateway2 "${vpngw2_name}a" $bgp_option -o none
    az network vpn-connection ipsec-policy add --connection-name "vng${gw1_id}tovng${gw2_id}a" -g "$rg" \
        --ike-encryption GCMAES256 --ike-integrity GCMAES256 --dh-group DHGroup14 \
        --ipsec-encryption GCMAES256 --ipsec-integrity GCMAES256 --pfs-group None \
        --sa-lifetime 102400 --sa-max-size 27000 --output none
    # 1->2B
    if [[ ${gw2_type} == "vng" ]] || [[ ${gw2_type} == "vng2" ]]
    then
        az network vpn-connection create -n "vng${gw1_id}tovng${gw2_id}b" -g "$rg" --vnet-gateway1 "vng${gw1_id}" \
            --shared-key "$psk" --local-gateway2 "${vpngw2_name}b" $bgp_option -o none
        az network vpn-connection ipsec-policy add --connection-name "vng${gw1_id}tovng${gw2_id}b" -g "$rg" \
            --ike-encryption GCMAES256 --ike-integrity GCMAES256 --dh-group DHGroup14 \
            --ipsec-encryption GCMAES256 --ipsec-integrity GCMAES256 --pfs-group None \
            --sa-lifetime 102400 --sa-max-size 27000 --output none
    fi
    echo "Connecting vng${gw2_id} to local gateways for vng${gw1_id}..."
    # 2->1A
    az network vpn-connection create -n "vng${gw2_id}tovng${gw1_id}a" -g "$rg" --vnet-gateway1 "vng${gw2_id}" \
        --shared-key "$psk" --local-gateway2 "${vpngw1_name}a" $bgp_option -o none
    az network vpn-connection ipsec-policy add --connection-name "vng${gw2_id}tovng${gw1_id}a" -g "$rg" \
        --ike-encryption GCMAES256 --ike-integrity GCMAES256 --dh-group DHGroup14 \
        --ipsec-encryption GCMAES256 --ipsec-integrity GCMAES256 --pfs-group None \
        --sa-lifetime 102400 --sa-max-size 27000 --output none
    # 2->1B
    if [[ ${gw1_type} == "vng" ]] || [[ ${gw1_type} == "vng2" ]]
    then
        az network vpn-connection create -n "vng${gw2_id}tovng${gw1_id}b" -g "$rg" --vnet-gateway1 "vng${gw2_id}" \
            --shared-key "$psk" --local-gateway2 "${vpngw1_name}b" $bgp_option -o none
        az network vpn-connection ipsec-policy add --connection-name "vng${gw2_id}tovng${gw1_id}b" -g "$rg" \
            --ike-encryption GCMAES256 --ike-integrity GCMAES256 --dh-group DHGroup14 \
            --ipsec-encryption GCMAES256 --ipsec-integrity GCMAES256 --pfs-group None \
            --sa-lifetime 102400 --sa-max-size 27000 --output none
    fi
}

# Deploy a VM in CSR's vnet, and configure CSR to route traffic
function create_vm_in_csr_vnet () {
    csr_id=$1
    csr_name=csr${csr_id}
    vnet_name=${csr_name}
    csr_vnet_prefix="10.${csr_id}.0.0/16"
    csr_subnet_prefix="10.${csr_id}.0.0/24"
    csr_bgp_ip="10.${csr_id}.0.10"
    vm_subnet_prefix="10.${csr_id}.1.0/24"
    vm_subnet_name=testvm
    vm_name=testvm${csr_id}
    vm_nsg_name="${vm_name}-NSG"
    vm_nic_name="${vm_name}-nic"
    vm_pip_name="${vm_name}-pip"
    vm_size=Standard_B1s
    rt_name="${vm_name}-rt"
    # Create VM
    test_vm_id=$(az vm show -n "$vm_name" -g "$rg" --query id -o tsv 2>/dev/null)
    if [[ -z "$test_vm_id" ]]
    then
        echo "Creating VM $vm_name in vnet $vnet_name..."
        az network nsg create -n "$vm_nsg_name" -g "$rg" -l "$location" -o none
        fix_nsg "$vm_nsg_name"

        az network vnet subnet create --vnet-name "$vnet_name" -g "$rg" -n testvm --address-prefixes "$vm_subnet_prefix" \
            --nsg "$vm_nsg_name" -o none

        az network public-ip create -n "$vm_pip_name" -g "$rg" -l "$location" --sku standard -o none

        az network nic create -n "$vm_nic_name" -g "$rg" -l "$location" --vnet-name "$vnet_name" --subnet $vm_subnet_name \
            --private-ip-address "10.${id}.1.4" --public-ip-address "$vm_pip_name" -o none
        
        az vm create -n "$vm_name" -g "$rg" -l "$location" --image "$test_vm_image_urn" --size "$vm_size" \
            --generate-ssh-keys --authentication-type all --admin-username "$default_username" --admin-password "$psk" \
            --nics "$vm_nic_name" --no-wait -o none
        az network route-table create -n "$rt_name" -g "$rg" -l "$location" -o none
        az network route-table route create -n localrange -g "$rg" --route-table-name "$rt_name" \
            --address-prefix "10.0.0.0/8" --next-hop-type VirtualAppliance --next-hop-ip-address "$csr_bgp_ip" -o none
        az network vnet subnet update -n "$vm_subnet_name" --vnet-name "$vnet_name" -g "$rg" --route-table "$rt_name" -o none
    else
        echo "Virtual machine $vm_name already exists"
    fi
}

function accept_csr_terms () {
    #USES NVA GLOBALS
    nva_version=$(az vm image list -p $nva_publisher -f $nva_offer -s $nva_sku --all --query '[0].version' -o tsv)
    if [[ -z "$nva_version" ]]
    then
        echo "Could not locate an image version for ${nva_publisher}:${nva_offer}:${nva_sku}, double-check Publisher, offer, and SKU"
        exit 1
    fi
    # Accept terms
    echo "Accepting image terms for ${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version}..."
    az vm image terms accept --urn "${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version}" -o none
    # Verify
    status=$(az vm image terms show --urn "${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version}" --query accepted -o tsv)
    if [[ "$status" != "true" ]]
    then
        echo "Marketplace image terms for ${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version} could not be accepted, do you have access at the subscription level?"
        exit 1
    fi
}

# Creates a CSR NVA
# Example: create_csr 1
function create_csr () {
    # USES NVA GLOBALS
    csr_id=$1
    csr_name=csr${csr_id}
    csr_nsg_name="${csr_name}-NSG"
    csr_nic_name="${csr_name}-nic"
    csr_pip_name="${csr_name}-pip"
    csr_vnet_prefix="10.${csr_id}.0.0/16"
    csr_subnet_prefix="10.${csr_id}.0.0/24"
    csr_bgp_ip="10.${csr_id}.0.10"
    
    nva_version=$(az vm image list -p $nva_publisher -f $nva_offer -s $nva_sku --all --query '[0].version' -o tsv)
    nva_size=Standard_B2ms
    # Create CSR
    echo "Creating VM csr${csr_id}-nva in Vnet $csr_vnet_prefix..."
    vm_id=$(az vm show -n "csr${csr_id}-nva" -g "$rg" --query id -o tsv 2>/dev/null)
    if [[ -z "$vm_id" ]]
    then
        az network nsg create -n "$csr_nsg_name" -g "$rg" -l "$location" -o none
        az network nsg rule create --nsg-name "$csr_nsg_name" -g "$rg" -n ike --priority 1100 \
            --source-address-prefixes Internet --destination-port-ranges 500 4500 --access Allow --protocol Udp \
            --description "UDP ports for IKE VPN" -o none
        fix_nsg "$csr_nsg_name"

        az network vnet create -n "$csr_name" -g "$rg" -l "$location" --address-prefix "$csr_vnet_prefix" --subnet-name nva \
            --subnet-prefixes "$csr_subnet_prefix" --nsg "$csr_nsg_name" -o none

        az network public-ip create -n "$csr_pip_name" -g "$rg" -l "$location" --sku standard -o none

        az network nic create -n "$csr_nic_name" -g "$rg" -l "$location" --vnet-name "$csr_name" --subnet nva \
            --private-ip-address "$csr_bgp_ip" --public-ip-address "$csr_pip_name" --ip-forwarding true -o none

        az vm create -n "csr${csr_id}-nva" -g "$rg" -l "$location" --image "${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version}" --size "$nva_size" \
            --generate-ssh-keys --admin-username "$default_username" --nics "$csr_nic_name" --no-wait -o none
    else
        echo "VM csr${csr_id}-nva already exists"
    fi

    # Get public IP
    csr_ip=$(az network public-ip show -n "csr${csr_id}-pip" -g "$rg" --query ipAddress -o tsv)
    # Create Local Network Gateway
    echo "CSR created with IP address $csr_ip. Creating Local Network Gateway now..."
    asn=$(get_router_asn_from_id "$csr_id")
    local_gw_id=$(az network local-gateway show -g "$rg" -n "${csr_name}" --query id -o tsv 2>/dev/null)
    if [[ -z "$local_gw_id" ]]
    then
        az network local-gateway create -g "$rg" -n "$csr_name" --gateway-ip-address "$csr_ip" \
            --local-address-prefixes "${csr_bgp_ip}/32" --asn "$asn" --bgp-peering-address "$csr_bgp_ip" --peer-weight 0 -o none
    else
        echo "Local gateway ${csr_name} already exists"
    fi
}

# Connects a CSR to one VNG
function connect_csr () {
    csr_id=$1
    gw_id=$2
    cx_type=$3
    gw_type=$(get_router_type_from_id "$gw_id")
    # csr_asn=$(get_router_asn_from_id $csr_id)  # Not used

    vpngw_name=vng${gw_id}
    vpngw_bgp_json=$(az network vnet-gateway show -n "$vpngw_name" -g "$rg" --query 'bgpSettings' -o json)
    vpngw_asn=$(echo "$vpngw_bgp_json" | jq -r '.asn')
    vpngw_gw0_pip=$(echo "$vpngw_bgp_json" | jq -r '.bgpPeeringAddresses[0].tunnelIpAddresses[0]')
    vpngw_gw0_bgp_ip=$(echo "$vpngw_bgp_json" | jq -r '.bgpPeeringAddresses[0].defaultBgpIpAddresses[0]')
    if [[ ${gw_type} == "vng" ]] || [[ ${gw_type} == "vng2" ]]
    then
        vpngw_gw1_pip=$(echo "$vpngw_bgp_json" | jq -r '.bgpPeeringAddresses[1].tunnelIpAddresses[0]')
        vpngw_gw1_bgp_ip=$(echo "$vpngw_bgp_json" | jq -r '.bgpPeeringAddresses[1].defaultBgpIpAddresses[0]')    
        echo "Extracted info for vpngw: ASN $vpngw_asn, GW0 $vpngw_gw0_pip, $vpngw_gw0_bgp_ip. GW1 $vpngw_gw1_pip, $vpngw_gw1_bgp_ip."
    elif [[ "$gw_type" ==  "vng1" ]]
    then
        echo "Extracted info for vpngw: ASN $vpngw_asn, $vpngw_gw0_pip, $vpngw_gw0_bgp_ip."
    else
        echo "Sorry, I do not understand the VNG type $gw_type"
    fi

    # Tunnels for vpngw (IKEv2)
    echo "Configuring tunnels between CSR $csr_id and VPN GW $gw_id"
    config_csr_tunnel "$csr_id" "${csr_id}${gw_id}0" "$vpngw_gw0_pip" "$vpngw_gw0_bgp_ip" "$(get_router_asn_from_id "$gw_id")" ikev2 "$cx_type"
    if [[ ${gw_type} == "vng" ]] || [[ ${gw_type} == "vng2" ]]
    then
        config_csr_tunnel "$csr_id" "${csr_id}${gw_id}1" "$vpngw_gw1_pip" "$vpngw_gw1_bgp_ip" "$(get_router_asn_from_id "$gw_id")" ikev2 "$cx_type"
    fi

    # Connect Local GWs to VNGs
    echo "Creating VPN connections in Azure..."
    if [[ "$cx_type" == "nobgp" ]]
    then
        bgp_option=""
    else
        bgp_option="--enable-bgp"
    fi
    az network vpn-connection create -n "vng${gw_id}tocsr${csr_id}" -g "$rg" --vnet-gateway1 "vng${gw_id}" \
        --shared-key "$psk" --local-gateway2 "csr${csr_id}" $bgp_option -o none
    az network vpn-connection ipsec-policy add --connection-name "vng${gw_id}tocsr${csr_id}" -g "$rg" \
        --ike-encryption GCMAES256 --ike-integrity GCMAES256 --dh-group DHGroup14 \
        --ipsec-encryption GCMAES256 --ipsec-integrity GCMAES256 --pfs-group None \
        --sa-lifetime 102400 --sa-max-size 27000 --output none
    if [[ -n "$gw2_id" ]]
    then
        az network vpn-connection create -n "vng${gw2_id}tocsr${csr_id}" -g "$rg" --vnet-gateway1 "vng${gw2_id}" \
            --shared-key "$psk" --local-gateway2 "csr${csr_id}" $bgp_option -o none
        az network vpn-connection ipsec-policy add --connection-name "vng${gw2_id}tocsr${csr_id}" -g "$rg" \
            --ike-encryption GCMAES256 --ike-integrity GCMAES256 --dh-group DHGroup14 \
            --ipsec-encryption GCMAES256 --ipsec-integrity GCMAES256 --pfs-group None \
            --sa-lifetime 102400 --sa-max-size 27000 --output none
    fi
}

# Run "show interface ip brief" on CSR
function sh_csr_int () {
    csr_id=$1
    csr_ip=$(az network public-ip show -n "csr${csr_id}-pip" -g "$rg" -o tsv --query ipAddress)
    ssh -n -o StrictHostKeyChecking=no -o ServerAliveInterval=60 -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" "sh ip int b" 2>/dev/null
}

# Open an interactive SSH session to a CSR
function ssh_csr () {
    csr_id=$1
    csr_ip=$(az network public-ip show -n "csr${csr_id}-pip" -g "$rg" -o tsv --query ipAddress)
    ssh "$csr_ip"
}

# Deploy baseline, VPN, and BGP config to a Cisco CSR
function config_csr_base () {
    csr_id=$1
    csr_ip=$(az network public-ip show -n "csr${csr_id}-pip" -g "$rg" -o tsv --query ipAddress)
    asn=$(get_router_asn_from_id "$csr_id")
    # Grab client IP for static route
        myip=$(curl -s4 "https://api.ipify.org/")
        until [[ $myip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]
        do
            sleep 5
            myip=$(curl -s4 "https://api.ipify.org/")
    done
    
    default_gateway="10.${csr_id}.0.1"

    echo "Configuring CSR${csr_id} at ${csr_ip} for necessary licensing features to use VPN and rebooting..."
    wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
    ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
    config t
        license boot level network-advantage addon dna-advantage
        do wr mem
        event manager applet reboot-now
        event timer watchdog time 5
        action 1.0 reload
        end
EOF

    sleep 60 # Avoid re-checking availability too early
    wait_until_csr_available "${csr_id}"

    echo "Configuring CSR${csr_id} at ${csr_ip} for VPN and BGP..."
    username=$(whoami)
    password=$psk
    wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
    ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
    config t
      username ${username} password 0 ${password}
      username ${username} privilege 15
      username ${default_username} password 0 ${password}
      username ${default_username} privilege 15
      no ip domain lookup
      crypto ikev2 keyring azure-keyring
      crypto ikev2 proposal azure-proposal
        encryption aes-gcm-256
        prf sha256
        group 14
      crypto ikev2 policy azure-policy
        proposal azure-proposal
      crypto ikev2 profile azure-profile
        match address local interface GigabitEthernet1
        authentication remote pre-share
        authentication local pre-share
        keyring local azure-keyring
      crypto ipsec transform-set azure-ipsec-proposal-set esp-gcm 256
        mode tunnel
      crypto ipsec profile azure-vti
        set security-association lifetime kilobytes 102400000
        set transform-set azure-ipsec-proposal-set
        set ikev2-profile azure-profile
      crypto isakmp policy 1
        encr aes
        authentication pre-share
        group 14
      crypto ipsec transform-set csr-ts esp-aes esp-sha-hmac
        mode tunnel
      crypto ipsec profile csr-profile
        set transform-set csr-ts
      router bgp $asn
        bgp router-id interface GigabitEthernet1
        network 10.${csr_id}.0.0 mask 255.255.0.0
        bgp log-neighbor-changes
        maximum-paths eibgp 4
      ip route ${myip} 255.255.255.255 ${default_gateway}
      ip route 10.${csr_id}.0.0 255.255.0.0 ${default_gateway}
      line vty 0 20
        exec-timeout 0 0
    end
    wr mem
EOF
}

# Configure a tunnel a BGP neighbor for a specific remote endpoint on a Cisco CSR
# "mode" can be either IKEv2 or ISAKMP. I havent been able to bring up 
#   a CSR-to-CSR connection using IKEv2, hence my workaround is using isakmp.
function config_csr_tunnel () {
    csr_id=$1
    tunnel_id=$2
    public_ip=$3
    private_ip=$4
    remote_asn=$5
    mode=$6
    cx_type=$7  # Can be none, ospf, bgpospf or empty (bgp per default)
    if [[ -z "$mode" ]]
    then
        mode=ikev2
    fi
    if [[ "$mode" == "ikev2" ]]
    then
        ipsec_profile="azure-vti"
    else
        ipsec_profile="csr-profile"
    fi
    asn=$(get_router_asn_from_id "${csr_id}")
    default_gateway="10.${csr_id}.0.1"
    csr_ip=$(az network public-ip show -n "csr${csr_id}-pip" -g "$rg" -o tsv --query ipAddress)
    echo "Configuring tunnel ${tunnel_id} in CSR${csr_id} at ${csr_ip}..."
    wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
    ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
    config t
      crypto ikev2 keyring azure-keyring
        peer ${public_ip}
          address ${public_ip}
          pre-shared-key ${psk}
      crypto ikev2 profile azure-profile
        match identity remote address ${public_ip} 255.255.255.255
      crypto isakmp key ${psk} address ${public_ip}
      interface Tunnel${tunnel_id}
        ip unnumbered GigabitEthernet1
        ip tcp adjust-mss 1350
        tunnel source GigabitEthernet1
        tunnel mode ipsec ipv4
        tunnel destination ${public_ip}
        tunnel protection ipsec profile ${ipsec_profile}
      ip route ${private_ip} 255.255.255.255 Tunnel${tunnel_id}
      ip route ${public_ip} 255.255.255.255 ${default_gateway}
    end
    wr mem
EOF
    if [[ -z "$cx_type" ]] || [[ "$cx_type" == "bgp" ]] || [[ "$cx_type" == "bgpospf" ]]
    then
      echo "Configuring BGP on tunnel ${tunnel_id} in CSR ${csr_ip}..."
      wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
      ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
        config t
          router bgp ${asn}
            neighbor ${private_ip} remote-as ${remote_asn}
            neighbor ${private_ip} update-source GigabitEthernet1
        end
        wr mem
EOF
      if [[ "$asn" == "$remote_asn" ]]
      then
        # iBGP
        wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
        ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
            config t
              router bgp ${asn}
                neighbor ${private_ip} next-hop-self
            end
            wr mem
EOF
      else
        # eBGP
        wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
        ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
            config t
              router bgp ${asn}
                neighbor ${private_ip} ebgp-multihop 5
            end
            wr mem
EOF
      fi
    elif [[ "$cx_type" == "ospf" ]] || [[ "$cx_type" == "bgpospf" ]]
    then
      echo "Configuring OSPF on tunnel ${tunnel_id} in CSR ${csr_ip}..."
      wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
      ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
        config t
          router ospf 100
            no passive-interface Tunnel${tunnel_id}
            network 10.${csr_id}.0.0 255.255.0.0 area 0
        end
        wr mem
EOF
    elif [[ "$cx_type" == "static" ]]  ## Not used
    then
      echo "Configuring static routes for tunnel ${tunnel_id} in CSR ${csr_ip}..."
      remote_id=$(echo "$tunnel_id" | head -c 2 | tail -c 1) # This only works with a max of 9 routers
      echo "Configuring OSPF on tunnel ${tunnel_id} in CSR ${csr_ip}..."
      wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
      ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
        config t
          ip route 10.${remote_id}.0.0 255.255.0.0 Tunnel${tunnel_id}
        end
        wr mem
EOF
    else
        echo "No routing protocol configured on ${tunnel_id} in CSR ${csr_ip}..."
    fi
}

# Connect two CSRs to each other over IPsec and BGP
function connect_csrs () {
    csr1_id=$1
    csr2_id=$2
    cx_type=$3
    csr1_ip=$(az network public-ip show -n "csr${csr1_id}-pip" -g "$rg" -o tsv --query ipAddress)
    csr2_ip=$(az network public-ip show -n "csr${csr2_id}-pip" -g "$rg" -o tsv --query ipAddress)
    csr1_asn=$(get_router_asn_from_id "${csr1_id}")
    csr2_asn=$(get_router_asn_from_id "${csr2_id}")
    csr1_bgp_ip="10.${csr1_id}.0.10"
    csr2_bgp_ip="10.${csr2_id}.0.10"
    # Tunnel from csr1 to csr2 (using ISAKMP instead of IKEv2)
    tunnel_id=${csr1_id}${csr2_id}
    config_csr_tunnel "$csr1_id" "$tunnel_id" "$csr2_ip" "$csr2_bgp_ip" "$csr2_asn" isakmp "$cx_type"
    # Tunnel from csr2 to csr1 (using ISAKMP instead of IKEv2)
    tunnel_id=${csr2_id}${csr1_id}
    config_csr_tunnel "$csr2_id" "$tunnel_id" "$csr1_ip" "$csr1_bgp_ip" "$csr1_asn" isakmp "$cx_type"
}

# Configure logging
function init_log () {
    logws_name=$(az monitor log-analytics workspace list -g "$rg" --query '[0].name' -o tsv)
    if [[ -z "$logws_name" ]]
    then
        logws_name=log$RANDOM
        echo "Creating LA workspace $logws_name..."
        az monitor log-analytics workspace create -n $logws_name -g "$rg" -o none
    else
        echo "Found log analytics workspace $logws_name"
    fi
    logws_id=$(az resource list -g "$rg" -n "$logws_name" --query '[].id' -o tsv)
    logws_customerid=$(az monitor log-analytics workspace show -n "$logws_name" -g "$rg" --query customerId -o tsv)
}

# Configures a certain VNG for logging to a previously created LA workspace
function log_gw () {
  gw_id=$1
  vpngw_id=$(az network vnet-gateway show -n "vng${gw_id}" -g "$rg" --query id -o tsv)
  echo "Configuring diagnostic settings for gateway vng${gw_id}"
  az monitor diagnostic-settings create -n mydiag --resource "$vpngw_id" --workspace "$logws_id" \
      --metrics '[{"category": "AllMetrics", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false }}]' \
      --logs '[{"category": "GatewayDiagnosticLog", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false}}, 
              {"category": "TunnelDiagnosticLog", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false}},
              {"category": "RouteDiagnosticLog", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false}},
              {"category": "IKEDiagnosticLog", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false}}]' -o none
}

# Gets IKE logs from Log Analytics
# Possible improvements:
# - Supply time and max number of msgs as parameters
function get_ike_logs () {
  query='AzureDiagnostics 
  | where ResourceType == "VIRTUALNETWORKGATEWAYS" 
  | where Category == "IKEDiagnosticLog" 
  | where TimeGenerated >= ago(5m) 
  | project Message
  | take 20'
  az monitor log-analytics query -w "$logws_customerid" --analytics-query "$query" -o tsv
}

# Creates a connection between two routers
# The function to call depends on whether they are CSRs or VNGs
function create_connection () {
      # Split router_params, different syntax for BASH and ZSH
      if [ -n "$BASH_VERSION" ]; then
          arr_opt=a
      elif [ -n "$ZSH_VERSION" ]; then
          arr_opt=A
      fi
      IFS=':' read -r"$arr_opt" router_params <<< "$1"
      if [ -n "$BASH_VERSION" ]; then
          router1_id="${router_params[0]}"
          router2_id="${router_params[1]}"
          cx_type="${router_params[2]}"
      elif [ -n "$ZSH_VERSION" ]; then
          router1_id="${router_params[1]}"
          router2_id="${router_params[2]}"
          cx_type="${router_params[3]}"
      else
          echo "Error identifying shell, it looks like neither BASH or ZSH!"
      fi
      router1_type=$(get_router_type_from_id "$router1_id")
      router2_type=$(get_router_type_from_id "$router2_id")
      echo "Creating connection between ${router1_type} ${router1_id} and ${router2_type} ${router2_id}, type \"${cx_type}\"..."
      if [[ "$router1_type" == "vng" ]] || [[ "$router1_type" == "vng1" ]] || [[ "$router1_type" == "vng2" ]]
      then
          # VNG-to-VNG
          if [[ "$router2_type" == "vng" ]] || [[ "$router2_type" == "vng1" ]] || [[ "$router2_type" == "vng2" ]]
          then
              connect_gws "$router1_id" "$router2_id" "$cx_type"
          # VNG-to-CSR
          else
              connect_csr "$router2_id" "$router1_id" "$cx_type"
          fi
      else
          # CSR-to-VNG
          if [[ "$router2_type" == "vng" ]] || [[ "$router2_type" == "vng1" ]] || [[ "$router2_type" == "vng2" ]]
          then
              connect_csr "$router1_id" "$router2_id" "$cx_type"
          # CSR-to-CSR
          else
              connect_csrs "$router1_id" "$router2_id" "$cx_type"
          fi
      fi
}

# Get router type for a specific router ID
function get_router_type_from_id () {
    id=$1
    for router in "${routers[@]}"
    do
        this_id=$(get_router_id "$router")
        if [[ "$id" -eq "$this_id" ]]
        then
            get_router_type "$router"
        fi
    done
}

# Get router ASN for a specific router ID
function get_router_asn_from_id () {
    id=$1
    for router in "${routers[@]}"
    do
        this_id=$(get_router_id  "$router")
        if [[ "$id" -eq "$this_id" ]]
        then
            get_router_asn "$router"
        fi
    done
}

# Verifies a VNG or CSR has been created in Azure
function verify_router () {
    router=$1
    echo "Verifying $router..."
    type=$(get_router_type "$router")
    id=$(get_router_id  "$router")
    if [[ "$type" == "csr" ]]
    then
        vm_status=$(az vm show -n "csr${id}-nva" -g "$rg" --query provisioningState -o tsv)
        ip=$(az network public-ip show -n "csr${id}-pip" -g "$rg" --query ipAddress -o tsv)
        echo "VM csr${id}-nva status is ${vm_status}, public IP is ${ip}"
    else
        gw_status=$(az network vnet-gateway show -n "vng${id}" -g "$rg" --query provisioningState -o tsv)
        # ip_a=$(az network public-ip show -n "vng${id}a" -g "$rg" --query ipAddress -o tsv)
        # ip_b=$(az network public-ip show -n "vng${id}b" -g "$rg" --query ipAddress -o tsv)
        # echo "Gateway vng${id} status is ${gw_status}, public IPs are ${ip_a} and ${ip_b}"
        echo "Gateway vng${id} status is ${gw_status}"
    fi
}

# Showing BGP neighbors for a certain router
function show_bgp_neighbors () {
    router=$1
    echo "Getting BGP neighbors for router $router..."
    type=$(get_router_type "$router")
    id=$(get_router_id  "$router")
    if [[ "$type" == "csr" ]]
    then
        ip=$(az network public-ip show -n "csr${id}-pip" -g "$rg" --query ipAddress -o tsv)
        neighbors=$(ssh -n -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${ip}" "show ip bgp summary | begin Neighbor" 2>/dev/null)
        echo "BGP neighbors for csr${id}-nva (${ip}):"
        clean_string "$neighbors"
    else
        neighbors=$(az network vnet-gateway list-bgp-peer-status -n "vng${id}" -g "$rg" -o table)
        echo "BGP neighbors for vng${id}:"
        echo "$neighbors"
    fi
}

# Create a VNG or a CSR, configuration given by a colon-separated parameter string (like "1:vng:65515")
function create_router () {
    type=$(get_router_type  "$router")
    id=$(get_router_id  "$router")
    asn=$(get_router_asn  "$router")
    echo "Creating router of type $type, id $id, ASN $asn..."
    case $type in
    "vng"|"vng1"|"vng2")
        create_vng "$id" "$asn"
        ;;
    "csr")
        create_csr "$id"
        ;;
    esac
}

# Gets the type out of a router configuration (like csr in "1:csr:65515")
function get_router_type () {
      # Split router_params, different syntax for BASH and ZSH
      if [ -n "$BASH_VERSION" ]; then
          arr_opt=a
      elif [ -n "$ZSH_VERSION" ]; then
          arr_opt=A
      fi
      IFS=':' read -r"$arr_opt" router_params <<< "$1"
      # In BASH array indexes start with 0, in ZSH with 1
      if [ -n "$BASH_VERSION" ]; then
          echo "${router_params[1]}"
      elif [ -n "$ZSH_VERSION" ]; then
          echo "${router_params[2]}"
      fi
}

# Gets the ID out of a router configuration (like 1 in "1:csr:65515")
function get_router_id () {
      # Split router_params, different syntax for BASH and ZSH
      if [ -n "$BASH_VERSION" ]; then
          arr_opt=a
      elif [ -n "$ZSH_VERSION" ]; then
          arr_opt=A
      fi
      IFS=':' read -r"$arr_opt" router_params <<< "$1"
      # In BASH array indexes start with 0, in ZSH with 1
      if [ -n "$BASH_VERSION" ]; then
          echo "${router_params[0]}"
      elif [ -n "$ZSH_VERSION" ]; then
          echo "${router_params[1]}"
      fi
}

# Gets the ASN out of a router configuration (like 65001 in "1:csr:65001")
function get_router_asn () {
      # Split router_params, different syntax for BASH and ZSH
      if [ -n "$BASH_VERSION" ]; then
          arr_opt=a
      elif [ -n "$ZSH_VERSION" ]; then
          arr_opt=A
      fi
      IFS=':' read -r"$arr_opt" router_params <<< "$1"
      # In BASH array indexes start with 0, in ZSH with 1
      if [ -n "$BASH_VERSION" ]; then
          echo "${router_params[2]}"
      elif [ -n "$ZSH_VERSION" ]; then
          echo "${router_params[3]}"
      fi
}

# Wait until all VNGs in the router list finish provisioning
function wait_for_gws_finished () {
    for router in "${routers[@]}"
    do
        type=$(get_router_type "$router")
        id=$(get_router_id "$router")
        if [[ "$type" == "vng" ]] || [[ "$type" == "vng1" ]] || [[ "$type" == "vng2" ]]
        then
            vng_name=vng${id}
            vpngw_id=$(az network vnet-gateway show -n "$vng_name" -g "$rg" --query id -o tsv)
            wait_until_finished "$vpngw_id"
        fi
    done
}

# Configure logging to LA for all gateways
function config_gw_logging () {
    for router in "${routers[@]}"
    do
        type=$(get_router_type "$router")
        id=$(get_router_id "$router")
        if [[ "$type" == "vng" ]] || [[ "$type" == "vng1" ]] || [[ "$type" == "vng2" ]]
        then
            log_gw "$id"
        fi
    done
}

# Deploy base config for all CSRs
function config_csrs_base () {
    for router in "${routers[@]}"
    do
        type=$(get_router_type  "$router")
        id=$(get_router_id  "$router")
        if [[ "$type" == "csr" ]]
        then
            config_csr_base "$id"
            create_vm_in_csr_vnet "$id"
        fi
    done
}

# Add an additional users to each test VM
function add_users_to_vms () {
    vm_list=$(az vm list -o tsv -g "$rg" --query "[?contains(name,'testvm')].name")
    while IFS= read -r vm_name; do
        az vm user update -g $rg -u "$default_username" -p "$psk" -n "$vm_name"
    done <<< "$vm_list"
}

# Converts a CSV list to a shell array
function convert_csv_to_array () {
    if [ -n "$BASH_VERSION" ]; then
        arr_opt=a
    elif [ -n "$ZSH_VERSION" ]; then
        arr_opt=A
    fi
    IFS=',' read -r"${arr_opt}" myarray <<< "$1"
    echo "${myarray[@]}"
}

# Checks that the PSK complies with password rules for VMs
# Returns a message if the password is not compliant with pasword rules, returns nothing if it is
function check_password () {
    password_to_check=$1
    password_to_check_len=${#password_to_check}
    # Length
    if [ "$password_to_check_len" -lt 12 ]; then
        echo "$password_to_check is shorter than 12 characters"
    else
        # Special characters
        if [[ -z $(echo "$password_to_check" | tr -d "[:alnum:]") ]]; then
            echo "$password_to_check does not contain non alphanumeric characters"
        else
            # Numbers
            if [[ -z $(echo "$password_to_check" | tr -cd "0-9") ]]; then
                echo "$password_to_check does not contain numbers"
            else
                # Lower case
                if [[ -z $(echo "$password_to_check" | tr -cd "a-z") ]]; then
                    echo "$password_to_check does not contain lower case characters"
                else
                    # Upper case
                    if [[ -z $(echo "$password_to_check" | tr -cd "A-Z") ]]; then
                        echo "$password_to_check does not contain upper case characters"
                    fi
                fi
            fi
        fi
    fi
}

# Verify certain things:
# - Presence of required binaries
# - Presence of requirec az extensions
# - Azure CLI logged in
function perform_system_checks () {
    # Verify software dependencies
    for binary in "ssh" "jq" "az" "awk"
    do
        binary_path=$(which "$binary")
        if [[ -z "$binary_path" ]]
        then
            echo "It seems that $binary is not installed in the system. Please install it before trying this script again"
            exit
        fi
    done
    echo "All dependencies checked successfully"

    # Verify az is logged in
    subscription_name=$(az account show --query name -o tsv)
    if [[ -z "$subscription_name" ]]
    then
        echo "It seems you are not logged into Azure with the Azure CLI. Please use \"az login\" before trying this script again"
        exit
    fi

    # Verify required az extensions installed
    for extension_name in "log-analytics"
    do
        az extension add --upgrade --yes --name $extension_name -o none
        extension_version=$(az extension show -n $extension_name --query version -o tsv)
        if [[ -z "$extension_version" ]]
        then
            echo "It seems that the Azure CLI extension \"$extension_name\" is not installed. Please install it with \"az extension add -n $extension_name\" before trying this script again"
            exit
        else
            echo "Azure CLI extension \"$extension_name\" found with version $extension_version"
        fi
    done
}

########
# Main #
########


# Deploy CSRs and VNGs
# echo "Routers array: $routers"
for router in "${routers[@]}"
do
    create_router "$router"
done

# Verify VMs/VNGs exist
for router in "${routers[@]}"
do
    verify_router "$router"
done

# Config BGP routers
wait_for_csrs_finished
config_csrs_base

# Fix NSGs to allow all traffic between RFC1918 addresses
fix_all_nsgs

# Wait for VNGs to finish provisioning and configuring logging
wait_for_gws_finished
init_log
config_gw_logging

# Configure connections
for connection in "${connections[@]}"
do
    create_connection "$connection"
done

# Verify VMs/VNGs exist
for router in "${routers[@]}"
do
    show_bgp_neighbors "$router"
done

# Finish
echo "Your resources should be ready to use in resource group $rg. Username/password for access is ${default_username}/${psk}. Enjoy!"

################################
# Sample diagnostics commands: #
################################
# az network vnet-gateway list -g $rg -o table
# az network local-gateway list -g $rg -o table
# az network vpn-connection list -g $rg -o table
# az network public-ip list -g $rg -o table
# az network vnet-gateway list-bgp-peer-status -n vng1 -g $rg -o table
# az network vnet-gateway list-learned-routes -n vng1 -g $rg -o table
# az network vnet-gateway list-advertised-routes -n vng1 -g $rg -o table
# az network nic show-effective-route-table -n testvm1VMNic -g $rg -o table
# sh_csr_int 4
#############

Lo hize así porque me daba un fallo el código ademas me daba timeout todo el rato y tuve que cada poco ir dando enter en la consola de bash de Azure y guardar en el editor clasico

Esta configuración es para 1 VNG y 2 routres

#!/bin/bash
## Define "Global" script variables
nva_publisher=cisco
nva_offer=cisco-c8000v-byol
nva_sku=17_15_01a-byol 
#####
default_username=labadmin

test_vm_image_urn=Ubuntu2404
 
routers=("1:vng:65001" "2:csr:65002" "3:csr:65100")
connections=("1:2" "1:3")
rg=mylab
location=westeurope
psk=Microsoft123!

# Waits until a resource finishes provisioning
# Example: wait_until_finished <resource_id> 
function wait_until_finished () {
     wait_interval=60
     resource_id=$1
     resource_name=$(echo "$resource_id" | cut -d/ -f 9)
     echo "Waiting for resource $resource_name to finish provisioning..."
     start_time=$(date +%s)
     state=$(az resource show --id "$resource_id" --query properties.provisioningState -o tsv 2>/dev/null)
     until [[ "$state" == "Succeeded" ]] || [[ "$state" == "Failed" ]] || [[ -z "$state" ]]
     do
        sleep $wait_interval
        state=$(az resource show --id "$resource_id" --query properties.provisioningState -o tsv 2>/dev/null)
     done
     if [[ -z "$state" ]]
     then
        echo "Something really bad happened..."
     else
        run_time=$(("$(date +%s)" - "$start_time"))
        ((minutes=run_time/60))
        ((seconds=run_time%60))
        echo "Resource $resource_name provisioning state is $state, wait time $minutes minutes and $seconds seconds"
     fi
}

# Remove special characters and empty lines from string
# Used in case the SSH session to IOS returns invalid characters (like CR aka 0x0d), but that doesnt seem to be the case,
#  so left to remove empty strings for the time being
function clean_string () {
    output=$1
    # output=$(echo $output | tr '\r' '\n')                   # Replace \r with \n
    output=$(echo "$output" | tr -d '\r')                   # Delete \r
    # output=$(echo $output | tr -dc '[:alnum:]\ \.\,\n')     # Remove special characters
    output=$(echo "$output" | awk NF)                       # Remove empty lines
    echo "$output"
}

# Wait until a public IP address answers via SSH
# The only thing CSR-specific is the command sent
function wait_until_csr_available () {
    wait_interval=15
    csr_id=$1
    csr_ip=$(az network public-ip show -n "csr${csr_id}-pip" -g "$rg" --query ipAddress -o tsv)
    echo "Waiting for CSR${csr_id} with IP address $csr_ip to answer over SSH..."
    start_time=$(date +%s)
    ssh_command="show version | include uptime"  # 'show version' contains VM name and uptime
    ssh_output=$(ssh -n -o ConnectTimeout=60 -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" "$ssh_command" 2>/dev/null)
    until [[ -n "$ssh_output" ]]
    do
        sleep $wait_interval
        fix_all_nsgs # possible my NSGs are broken?
        ssh_output=$(ssh -n -o ConnectTimeout=60 -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" "$ssh_command" 2>/dev/null)
    done
    run_time=$(("$(date +%s)" - "$start_time"))
    ((minutes=run_time/60))
    ((seconds=run_time%60))
    echo "IP address $csr_ip is available (wait time $minutes minutes and $seconds seconds). Answer to SSH command \"$ssh_command\": $(clean_string "$ssh_output")"
}

# Wait until all VNGs in the router list finish provisioning
function wait_for_csrs_finished () {
    for router in "${routers[@]}"
    do
        type=$(get_router_type  "$router")
        id=$(get_router_id  "$router")
        if [[ "$type" == "csr" ]]
        then
            wait_until_csr_available "$id"
        fi
    done
}

# Add required rules to an NSG to allow traffic between the vnets (RFC1918)
function fix_nsg () {
    nsg_name=$1
    if [[ -z "$2" ]]
    then
        # Grab my client IP for SSH admin rule
        myip=$(curl -s4 "https://api.ipify.org/")
        declare -i i="0"
        until [[ $myip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ $i -gt 5 ]]
        do
            sleep 5
            myip=$(curl -s4 "https://api.ipify.org/")
            ((i++))
        done

        # I tried to get my IP many times. Maybe something bad happened?
        if [[ $i -gt 5 ]]
        then
            echo "Something is wrong with retrieving my public IP from the remote API service at \"https://api.ipify.org/\". Response from service: $myip"
            exit 1
        fi
    else
        # Called by something else that already did the work for me
        myip=$2
    fi

    #echo "Adding SSH permit for ${myip} to NSG ${nsg_name}..."    
    az network nsg rule create --nsg-name "$nsg_name" -g "$rg" -n ssh-inbound-allow --priority 1000 \
        --source-address-prefixes "$myip" --destination-port-ranges 22 --access Allow --protocol Tcp \
        --description "Allow ssh inbound"  -o none

    #echo "Adding RFC1918 prefixes to NSG ${nsg_name}..."
    az network nsg rule create --nsg-name "$nsg_name" -g "$rg" -n Allow_Inbound_RFC1918 --priority 2000 \
        --access Allow --protocol '*' --source-address-prefixes '10.0.0.0/8' '172.16.0.0/12' '192.168.0.0/16' --direction Inbound \
        --destination-address-prefixes '10.0.0.0/8' '172.16.0.0/12' '192.168.0.0/16' --destination-port-ranges '*' -o none
    az network nsg rule create --nsg-name "$nsg_name" -g "$rg" -n Allow_Outbound_RFC1918 --priority 2000 \
        --access Allow --protocol '*' --source-address-prefixes '10.0.0.0/8' '172.16.0.0/12' '192.168.0.0/16' --direction Outbound \
        --destination-address-prefixes '10.0.0.0/8' '172.16.0.0/12' '192.168.0.0/16' --destination-port-ranges '*' -o none
}

# Add permit rules to all nsgs in the RG
function fix_all_nsgs () {
    nsg_list=$(az network nsg list -g "$rg" --query '[].name' -o tsv)

    # Grab my client IP for SSH admin rule
    myip=$(curl -s4 "https://api.ipify.org/")
    declare -i i="0"
    until [[ $myip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ $i -gt 5 ]]
    do
        sleep 5
        myip=$(curl -s4 "https://api.ipify.org/")
        ((i++))
    done

    # I tried to get my IP many times. Maybe something bad happened?
    if [[ $i -gt 5 ]]
    then
        echo "Something is wrong with retrieving my public IP from the remote API service at \"https://api.ipify.org/\". Response from service: $myip"
        exit 1
    fi

    echo "$nsg_list" | while read nsg
    do
        fix_nsg "$nsg" "$myip"
    done
}

# Creates BGP-enabled VNG
# ASN as parameter is optional
function create_vng () {
    id=$1
    asn=$2
    if [[ -n "$2" ]]
    then
        asn=$2
    else
        asn=$(get_router_asn_from_id "${id}")
    fi
    vnet_name=vng${id}
    vnet_prefix=10.${id}.0.0/16
    subnet_prefix=10.${id}.0.0/24
    test_vm_name=testvm${id}
    test_vm_nsg_name="${test_vm_name}-NSG"
    test_vm_nic_name="${test_vm_name}-nic"
    test_vm_pip_name="${test_vm_name}-pip"
    test_vm_size=Standard_B1s
    test_vm_subnet_prefix=10.${id}.1.0/24
    type=$(get_router_type_from_id "$id")
    # Create vnet
    echo "Creating vnet $vnet_name..."
    az network vnet create -g "$rg" -n "$vnet_name" --address-prefix "$vnet_prefix" --subnet-name GatewaySubnet --subnet-prefix "$subnet_prefix" -o none
    # Create test VM (to be able to see effective routes)
    # Not possible to create a test VM while a gateway is Updating, therefore starting the VM creation before the gateway
    test_vm_id=$(az vm show -n "$test_vm_name" -g "$rg" --query id -o tsv 2>/dev/null)
    if [[ -z "$test_vm_id" ]]
    then
        echo "Creating test virtual machine $test_vm_name in vnet $vnet_name in new subnet $test_vm_subnet_prefix..."
        az network nsg create -n "$test_vm_nsg_name" -g "$rg" -l "$location" -o none
        fix_nsg "$test_vm_nsg_name"

        az network vnet subnet create --vnet-name "$vnet_name" -g "$rg" -n testvm --address-prefixes "$test_vm_subnet_prefix" \
            --nsg "$test_vm_nsg_name" -o none

        az network public-ip create -n "$test_vm_pip_name" -g "$rg" -l "$location" --sku standard -o none

        az network nic create -n "$test_vm_nic_name" -g "$rg" -l "$location" --vnet-name "$vnet_name" --subnet testvm \
            --private-ip-address "10.${id}.1.4" --public-ip-address "$test_vm_pip_name" -o none

        # Not using $psk as password because it might not fulfill the password requirements for Azure VMs
        az vm create -n "$test_vm_name" -g "$rg" -l "$location" --image "$test_vm_image_urn" --size "$test_vm_size" \
            --generate-ssh-keys --authentication-type all --admin-username "$default_username" --admin-password "$psk" \
            --nics "$test_vm_nic_name" --no-wait -o none
    else
        echo "Virtual machine $test_vm_name already exists"
    fi
    # Create PIPs for gateway (this gives time as well for the test VM NICs to be created and attached to the subnet)
    echo "Creating public IP addresses for gateway vng${id}..."
    az network public-ip create -g "$rg" -n "vng${id}a" --sku standard -o none
    az network public-ip create -g "$rg" -n "vng${id}b" --sku standard -o none
    # Create VNG
    vng_id=$(az network vnet-gateway show -n "vng${id}" -g "$rg" --query id -o tsv 2>/dev/null)
    if [[ -z "${vng_id}" ]]
    then
        if [[ "$type" ==  "vng" ]] || [[ "$type" ==  "vng2" ]]
        then
            echo "Creating VNG vng${id} in active/active mode..."
            az network vnet-gateway create -g "$rg" --sku VpnGw2 --vpn-gateway-generation Generation2 --gateway-type Vpn --vpn-type RouteBased \
            --vnet "$vnet_name" -n "vng${id}" --asn "$asn" --public-ip-address "vng${id}a" "vng${id}b" --no-wait
        elif [[ "$type" ==  "vng1" ]]
        then
            echo "Creating VNG vng${id} in active/passive mode..."
            az network vnet-gateway create -g "$rg" --sku VpnGw2 --vpn-gateway-generation Generation2 --gateway-type Vpn --vpn-type RouteBased \
            --vnet "$vnet_name" -n "vng${id}" --asn "$asn" --public-ip-address "vng${id}a" --no-wait
        else
            echo "Sorry, I do not understand the VNG type $type"
        fi
    else
        echo "VNG vng${id} already exists"
    fi
}

# Connect 2 VPN gateways to each other
function connect_gws () {
    gw1_id=$1
    gw2_id=$2
    cx_type=$3
    gw1_type=$(get_router_type_from_id "$gw1_id")
    gw2_type=$(get_router_type_from_id "$gw2_id")
    echo "Connecting vng${gw1_id} and vng${gw2_id}. Finding out information about the gateways..." 

    # Using Vnet-to-Vnet connections (no BGP supported)
    # az network vpn-connection create -g $rg -n ${gw1_id}to${gw2_id} \
    #   --enable-bgp --shared-key $psk \
    #   --vnet-gateway1 vng${gw1_id} --vnet-gateway2 vng${gw2_id}

    # Using local gws
    # Create Local Gateways for vpngw1
    vpngw1_name=vng${gw1_id}
    vpngw1_bgp_json=$(az network vnet-gateway show -n "$vpngw1_name" -g "$rg" --query 'bgpSettings' -o json )
    vpngw1_asn=$(echo "$vpngw1_bgp_json" | jq -r '.asn')
    vpngw1_gw0_pip=$(echo "$vpngw1_bgp_json" | jq -r '.bgpPeeringAddresses[0].tunnelIpAddresses[0]')
    vpngw1_gw0_bgp_ip=$(echo "$vpngw1_bgp_json" | jq -r '.bgpPeeringAddresses[0].defaultBgpIpAddresses[0]')
    if [[ ${gw1_type} == "vng" ]] || [[ ${gw1_type} == "vng2" ]]
    then
        vpngw1_gw1_pip=$(echo "$vpngw1_bgp_json" | jq -r '.bgpPeeringAddresses[1].tunnelIpAddresses[0]')
        vpngw1_gw1_bgp_ip=$(echo "$vpngw1_bgp_json" | jq -r '.bgpPeeringAddresses[1].defaultBgpIpAddresses[0]')    
        echo "Extracted info for vpngw1: ASN $vpngw1_asn, GW0 $vpngw1_gw0_pip, $vpngw1_gw0_bgp_ip. GW1 $vpngw1_gw1_pip, $vpngw1_gw1_bgp_ip."
    elif [[ "$gw1_type" ==  "vng1" ]]
    then
        echo "Extracted info for vpngw1: ASN $vpngw1_asn, $vpngw1_gw0_pip, $vpngw1_gw0_bgp_ip."
    else
        echo "Sorry, I do not understand the VNG type $gw1_type"
    fi

    echo "Creating local network gateways for vng${gw1_id}..."
    local_gw_id=$(az network local-gateway show -g "$rg" -n "${vpngw1_name}a" --query id -o tsv 2>/dev/null)
    if [[ -z "$local_gw_id" ]]
    then
        az network local-gateway create -g "$rg" -n "${vpngw1_name}a" --gateway-ip-address "$vpngw1_gw0_pip" \
            --local-address-prefixes "${vpngw1_gw0_bgp_ip}/32" --asn "$vpngw1_asn" --bgp-peering-address "$vpngw1_gw0_bgp_ip" --peer-weight 0 -o none
    else
        echo "Local gateway ${vpngw1_name}a already exists"
    fi
    # Create second local gateway only if the type is "vng" (default) or "vng2"
    if [[ ${gw1_type} == "vng" ]] || [[ ${gw1_type} == "vng2" ]]
    then
        local_gw_id=$(az network local-gateway show -g "$rg" -n "${vpngw1_name}b" --query id -o tsv 2>/dev/null)
        if [[ -z "$local_gw_id" ]]
        then
            az network local-gateway create -g "$rg" -n "${vpngw1_name}b" --gateway-ip-address "$vpngw1_gw1_pip" \
                --local-address-prefixes "${vpngw1_gw1_bgp_ip}/32" --asn "$vpngw1_asn" --bgp-peering-address "$vpngw1_gw1_bgp_ip" --peer-weight 0 -o none
        else
            echo "Local gateway ${vpngw1_name}b already exists"
        fi
    fi
    # Create Local Gateways for vpngw2
    vpngw2_name=vng${gw2_id}
    vpngw2_bgp_json=$(az network vnet-gateway show -n "$vpngw2_name" -g "$rg" -o json --query 'bgpSettings')
    vpngw2_asn=$(echo "$vpngw2_bgp_json" | jq -r '.asn')
    vpngw2_gw0_pip=$(echo "$vpngw2_bgp_json" | jq -r '.bgpPeeringAddresses[0].tunnelIpAddresses[0]')
    vpngw2_gw0_bgp_ip=$(echo "$vpngw2_bgp_json" | jq -r '.bgpPeeringAddresses[0].defaultBgpIpAddresses[0]')
    echo "Creating local network gateways for vng${gw2_id}..."
    local_gw_id=$(az network local-gateway show -g "$rg" -n "${vpngw2_name}a" --query id -o tsv 2>/dev/null)
    if [[ ${gw2_type} == "vng" ]] || [[ ${gw2_type} == "vng2" ]]
    then
        vpngw2_gw1_pip=$(echo "$vpngw2_bgp_json" | jq -r '.bgpPeeringAddresses[1].tunnelIpAddresses[0]')
        vpngw2_gw1_bgp_ip=$(echo "$vpngw2_bgp_json" | jq -r '.bgpPeeringAddresses[1].defaultBgpIpAddresses[0]')
        echo "Extracted info for vpngw2: ASN $vpngw2_asn GW0 $vpngw2_gw0_pip, $vpngw2_gw0_bgp_ip. GW1 $vpngw2_gw1_pip, $vpngw2_gw1_bgp_ip."
    elif [[ "$gw2_type" ==  "vng1" ]]
    then
        echo "Extracted info for vpngw1: ASN $vpngw2_asn, $vpngw2_gw0_pip, $vpngw2_gw0_bgp_ip."
    else
        echo "Sorry, I do not understand the VNG type $gw2_type"
    fi
    if [[ -z "$local_gw_id" ]]
    then
        az network local-gateway create -g "$rg" -n "${vpngw2_name}a" --gateway-ip-address "$vpngw2_gw0_pip" \
            --local-address-prefixes "${vpngw2_gw0_bgp_ip}/32" --asn "$vpngw2_asn" --bgp-peering-address "$vpngw2_gw0_bgp_ip" --peer-weight 0 -o none
    else
        echo "Local gateway ${vpngw2_name}a already exists"
    fi
    # Create second local gateway only if the type is "vng" (default) or "vng2"
    if [[ ${gw2_type} == "vng" ]] || [[ ${gw2_type} == "vng2" ]]
    then
        local_gw_id=$(az network local-gateway show -g "$rg" -n "${vpngw2_name}b" --query id -o tsv 2>/dev/null)
        if [[ -z "$local_gw_id" ]]
        then
            az network local-gateway create -g "$rg" -n "${vpngw2_name}b" --gateway-ip-address "$vpngw2_gw1_pip" \
                --local-address-prefixes "${vpngw2_gw1_bgp_ip}/32" --asn "$vpngw2_asn" --bgp-peering-address "$vpngw2_gw1_bgp_ip" --peer-weight 0 -o none
        else
            echo "Local gateway ${vpngw2_name}b already exists"
        fi
    fi
    # Create connections
    echo "Connecting vng${gw1_id} to local gateways for vng${gw2_id}..."
    if [[ "$cx_type" == "nobgp" ]]
    then
        bgp_option=""
    else
        bgp_option="--enable-bgp"
    fi
    # 1->2A
    az network vpn-connection create -n "vng${gw1_id}tovng${gw2_id}a" -g "$rg" --vnet-gateway1 "vng${gw1_id}" \
        --shared-key "$psk" --local-gateway2 "${vpngw2_name}a" $bgp_option -o none
    az network vpn-connection ipsec-policy add --connection-name "vng${gw1_id}tovng${gw2_id}a" -g "$rg" \
        --ike-encryption GCMAES256 --ike-integrity GCMAES256 --dh-group DHGroup14 \
        --ipsec-encryption GCMAES256 --ipsec-integrity GCMAES256 --pfs-group None \
        --sa-lifetime 102400 --sa-max-size 27000 --output none
    # 1->2B
    if [[ ${gw2_type} == "vng" ]] || [[ ${gw2_type} == "vng2" ]]
    then
        az network vpn-connection create -n "vng${gw1_id}tovng${gw2_id}b" -g "$rg" --vnet-gateway1 "vng${gw1_id}" \
            --shared-key "$psk" --local-gateway2 "${vpngw2_name}b" $bgp_option -o none
        az network vpn-connection ipsec-policy add --connection-name "vng${gw1_id}tovng${gw2_id}b" -g "$rg" \
            --ike-encryption GCMAES256 --ike-integrity GCMAES256 --dh-group DHGroup14 \
            --ipsec-encryption GCMAES256 --ipsec-integrity GCMAES256 --pfs-group None \
            --sa-lifetime 102400 --sa-max-size 27000 --output none
    fi
    echo "Connecting vng${gw2_id} to local gateways for vng${gw1_id}..."
    # 2->1A
    az network vpn-connection create -n "vng${gw2_id}tovng${gw1_id}a" -g "$rg" --vnet-gateway1 "vng${gw2_id}" \
        --shared-key "$psk" --local-gateway2 "${vpngw1_name}a" $bgp_option -o none
    az network vpn-connection ipsec-policy add --connection-name "vng${gw2_id}tovng${gw1_id}a" -g "$rg" \
        --ike-encryption GCMAES256 --ike-integrity GCMAES256 --dh-group DHGroup14 \
        --ipsec-encryption GCMAES256 --ipsec-integrity GCMAES256 --pfs-group None \
        --sa-lifetime 102400 --sa-max-size 27000 --output none
    # 2->1B
    if [[ ${gw1_type} == "vng" ]] || [[ ${gw1_type} == "vng2" ]]
    then
        az network vpn-connection create -n "vng${gw2_id}tovng${gw1_id}b" -g "$rg" --vnet-gateway1 "vng${gw2_id}" \
            --shared-key "$psk" --local-gateway2 "${vpngw1_name}b" $bgp_option -o none
        az network vpn-connection ipsec-policy add --connection-name "vng${gw2_id}tovng${gw1_id}b" -g "$rg" \
            --ike-encryption GCMAES256 --ike-integrity GCMAES256 --dh-group DHGroup14 \
            --ipsec-encryption GCMAES256 --ipsec-integrity GCMAES256 --pfs-group None \
            --sa-lifetime 102400 --sa-max-size 27000 --output none
    fi
}

# Deploy a VM in CSR's vnet, and configure CSR to route traffic
function create_vm_in_csr_vnet () {
    csr_id=$1
    csr_name=csr${csr_id}
    vnet_name=${csr_name}
    csr_vnet_prefix="10.${csr_id}.0.0/16"
    csr_subnet_prefix="10.${csr_id}.0.0/24"
    csr_bgp_ip="10.${csr_id}.0.10"
    vm_subnet_prefix="10.${csr_id}.1.0/24"
    vm_subnet_name=testvm
    vm_name=testvm${csr_id}
    vm_nsg_name="${vm_name}-NSG"
    vm_nic_name="${vm_name}-nic"
    vm_pip_name="${vm_name}-pip"
    vm_size=Standard_B1s
    rt_name="${vm_name}-rt"
    # Create VM
    test_vm_id=$(az vm show -n "$vm_name" -g "$rg" --query id -o tsv 2>/dev/null)
    if [[ -z "$test_vm_id" ]]
    then
        echo "Creating VM $vm_name in vnet $vnet_name..."
        az network nsg create -n "$vm_nsg_name" -g "$rg" -l "$location" -o none
        fix_nsg "$vm_nsg_name"

        az network vnet subnet create --vnet-name "$vnet_name" -g "$rg" -n testvm --address-prefixes "$vm_subnet_prefix" \
            --nsg "$vm_nsg_name" -o none

        az network public-ip create -n "$vm_pip_name" -g "$rg" -l "$location" --sku standard -o none

        az network nic create -n "$vm_nic_name" -g "$rg" -l "$location" --vnet-name "$vnet_name" --subnet $vm_subnet_name \
            --private-ip-address "10.${id}.1.4" --public-ip-address "$vm_pip_name" -o none
        
        az vm create -n "$vm_name" -g "$rg" -l "$location" --image "$test_vm_image_urn" --size "$vm_size" \
            --generate-ssh-keys --authentication-type all --admin-username "$default_username" --admin-password "$psk" \
            --nics "$vm_nic_name" --no-wait -o none
        az network route-table create -n "$rt_name" -g "$rg" -l "$location" -o none
        az network route-table route create -n localrange -g "$rg" --route-table-name "$rt_name" \
            --address-prefix "10.0.0.0/8" --next-hop-type VirtualAppliance --next-hop-ip-address "$csr_bgp_ip" -o none
        az network vnet subnet update -n "$vm_subnet_name" --vnet-name "$vnet_name" -g "$rg" --route-table "$rt_name" -o none
    else
        echo "Virtual machine $vm_name already exists"
    fi
}

function accept_csr_terms () {
    #USES NVA GLOBALS
    nva_version=$(az vm image list -p $nva_publisher -f $nva_offer -s $nva_sku --all --query '[0].version' -o tsv)
    if [[ -z "$nva_version" ]]
    then
        echo "Could not locate an image version for ${nva_publisher}:${nva_offer}:${nva_sku}, double-check Publisher, offer, and SKU"
        exit 1
    fi
    # Accept terms
    echo "Accepting image terms for ${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version}..."
    az vm image terms accept --urn "${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version}" -o none
    # Verify
    status=$(az vm image terms show --urn "${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version}" --query accepted -o tsv)
    if [[ "$status" != "true" ]]
    then
        echo "Marketplace image terms for ${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version} could not be accepted, do you have access at the subscription level?"
        exit 1
    fi
}

# Creates a CSR NVA
# Example: create_csr 1
function create_csr () {
    # USES NVA GLOBALS
    csr_id=$1
    csr_name=csr${csr_id}
    csr_nsg_name="${csr_name}-NSG"
    csr_nic_name="${csr_name}-nic"
    csr_pip_name="${csr_name}-pip"
    csr_vnet_prefix="10.${csr_id}.0.0/16"
    csr_subnet_prefix="10.${csr_id}.0.0/24"
    csr_bgp_ip="10.${csr_id}.0.10"
    
    nva_version=$(az vm image list -p $nva_publisher -f $nva_offer -s $nva_sku --all --query '[0].version' -o tsv)
    nva_size=Standard_B2ms
    # Create CSR
    echo "Creating VM csr${csr_id}-nva in Vnet $csr_vnet_prefix..."
    vm_id=$(az vm show -n "csr${csr_id}-nva" -g "$rg" --query id -o tsv 2>/dev/null)
    if [[ -z "$vm_id" ]]
    then
        az network nsg create -n "$csr_nsg_name" -g "$rg" -l "$location" -o none
        az network nsg rule create --nsg-name "$csr_nsg_name" -g "$rg" -n ike --priority 1100 \
            --source-address-prefixes Internet --destination-port-ranges 500 4500 --access Allow --protocol Udp \
            --description "UDP ports for IKE VPN" -o none
        fix_nsg "$csr_nsg_name"

        az network vnet create -n "$csr_name" -g "$rg" -l "$location" --address-prefix "$csr_vnet_prefix" --subnet-name nva \
            --subnet-prefixes "$csr_subnet_prefix" --nsg "$csr_nsg_name" -o none

        az network public-ip create -n "$csr_pip_name" -g "$rg" -l "$location" --sku standard -o none

        az network nic create -n "$csr_nic_name" -g "$rg" -l "$location" --vnet-name "$csr_name" --subnet nva \
            --private-ip-address "$csr_bgp_ip" --public-ip-address "$csr_pip_name" --ip-forwarding true -o none

        az vm create -n "csr${csr_id}-nva" -g "$rg" -l "$location" --image "${nva_publisher}:${nva_offer}:${nva_sku}:${nva_version}" --size "$nva_size" \
            --generate-ssh-keys --admin-username "$default_username" --nics "$csr_nic_name" --no-wait -o none
    else
        echo "VM csr${csr_id}-nva already exists"
    fi

    # Get public IP
    csr_ip=$(az network public-ip show -n "csr${csr_id}-pip" -g "$rg" --query ipAddress -o tsv)
    # Create Local Network Gateway
    echo "CSR created with IP address $csr_ip. Creating Local Network Gateway now..."
    asn=$(get_router_asn_from_id "$csr_id")
    local_gw_id=$(az network local-gateway show -g "$rg" -n "${csr_name}" --query id -o tsv 2>/dev/null)
    if [[ -z "$local_gw_id" ]]
    then
        az network local-gateway create -g "$rg" -n "$csr_name" --gateway-ip-address "$csr_ip" \
            --local-address-prefixes "${csr_bgp_ip}/32" --asn "$asn" --bgp-peering-address "$csr_bgp_ip" --peer-weight 0 -o none
    else
        echo "Local gateway ${csr_name} already exists"
    fi
}

# Connects a CSR to one VNG
function connect_csr () {
    csr_id=$1
    gw_id=$2
    cx_type=$3
    gw_type=$(get_router_type_from_id "$gw_id")
    # csr_asn=$(get_router_asn_from_id $csr_id)  # Not used

    vpngw_name=vng${gw_id}
    vpngw_bgp_json=$(az network vnet-gateway show -n "$vpngw_name" -g "$rg" --query 'bgpSettings' -o json)
    vpngw_asn=$(echo "$vpngw_bgp_json" | jq -r '.asn')
    vpngw_gw0_pip=$(echo "$vpngw_bgp_json" | jq -r '.bgpPeeringAddresses[0].tunnelIpAddresses[0]')
    vpngw_gw0_bgp_ip=$(echo "$vpngw_bgp_json" | jq -r '.bgpPeeringAddresses[0].defaultBgpIpAddresses[0]')
    if [[ ${gw_type} == "vng" ]] || [[ ${gw_type} == "vng2" ]]
    then
        vpngw_gw1_pip=$(echo "$vpngw_bgp_json" | jq -r '.bgpPeeringAddresses[1].tunnelIpAddresses[0]')
        vpngw_gw1_bgp_ip=$(echo "$vpngw_bgp_json" | jq -r '.bgpPeeringAddresses[1].defaultBgpIpAddresses[0]')    
        echo "Extracted info for vpngw: ASN $vpngw_asn, GW0 $vpngw_gw0_pip, $vpngw_gw0_bgp_ip. GW1 $vpngw_gw1_pip, $vpngw_gw1_bgp_ip."
    elif [[ "$gw_type" ==  "vng1" ]]
    then
        echo "Extracted info for vpngw: ASN $vpngw_asn, $vpngw_gw0_pip, $vpngw_gw0_bgp_ip."
    else
        echo "Sorry, I do not understand the VNG type $gw_type"
    fi

    # Tunnels for vpngw (IKEv2)
    echo "Configuring tunnels between CSR $csr_id and VPN GW $gw_id"
    config_csr_tunnel "$csr_id" "${csr_id}${gw_id}0" "$vpngw_gw0_pip" "$vpngw_gw0_bgp_ip" "$(get_router_asn_from_id "$gw_id")" ikev2 "$cx_type"
    if [[ ${gw_type} == "vng" ]] || [[ ${gw_type} == "vng2" ]]
    then
        config_csr_tunnel "$csr_id" "${csr_id}${gw_id}1" "$vpngw_gw1_pip" "$vpngw_gw1_bgp_ip" "$(get_router_asn_from_id "$gw_id")" ikev2 "$cx_type"
    fi

    # Connect Local GWs to VNGs
    echo "Creating VPN connections in Azure..."
    if [[ "$cx_type" == "nobgp" ]]
    then
        bgp_option=""
    else
        bgp_option="--enable-bgp"
    fi
    az network vpn-connection create -n "vng${gw_id}tocsr${csr_id}" -g "$rg" --vnet-gateway1 "vng${gw_id}" \
        --shared-key "$psk" --local-gateway2 "csr${csr_id}" $bgp_option -o none
    az network vpn-connection ipsec-policy add --connection-name "vng${gw_id}tocsr${csr_id}" -g "$rg" \
        --ike-encryption GCMAES256 --ike-integrity GCMAES256 --dh-group DHGroup14 \
        --ipsec-encryption GCMAES256 --ipsec-integrity GCMAES256 --pfs-group None \
        --sa-lifetime 102400 --sa-max-size 27000 --output none
    if [[ -n "$gw2_id" ]]
    then
        az network vpn-connection create -n "vng${gw2_id}tocsr${csr_id}" -g "$rg" --vnet-gateway1 "vng${gw2_id}" \
            --shared-key "$psk" --local-gateway2 "csr${csr_id}" $bgp_option -o none
        az network vpn-connection ipsec-policy add --connection-name "vng${gw2_id}tocsr${csr_id}" -g "$rg" \
            --ike-encryption GCMAES256 --ike-integrity GCMAES256 --dh-group DHGroup14 \
            --ipsec-encryption GCMAES256 --ipsec-integrity GCMAES256 --pfs-group None \
            --sa-lifetime 102400 --sa-max-size 27000 --output none
    fi
}

# Run "show interface ip brief" on CSR
function sh_csr_int () {
    csr_id=$1
    csr_ip=$(az network public-ip show -n "csr${csr_id}-pip" -g "$rg" -o tsv --query ipAddress)
    ssh -n -o StrictHostKeyChecking=no -o ServerAliveInterval=60 -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" "sh ip int b" 2>/dev/null
}

# Open an interactive SSH session to a CSR
function ssh_csr () {
    csr_id=$1
    csr_ip=$(az network public-ip show -n "csr${csr_id}-pip" -g "$rg" -o tsv --query ipAddress)
    ssh "$csr_ip"
}

# Deploy baseline, VPN, and BGP config to a Cisco CSR
function config_csr_base () {
    csr_id=$1
    csr_ip=$(az network public-ip show -n "csr${csr_id}-pip" -g "$rg" -o tsv --query ipAddress)
    asn=$(get_router_asn_from_id "$csr_id")
    # Grab client IP for static route
        myip=$(curl -s4 "https://api.ipify.org/")
        until [[ $myip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]
        do
            sleep 5
            myip=$(curl -s4 "https://api.ipify.org/")
    done
    
    default_gateway="10.${csr_id}.0.1"

    echo "Configuring CSR${csr_id} at ${csr_ip} for necessary licensing features to use VPN and rebooting..."
    wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
    ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
    config t
        license boot level network-advantage addon dna-advantage
        do wr mem
        event manager applet reboot-now
        event timer watchdog time 5
        action 1.0 reload
        end
EOF

    sleep 60 # Avoid re-checking availability too early
    wait_until_csr_available "${csr_id}"

    echo "Configuring CSR${csr_id} at ${csr_ip} for VPN and BGP..."
    username=$(whoami)
    password=$psk
    wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
    ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
    config t
      username ${username} password 0 ${password}
      username ${username} privilege 15
      username ${default_username} password 0 ${password}
      username ${default_username} privilege 15
      no ip domain lookup
      crypto ikev2 keyring azure-keyring
      crypto ikev2 proposal azure-proposal
        encryption aes-gcm-256
        prf sha256
        group 14
      crypto ikev2 policy azure-policy
        proposal azure-proposal
      crypto ikev2 profile azure-profile
        match address local interface GigabitEthernet1
        authentication remote pre-share
        authentication local pre-share
        keyring local azure-keyring
      crypto ipsec transform-set azure-ipsec-proposal-set esp-gcm 256
        mode tunnel
      crypto ipsec profile azure-vti
        set security-association lifetime kilobytes 102400000
        set transform-set azure-ipsec-proposal-set
        set ikev2-profile azure-profile
      crypto isakmp policy 1
        encr aes
        authentication pre-share
        group 14
      crypto ipsec transform-set csr-ts esp-aes esp-sha-hmac
        mode tunnel
      crypto ipsec profile csr-profile
        set transform-set csr-ts
      router bgp $asn
        bgp router-id interface GigabitEthernet1
        network 10.${csr_id}.0.0 mask 255.255.0.0
        bgp log-neighbor-changes
        maximum-paths eibgp 4
      ip route ${myip} 255.255.255.255 ${default_gateway}
      ip route 10.${csr_id}.0.0 255.255.0.0 ${default_gateway}
      line vty 0 20
        exec-timeout 0 0
    end
    wr mem
EOF
}

# Configure a tunnel a BGP neighbor for a specific remote endpoint on a Cisco CSR
# "mode" can be either IKEv2 or ISAKMP. I havent been able to bring up 
#   a CSR-to-CSR connection using IKEv2, hence my workaround is using isakmp.
function config_csr_tunnel () {
    csr_id=$1
    tunnel_id=$2
    public_ip=$3
    private_ip=$4
    remote_asn=$5
    mode=$6
    cx_type=$7  # Can be none, ospf, bgpospf or empty (bgp per default)
    if [[ -z "$mode" ]]
    then
        mode=ikev2
    fi
    if [[ "$mode" == "ikev2" ]]
    then
        ipsec_profile="azure-vti"
    else
        ipsec_profile="csr-profile"
    fi
    asn=$(get_router_asn_from_id "${csr_id}")
    default_gateway="10.${csr_id}.0.1"
    csr_ip=$(az network public-ip show -n "csr${csr_id}-pip" -g "$rg" -o tsv --query ipAddress)
    echo "Configuring tunnel ${tunnel_id} in CSR${csr_id} at ${csr_ip}..."
    wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
    ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
    config t
      crypto ikev2 keyring azure-keyring
        peer ${public_ip}
          address ${public_ip}
          pre-shared-key ${psk}
      crypto ikev2 profile azure-profile
        match identity remote address ${public_ip} 255.255.255.255
      crypto isakmp key ${psk} address ${public_ip}
      interface Tunnel${tunnel_id}
        ip unnumbered GigabitEthernet1
        ip tcp adjust-mss 1350
        tunnel source GigabitEthernet1
        tunnel mode ipsec ipv4
        tunnel destination ${public_ip}
        tunnel protection ipsec profile ${ipsec_profile}
      ip route ${private_ip} 255.255.255.255 Tunnel${tunnel_id}
      ip route ${public_ip} 255.255.255.255 ${default_gateway}
    end
    wr mem
EOF
    if [[ -z "$cx_type" ]] || [[ "$cx_type" == "bgp" ]] || [[ "$cx_type" == "bgpospf" ]]
    then
      echo "Configuring BGP on tunnel ${tunnel_id} in CSR ${csr_ip}..."
      wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
      ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
        config t
          router bgp ${asn}
            neighbor ${private_ip} remote-as ${remote_asn}
            neighbor ${private_ip} update-source GigabitEthernet1
        end
        wr mem
EOF
      if [[ "$asn" == "$remote_asn" ]]
      then
        # iBGP
        wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
        ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
            config t
              router bgp ${asn}
                neighbor ${private_ip} next-hop-self
            end
            wr mem
EOF
      else
        # eBGP
        wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
        ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
            config t
              router bgp ${asn}
                neighbor ${private_ip} ebgp-multihop 5
            end
            wr mem
EOF
      fi
    elif [[ "$cx_type" == "ospf" ]] || [[ "$cx_type" == "bgpospf" ]]
    then
      echo "Configuring OSPF on tunnel ${tunnel_id} in CSR ${csr_ip}..."
      wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
      ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
        config t
          router ospf 100
            no passive-interface Tunnel${tunnel_id}
            network 10.${csr_id}.0.0 255.255.0.0 area 0
        end
        wr mem
EOF
    elif [[ "$cx_type" == "static" ]]  ## Not used
    then
      echo "Configuring static routes for tunnel ${tunnel_id} in CSR ${csr_ip}..."
      remote_id=$(echo "$tunnel_id" | head -c 2 | tail -c 1) # This only works with a max of 9 routers
      echo "Configuring OSPF on tunnel ${tunnel_id} in CSR ${csr_ip}..."
      wait_until_csr_available "${csr_id}" # Make sure I can still talk to the CSR
      ssh -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${csr_ip}" >/dev/null 2>&1 <<EOF
        config t
          ip route 10.${remote_id}.0.0 255.255.0.0 Tunnel${tunnel_id}
        end
        wr mem
EOF
    else
        echo "No routing protocol configured on ${tunnel_id} in CSR ${csr_ip}..."
    fi
}

# Connect two CSRs to each other over IPsec and BGP
function connect_csrs () {
    csr1_id=$1
    csr2_id=$2
    cx_type=$3
    csr1_ip=$(az network public-ip show -n "csr${csr1_id}-pip" -g "$rg" -o tsv --query ipAddress)
    csr2_ip=$(az network public-ip show -n "csr${csr2_id}-pip" -g "$rg" -o tsv --query ipAddress)
    csr1_asn=$(get_router_asn_from_id "${csr1_id}")
    csr2_asn=$(get_router_asn_from_id "${csr2_id}")
    csr1_bgp_ip="10.${csr1_id}.0.10"
    csr2_bgp_ip="10.${csr2_id}.0.10"
    # Tunnel from csr1 to csr2 (using ISAKMP instead of IKEv2)
    tunnel_id=${csr1_id}${csr2_id}
    config_csr_tunnel "$csr1_id" "$tunnel_id" "$csr2_ip" "$csr2_bgp_ip" "$csr2_asn" isakmp "$cx_type"
    # Tunnel from csr2 to csr1 (using ISAKMP instead of IKEv2)
    tunnel_id=${csr2_id}${csr1_id}
    config_csr_tunnel "$csr2_id" "$tunnel_id" "$csr1_ip" "$csr1_bgp_ip" "$csr1_asn" isakmp "$cx_type"
}

# Configure logging
function init_log () {
    logws_name=$(az monitor log-analytics workspace list -g "$rg" --query '[0].name' -o tsv)
    if [[ -z "$logws_name" ]]
    then
        logws_name=log$RANDOM
        echo "Creating LA workspace $logws_name..."
        az monitor log-analytics workspace create -n $logws_name -g "$rg" -o none
    else
        echo "Found log analytics workspace $logws_name"
    fi
    logws_id=$(az resource list -g "$rg" -n "$logws_name" --query '[].id' -o tsv)
    logws_customerid=$(az monitor log-analytics workspace show -n "$logws_name" -g "$rg" --query customerId -o tsv)
}

# Configures a certain VNG for logging to a previously created LA workspace
function log_gw () {
  gw_id=$1
  vpngw_id=$(az network vnet-gateway show -n "vng${gw_id}" -g "$rg" --query id -o tsv)
  echo "Configuring diagnostic settings for gateway vng${gw_id}"
  az monitor diagnostic-settings create -n mydiag --resource "$vpngw_id" --workspace "$logws_id" \
      --metrics '[{"category": "AllMetrics", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false }}]' \
      --logs '[{"category": "GatewayDiagnosticLog", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false}}, 
              {"category": "TunnelDiagnosticLog", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false}},
              {"category": "RouteDiagnosticLog", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false}},
              {"category": "IKEDiagnosticLog", "enabled": true, "retentionPolicy": {"days": 0, "enabled": false}}]' -o none
}

# Gets IKE logs from Log Analytics
# Possible improvements:
# - Supply time and max number of msgs as parameters
function get_ike_logs () {
  query='AzureDiagnostics 
  | where ResourceType == "VIRTUALNETWORKGATEWAYS" 
  | where Category == "IKEDiagnosticLog" 
  | where TimeGenerated >= ago(5m) 
  | project Message
  | take 20'
  az monitor log-analytics query -w "$logws_customerid" --analytics-query "$query" -o tsv
}

# Creates a connection between two routers
# The function to call depends on whether they are CSRs or VNGs
function create_connection () {
      # Split router_params, different syntax for BASH and ZSH
      if [ -n "$BASH_VERSION" ]; then
          arr_opt=a
      elif [ -n "$ZSH_VERSION" ]; then
          arr_opt=A
      fi
      IFS=':' read -r"$arr_opt" router_params <<< "$1"
      if [ -n "$BASH_VERSION" ]; then
          router1_id="${router_params[0]}"
          router2_id="${router_params[1]}"
          cx_type="${router_params[2]}"
      elif [ -n "$ZSH_VERSION" ]; then
          router1_id="${router_params[1]}"
          router2_id="${router_params[2]}"
          cx_type="${router_params[3]}"
      else
          echo "Error identifying shell, it looks like neither BASH or ZSH!"
      fi
      router1_type=$(get_router_type_from_id "$router1_id")
      router2_type=$(get_router_type_from_id "$router2_id")
      echo "Creating connection between ${router1_type} ${router1_id} and ${router2_type} ${router2_id}, type \"${cx_type}\"..."
      if [[ "$router1_type" == "vng" ]] || [[ "$router1_type" == "vng1" ]] || [[ "$router1_type" == "vng2" ]]
      then
          # VNG-to-VNG
          if [[ "$router2_type" == "vng" ]] || [[ "$router2_type" == "vng1" ]] || [[ "$router2_type" == "vng2" ]]
          then
              connect_gws "$router1_id" "$router2_id" "$cx_type"
          # VNG-to-CSR
          else
              connect_csr "$router2_id" "$router1_id" "$cx_type"
          fi
      else
          # CSR-to-VNG
          if [[ "$router2_type" == "vng" ]] || [[ "$router2_type" == "vng1" ]] || [[ "$router2_type" == "vng2" ]]
          then
              connect_csr "$router1_id" "$router2_id" "$cx_type"
          # CSR-to-CSR
          else
              connect_csrs "$router1_id" "$router2_id" "$cx_type"
          fi
      fi
}

# Get router type for a specific router ID
function get_router_type_from_id () {
    id=$1
    for router in "${routers[@]}"
    do
        this_id=$(get_router_id "$router")
        if [[ "$id" -eq "$this_id" ]]
        then
            get_router_type "$router"
        fi
    done
}

# Get router ASN for a specific router ID
function get_router_asn_from_id () {
    id=$1
    for router in "${routers[@]}"
    do
        this_id=$(get_router_id  "$router")
        if [[ "$id" -eq "$this_id" ]]
        then
            get_router_asn "$router"
        fi
    done
}

# Verifies a VNG or CSR has been created in Azure
function verify_router () {
    router=$1
    echo "Verifying $router..."
    type=$(get_router_type "$router")
    id=$(get_router_id  "$router")
    if [[ "$type" == "csr" ]]
    then
        vm_status=$(az vm show -n "csr${id}-nva" -g "$rg" --query provisioningState -o tsv)
        ip=$(az network public-ip show -n "csr${id}-pip" -g "$rg" --query ipAddress -o tsv)
        echo "VM csr${id}-nva status is ${vm_status}, public IP is ${ip}"
    else
        gw_status=$(az network vnet-gateway show -n "vng${id}" -g "$rg" --query provisioningState -o tsv)
        # ip_a=$(az network public-ip show -n "vng${id}a" -g "$rg" --query ipAddress -o tsv)
        # ip_b=$(az network public-ip show -n "vng${id}b" -g "$rg" --query ipAddress -o tsv)
        # echo "Gateway vng${id} status is ${gw_status}, public IPs are ${ip_a} and ${ip_b}"
        echo "Gateway vng${id} status is ${gw_status}"
    fi
}

# Showing BGP neighbors for a certain router
function show_bgp_neighbors () {
    router=$1
    echo "Getting BGP neighbors for router $router..."
    type=$(get_router_type "$router")
    id=$(get_router_id  "$router")
    if [[ "$type" == "csr" ]]
    then
        ip=$(az network public-ip show -n "csr${id}-pip" -g "$rg" --query ipAddress -o tsv)
        neighbors=$(ssh -n -o ServerAliveInterval=60 -o BatchMode=yes -o StrictHostKeyChecking=no -o KexAlgorithms=+diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -o MACs=+hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com "${default_username}@${ip}" "show ip bgp summary | begin Neighbor" 2>/dev/null)
        echo "BGP neighbors for csr${id}-nva (${ip}):"
        clean_string "$neighbors"
    else
        neighbors=$(az network vnet-gateway list-bgp-peer-status -n "vng${id}" -g "$rg" -o table)
        echo "BGP neighbors for vng${id}:"
        echo "$neighbors"
    fi
}

# Create a VNG or a CSR, configuration given by a colon-separated parameter string (like "1:vng:65515")
function create_router () {
    type=$(get_router_type  "$router")
    id=$(get_router_id  "$router")
    asn=$(get_router_asn  "$router")
    echo "Creating router of type $type, id $id, ASN $asn..."
    case $type in
    "vng"|"vng1"|"vng2")
        create_vng "$id" "$asn"
        ;;
    "csr")
        create_csr "$id"
        ;;
    esac
}

# Gets the type out of a router configuration (like csr in "1:csr:65515")
function get_router_type () {
      # Split router_params, different syntax for BASH and ZSH
      if [ -n "$BASH_VERSION" ]; then
          arr_opt=a
      elif [ -n "$ZSH_VERSION" ]; then
          arr_opt=A
      fi
      IFS=':' read -r"$arr_opt" router_params <<< "$1"
      # In BASH array indexes start with 0, in ZSH with 1
      if [ -n "$BASH_VERSION" ]; then
          echo "${router_params[1]}"
      elif [ -n "$ZSH_VERSION" ]; then
          echo "${router_params[2]}"
      fi
}

# Gets the ID out of a router configuration (like 1 in "1:csr:65515")
function get_router_id () {
      # Split router_params, different syntax for BASH and ZSH
      if [ -n "$BASH_VERSION" ]; then
          arr_opt=a
      elif [ -n "$ZSH_VERSION" ]; then
          arr_opt=A
      fi
      IFS=':' read -r"$arr_opt" router_params <<< "$1"
      # In BASH array indexes start with 0, in ZSH with 1
      if [ -n "$BASH_VERSION" ]; then
          echo "${router_params[0]}"
      elif [ -n "$ZSH_VERSION" ]; then
          echo "${router_params[1]}"
      fi
}

# Gets the ASN out of a router configuration (like 65001 in "1:csr:65001")
function get_router_asn () {
      # Split router_params, different syntax for BASH and ZSH
      if [ -n "$BASH_VERSION" ]; then
          arr_opt=a
      elif [ -n "$ZSH_VERSION" ]; then
          arr_opt=A
      fi
      IFS=':' read -r"$arr_opt" router_params <<< "$1"
      # In BASH array indexes start with 0, in ZSH with 1
      if [ -n "$BASH_VERSION" ]; then
          echo "${router_params[2]}"
      elif [ -n "$ZSH_VERSION" ]; then
          echo "${router_params[3]}"
      fi
}

# Wait until all VNGs in the router list finish provisioning
function wait_for_gws_finished () {
    for router in "${routers[@]}"
    do
        type=$(get_router_type "$router")
        id=$(get_router_id "$router")
        if [[ "$type" == "vng" ]] || [[ "$type" == "vng1" ]] || [[ "$type" == "vng2" ]]
        then
            vng_name=vng${id}
            vpngw_id=$(az network vnet-gateway show -n "$vng_name" -g "$rg" --query id -o tsv)
            wait_until_finished "$vpngw_id"
        fi
    done
}

# Configure logging to LA for all gateways
function config_gw_logging () {
    for router in "${routers[@]}"
    do
        type=$(get_router_type "$router")
        id=$(get_router_id "$router")
        if [[ "$type" == "vng" ]] || [[ "$type" == "vng1" ]] || [[ "$type" == "vng2" ]]
        then
            log_gw "$id"
        fi
    done
}

# Deploy base config for all CSRs
function config_csrs_base () {
    for router in "${routers[@]}"
    do
        type=$(get_router_type  "$router")
        id=$(get_router_id  "$router")
        if [[ "$type" == "csr" ]]
        then
            config_csr_base "$id"
            create_vm_in_csr_vnet "$id"
        fi
    done
}

# Add an additional users to each test VM
function add_users_to_vms () {
    vm_list=$(az vm list -o tsv -g "$rg" --query "[?contains(name,'testvm')].name")
    while IFS= read -r vm_name; do
        az vm user update -g $rg -u "$default_username" -p "$psk" -n "$vm_name"
    done <<< "$vm_list"
}

# Converts a CSV list to a shell array
function convert_csv_to_array () {
    if [ -n "$BASH_VERSION" ]; then
        arr_opt=a
    elif [ -n "$ZSH_VERSION" ]; then
        arr_opt=A
    fi
    IFS=',' read -r"${arr_opt}" myarray <<< "$1"
    echo "${myarray[@]}"
}

# Checks that the PSK complies with password rules for VMs
# Returns a message if the password is not compliant with pasword rules, returns nothing if it is
function check_password () {
    password_to_check=$1
    password_to_check_len=${#password_to_check}
    # Length
    if [ "$password_to_check_len" -lt 12 ]; then
        echo "$password_to_check is shorter than 12 characters"
    else
        # Special characters
        if [[ -z $(echo "$password_to_check" | tr -d "[:alnum:]") ]]; then
            echo "$password_to_check does not contain non alphanumeric characters"
        else
            # Numbers
            if [[ -z $(echo "$password_to_check" | tr -cd "0-9") ]]; then
                echo "$password_to_check does not contain numbers"
            else
                # Lower case
                if [[ -z $(echo "$password_to_check" | tr -cd "a-z") ]]; then
                    echo "$password_to_check does not contain lower case characters"
                else
                    # Upper case
                    if [[ -z $(echo "$password_to_check" | tr -cd "A-Z") ]]; then
                        echo "$password_to_check does not contain upper case characters"
                    fi
                fi
            fi
        fi
    fi
}

# Verify certain things:
# - Presence of required binaries
# - Presence of requirec az extensions
# - Azure CLI logged in
function perform_system_checks () {
    # Verify software dependencies
    for binary in "ssh" "jq" "az" "awk"
    do
        binary_path=$(which "$binary")
        if [[ -z "$binary_path" ]]
        then
            echo "It seems that $binary is not installed in the system. Please install it before trying this script again"
            exit
        fi
    done
    echo "All dependencies checked successfully"

    # Verify az is logged in
    subscription_name=$(az account show --query name -o tsv)
    if [[ -z "$subscription_name" ]]
    then
        echo "It seems you are not logged into Azure with the Azure CLI. Please use \"az login\" before trying this script again"
        exit
    fi

    # Verify required az extensions installed
    for extension_name in "log-analytics"
    do
        az extension add --upgrade --yes --name $extension_name -o none
        extension_version=$(az extension show -n $extension_name --query version -o tsv)
        if [[ -z "$extension_version" ]]
        then
            echo "It seems that the Azure CLI extension \"$extension_name\" is not installed. Please install it with \"az extension add -n $extension_name\" before trying this script again"
            exit
        else
            echo "Azure CLI extension \"$extension_name\" found with version $extension_version"
        fi
    done
}

########
# Main #
########


# Deploy CSRs and VNGs
# echo "Routers array: $routers"
for router in "${routers[@]}"
do
    create_router "$router"
done

# Verify VMs/VNGs exist
for router in "${routers[@]}"
do
    verify_router "$router"
done

# Config BGP routers
wait_for_csrs_finished
config_csrs_base

# Fix NSGs to allow all traffic between RFC1918 addresses
fix_all_nsgs

# Wait for VNGs to finish provisioning and configuring logging
wait_for_gws_finished
init_log
config_gw_logging

# Configure connections
for connection in "${connections[@]}"
do
    create_connection "$connection"
done

# Verify VMs/VNGs exist
for router in "${routers[@]}"
do
    show_bgp_neighbors "$router"
done

# Finish
echo "Your resources should be ready to use in resource group $rg. Username/password for access is ${default_username}/${psk}. Enjoy!"

################################
# Sample diagnostics commands: #
################################
# az network vnet-gateway list -g $rg -o table
# az network local-gateway list -g $rg -o table
# az network vpn-connection list -g $rg -o table
# az network public-ip list -g $rg -o table
# az network vnet-gateway list-bgp-peer-status -n vng1 -g $rg -o table
# az network vnet-gateway list-learned-routes -n vng1 -g $rg -o table
# az network vnet-gateway list-advertised-routes -n vng1 -g $rg -o table
# az network nic show-effective-route-table -n testvm1VMNic -g $rg -o table
# sh_csr_int 4
#############

Podemos ver que en el caso de un VNG Activo-Activo con 2 routers la tabla de BGP peers es como sigue BGP peers