|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +# Author: Salvatore Cuzzilla |
| 4 | +# em@il: salvatore@cuzzilla.org |
| 5 | +# Starting date: 07-01-2021 |
| 6 | +# Last change date: 19-01-2020 |
| 7 | +# Release date: TBD |
| 8 | +# Description: extract L3VPN overlay cfg from an MPLS router |
| 9 | + |
| 10 | + |
| 11 | +set -o errexit |
| 12 | +set -o nounset |
| 13 | +set -o pipefail |
| 14 | + |
| 15 | +work_dir=$(dirname "$(readlink --canonicalize-existing "${0}" 2> /dev/null)") |
| 16 | + |
| 17 | +readonly epoch=$(date +'%s') |
| 18 | +readonly error_validating_input=79 |
| 19 | +readonly error_reading_file=80 |
| 20 | +readonly error_parsing_options=81 |
| 21 | +readonly error_missing_options=82 |
| 22 | +readonly error_unknown_options=83 |
| 23 | +readonly error_missing_options_arg=84 |
| 24 | +readonly error_unimplemented_options=85 |
| 25 | +readonly readonly script_name="${0##*/}" |
| 26 | + |
| 27 | +f_option_flag=0 |
| 28 | +i_option_flag=0 |
| 29 | +h_option_flag=0 |
| 30 | + |
| 31 | +trap clean_up ERR SIGINT SIGTERM |
| 32 | + |
| 33 | +usage() { |
| 34 | + cat <<MAN |
| 35 | + Usage: ${script_name} [-f <ARG> ] [ -i <ARG> ] || [-h] |
| 36 | + |
| 37 | + DESCRIPTION: |
| 38 | + this tool can be used the extract the overaly configuration (MPLS-L3VPN) |
| 39 | + from MPLS-PE routers based on CISCO-XR OS |
| 40 | + |
| 41 | + OPTIONS: |
| 42 | + -h |
| 43 | + Print this help and exit |
| 44 | + -f |
| 45 | + [mandatory] Specify the MPLS-PE configuration file - Must be in 'cisco/formal' format |
| 46 | + -i |
| 47 | + [mandatory] Specify Bundle-Ethernet interface/s |
| 48 | + (Examples: |
| 49 | + - Bundle-Ether7 is considering all sub-if belonging to bundle number 7 |
| 50 | + - Bundle-Ether7.100 is considering only sub-if 100) |
| 51 | +MAN |
| 52 | +} |
| 53 | + |
| 54 | +clean_up() { |
| 55 | + if [[ -d "${work_dir}/lsts/${pe_hostname}_${epoch}" ]]; then |
| 56 | + echo -e "Deleting: ${work_dir}/lsts/${pe_hostname}_${epoch} ..." |
| 57 | + rm -rf "${work_dir}/lsts/${pe_hostname}_${epoch}" |
| 58 | + fi |
| 59 | + |
| 60 | + if [[ -d "${work_dir}/cfgs/${pe_hostname}_${epoch}" ]]; then |
| 61 | + echo -e "Deleting: ${work_dir}/cfgs/${pe_hostname}_${epoch} ..." |
| 62 | + rm -rf "${work_dir}/cfgs/${pe_hostname}_${epoch}" |
| 63 | + fi |
| 64 | +} |
| 65 | + |
| 66 | +die() { |
| 67 | + local -r msg="${1}" |
| 68 | + local -r code="${2:-90}" |
| 69 | + echo "${msg}" >&2 |
| 70 | + exit "${code}" |
| 71 | +} |
| 72 | + |
| 73 | +parse_user_options() { |
| 74 | + while getopts ":f:i:h" opts; do |
| 75 | + case "${opts}" in |
| 76 | + f) |
| 77 | + f_option_flag=1 |
| 78 | + readonly f_arg="${OPTARG}" |
| 79 | + ;; |
| 80 | + i) |
| 81 | + i_option_flag=1 |
| 82 | + readonly i_arg="${OPTARG}" |
| 83 | + ;; |
| 84 | + h) |
| 85 | + h_option_flag=1 |
| 86 | + ;; |
| 87 | + :) |
| 88 | + die "error - mind your options/arguments - [ -h ] to know more" "${error_unknown_options}" |
| 89 | + ;; |
| 90 | + \?) |
| 91 | + die "error - mind your options/arguments - [ -h ] to know more" "${error_missing_options_arg}" |
| 92 | + ;; |
| 93 | + *) |
| 94 | + die "error - mind your options/arguments - [ -h ] to know more" "${error_unimplemented_options}" |
| 95 | + ;; |
| 96 | + esac |
| 97 | + done |
| 98 | +} |
| 99 | +shift $((OPTIND -1)) |
| 100 | + |
| 101 | +# Generating the lst "bundle-if,vrf" main source to be able to extract the level2 lst & the level1 cf |
| 102 | +# Input: $f_arg && $i_arg |
| 103 | +# Output: $level1_lst |
| 104 | +gen_level1_lst_rgx1() { |
| 105 | + echo -e "Generating ${level1_lst} from ${pe_formal_cf} ..." |
| 106 | + $(egrep "^interface ${pe_bundle_if}\svrf" "${pe_formal_cf}" | awk -F " " '{print $2","$4}' > "${level1_lst}") |
| 107 | +} |
| 108 | + |
| 109 | +gen_level1_lst_rgx2() { |
| 110 | + echo -e "Generating ${level1_lst} from ${pe_formal_cf} ..." |
| 111 | + $(egrep "^interface ${pe_bundle_if}.*\svrf" "${pe_formal_cf}" | awk -F " " '{print $2","$4}' > "${level1_lst}") |
| 112 | +} |
| 113 | + |
| 114 | +# Generating the lst for policy-maps & route-policies to be able to extract the associated overaly cf |
| 115 | +# Input: $f_arg && $level1_lst |
| 116 | +# Output: $level2_pm_lst && $level2_rpl_lst |
| 117 | +gen_level2_lst() { |
| 118 | + if [[ ! -f "${level1_lst}" ]]; then |
| 119 | + die "error - reading file: ${level1_lst}" "${error_reading_file}" |
| 120 | + fi |
| 121 | + |
| 122 | + echo -e "Generating ${level2_pm_lst} && ${level2_rpl_lst} from ${pe_formal_cf} ..." |
| 123 | + |
| 124 | + while read -r line |
| 125 | + do |
| 126 | + local be_if="$(echo "${line}" | awk -F "," '{print $1}')" |
| 127 | + local vrf_if="$(echo "${line}" | awk -F "," '{print $2}')" |
| 128 | + # to be able to distinguish between rpl-00 & rpl-01 |
| 129 | + local vrf_if_rpl="${vrf_if::-3}" |
| 130 | + # workaround to cover rpl name unmatching the vrf name |
| 131 | + local vrf_if_rpl_unmatch="${vrf_if_rpl#"NGDCS-"}" |
| 132 | + |
| 133 | + if [[ ${vrf_if} != "hsrp" ]]; then |
| 134 | + local policy_map=$(egrep "${vrf_if}|${be_if}$be_if_pad" "${pe_formal_cf}" | egrep "^policy-map.*") |
| 135 | + #local route_policy=$(egrep "${vrf_if_rpl}|${be_if}$be_if_pad" "${pe_formal_cf}" | egrep "^route-policy.*") |
| 136 | + # workaround to cover rpl name unmatching the vrf name |
| 137 | + local route_policy=$(egrep "${vrf_if_rpl_unmatch}|${be_if}$be_if_pad" "${pe_formal_cf}" | egrep "^route-policy.*") |
| 138 | + |
| 139 | + if [[ ! -z "${policy_map}" ]]; then |
| 140 | + echo -e "${policy_map}" >> "${level2_pm_lst}" |
| 141 | + else |
| 142 | + echo -e "pm empty for line ${be_if},${vrf_if}" >> "${level2_pm_empty_lst}" |
| 143 | + fi |
| 144 | + |
| 145 | + if [[ ! -z "${route_policy}" ]]; then |
| 146 | + echo -e "${route_policy}" >> "${level2_rpl_lst}" |
| 147 | + else |
| 148 | + echo -e "rpl empty for line ${be_if},${vrf_if}" >> "${level2_rpl_empty_lst}" |
| 149 | + fi |
| 150 | + fi |
| 151 | + done < "${level1_lst}" |
| 152 | +} |
| 153 | + |
| 154 | +# Generating level1 cf ( vrf, if, router_[bgp|static|hsrp] ) |
| 155 | +# Input: $f_arg && $level1_lst |
| 156 | +# Output: $level1_vrf_cf && $level1_if_cf && $level1_rbgp_cf && $level1_rstatic_cf && $level1_hsrp_cf |
| 157 | +gen_level1_cf() { |
| 158 | + while read -r line |
| 159 | + do |
| 160 | + local be_if="$(echo "${line}" | awk -F "," '{print $1}')" |
| 161 | + local vrf_if="$(echo "${line}" | awk -F "," '{print $2}')" |
| 162 | + |
| 163 | + if [[ ${vrf_if} != "hsrp" ]]; then |
| 164 | + local vrf=$(egrep "${vrf_if}|${be_if}$be_if_pad" "${pe_formal_cf}" | egrep "^vrf.*") |
| 165 | + local interface=$(egrep "${vrf_if}|${be_if}$be_if_pad" "${pe_formal_cf}" | egrep "^interface.*") |
| 166 | + local router_bgp=$(egrep "${vrf_if}|${be_if}$be_if_pad" "${pe_formal_cf}" | egrep "^router\sbgp.*") |
| 167 | + local router_static=$(egrep "${vrf_if}|${be_if}$be_if_pad" "${pe_formal_cf}" | egrep "^router\sstatic.*") |
| 168 | + local router_hsrp=$(egrep "${vrf_if}|${be_if}$be_if_pad" "${pe_formal_cf}" | egrep "^router\shsrp.*") |
| 169 | + |
| 170 | + echo -e "Extracting Level1 MPLS-PEs overlay configrations for ${be_if} ..." |
| 171 | + |
| 172 | + if [[ ! -z "${vrf}" ]]; then |
| 173 | + echo -e "${vrf}" >> "${level1_vrf_cf}" |
| 174 | + else |
| 175 | + echo -e "vrf empty for line ${be_if},${vrf_if}" >> "${level1_vrf_empty_cf}" |
| 176 | + fi |
| 177 | + |
| 178 | + if [[ ! -z "${interface}" ]]; then |
| 179 | + echo -e "${interface}" >> "${level1_if_cf}" |
| 180 | + else |
| 181 | + echo -e "interface empty for line ${be_if},${vrf_if}" >> "${level1_if_empty_cf}" |
| 182 | + fi |
| 183 | + |
| 184 | + if [[ ! -z "${router_bgp}" ]]; then |
| 185 | + echo -e "${router_bgp}" >> "${level1_rbgp_cf}" |
| 186 | + else |
| 187 | + echo -e "router_bgp empty for line ${be_if},${vrf_if}" >> "${level1_rbgp_empty_cf}" |
| 188 | + fi |
| 189 | + |
| 190 | + if [[ ! -z "${router_static}" ]]; then |
| 191 | + echo -e "${router_static}" >> "${level1_rstatic_cf}" |
| 192 | + else |
| 193 | + echo -e "router_static empty for line ${be_if},${vrf_if}" >> "${level1_rstatic_empty_cf}" |
| 194 | + fi |
| 195 | + |
| 196 | + if [[ ! -z "${router_hsrp}" ]]; then |
| 197 | + echo -e "${router_hsrp}" >> "${level1_hsrp_cf}" |
| 198 | + else |
| 199 | + echo -e "router_hsrp empty for line ${be_if},${vrf_if}" >> "${level1_router_hsrp_empty_cf}" |
| 200 | + fi |
| 201 | + fi |
| 202 | + done < "${level1_lst}" |
| 203 | +} |
| 204 | + |
| 205 | +# Generating level2 cf ( policy_map, route_policy ) |
| 206 | +# Input: $f_arg && $level2_pm_lst && $level2_rpl_lst |
| 207 | +# Output: $level2_pm_cf && $level2_rpl_cf |
| 208 | +gen_level2_cf() { |
| 209 | + if [[ ! -f "${level2_rpl_lst}" ]]; then |
| 210 | + die "error - reading file: ${level2_rpl_lst}" "${error_reading_file}" |
| 211 | + fi |
| 212 | + |
| 213 | + if [[ ! -f "${level2_pm_lst}" ]]; then |
| 214 | + die "error - reading file: ${level2_pm_lst}" "${error_reading_file}" |
| 215 | + fi |
| 216 | + |
| 217 | + while read -r line |
| 218 | + do |
| 219 | + if [[ ! -z "${line}" ]]; then |
| 220 | + rpl=$(sed -n "/^${line}/,/end-policy/p" "${pe_formal_cf}") |
| 221 | + |
| 222 | + echo -e "Extracting Level2 MPLS-PEs overlay configrations for ${line} ..." |
| 223 | + |
| 224 | + if [[ ! -z "${rpl}" ]]; then |
| 225 | + echo -e "${rpl}" >> "${level2_rpl_cf}" |
| 226 | + echo -e "!" >> "${level2_rpl_cf}" |
| 227 | + else |
| 228 | + echo -e "rpl empty for line ${be_if},${vrf_if}" >> "${level2_rpl_empty_cf}" |
| 229 | + fi |
| 230 | + fi |
| 231 | + done < "${level2_rpl_lst}" |
| 232 | + |
| 233 | + while read -r line |
| 234 | + do |
| 235 | + if [[ ! -z "${line}" ]]; then |
| 236 | + pm=$(sed -n "/^${line}/,/end-policy-map/p" "${pe_formal_cf}") |
| 237 | + |
| 238 | + echo -e "Extracting Level2 MPLS-PEs overlay configrations for ${line} ..." |
| 239 | + |
| 240 | + if [[ ! -z "${pm}" ]]; then |
| 241 | + echo -e "${pm}" >> "${level2_pm_cf}" |
| 242 | + echo -e "!" >> "${level2_pm_cf}" |
| 243 | + else |
| 244 | + echo -e "pm empty for line ${be_if},${vrf_if}" >> "${level2_pm_empty_cf}" |
| 245 | + fi |
| 246 | + fi |
| 247 | + done < "${level2_pm_lst}" |
| 248 | +} |
| 249 | + |
| 250 | +parse_user_options "${@}" |
| 251 | + |
| 252 | +if ((h_option_flag)); then |
| 253 | + usage |
| 254 | + exit 0 |
| 255 | +fi |
| 256 | + |
| 257 | +if ((f_option_flag)) && ((i_option_flag)); then |
| 258 | + readonly pe_hostname=$(echo "${f_arg}" | awk -F "." '{print $1}') |
| 259 | + readonly pe_formal_cf="${work_dir}/${f_arg}" |
| 260 | + readonly pe_bundle_if="${i_arg}" |
| 261 | + readonly be_if_pad=" " |
| 262 | + readonly level1_lst="${work_dir}/lsts/${pe_hostname}_${epoch}/level1.lst" |
| 263 | + readonly level2_lst="${work_dir}/lsts/${pe_hostname}_${epoch}/level2.lst" |
| 264 | + readonly level2_pm_lst="${work_dir}/lsts/${pe_hostname}_${epoch}/level2_pm.lst" |
| 265 | + readonly level2_rpl_lst="${work_dir}/lsts/${pe_hostname}_${epoch}/level2_rpl.lst" |
| 266 | + readonly level1_vrf_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level1_vrf.cf" |
| 267 | + readonly level1_if_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level1_if.cf" |
| 268 | + readonly level1_rbgp_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level1_rbgp.cf" |
| 269 | + readonly level1_rstatic_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level1_rstatic.cf" |
| 270 | + readonly level1_hsrp_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level1_hsrp.cf" |
| 271 | + readonly level2_rpl_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level2_rpl.cf" |
| 272 | + readonly level2_pm_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level2_pm.cf" |
| 273 | + |
| 274 | + readonly level2_pm_empty_lst="${work_dir}/lsts/${pe_hostname}_${epoch}/level2_pm_empty.lst" |
| 275 | + readonly level2_rpl_empty_lst="${work_dir}/lsts/${pe_hostname}_${epoch}/level2_rpl_empty.lst" |
| 276 | + readonly level1_vrf_empty_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level1_vrf_empty.cf" |
| 277 | + readonly level1_if_empty_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level1_if_empty.cf" |
| 278 | + readonly level1_rbgp_empty_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level1_rbgp_empty.cf" |
| 279 | + readonly level1_rstatic_empty_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level1_rstatic_empty.cf" |
| 280 | + readonly level1_hsrp_empty_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level1_hsrp_empty.cf" |
| 281 | + readonly level2_rpl_empty_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level2_rpl_empty.cf" |
| 282 | + readonly level2_pm_empty_cf="${work_dir}/cfgs/${pe_hostname}_${epoch}/level2_pm_empty.cf" |
| 283 | + |
| 284 | + readonly pe_bundle_if_rgx1="^Bundle-Ether[0-9]{1,5}\.[0-9]{1,5}$" |
| 285 | + readonly pe_bundle_if_rgx2="^Bundle-Ether[0-9]{1,5}$" |
| 286 | + |
| 287 | + if [[ ! -f "${pe_formal_cf}" ]]; then |
| 288 | + die "error - file reading failed: ${pe_formal_cf}" "${error_reading_file}" |
| 289 | + fi |
| 290 | + |
| 291 | + if [[ ! "${pe_bundle_if}" =~ ${pe_bundle_if_rgx1} ]] | [[ ! "${pe_bundle_if}" =~ ${pe_bundle_if_rgx2} ]]; then |
| 292 | + die "error - input validation failed: ${pe_bundle_if}" "${error_validating_input}" |
| 293 | + fi |
| 294 | + |
| 295 | + if [[ ! -d "${work_dir}/lsts/${pe_hostname}_${epoch}" ]]; then |
| 296 | + mkdir -p "${work_dir}/lsts/${pe_hostname}_${epoch}" |
| 297 | + fi |
| 298 | + |
| 299 | + if [[ ! -d "${work_dir}/cfgs/${pe_hostname}_${epoch}" ]]; then |
| 300 | + mkdir -p "${work_dir}/cfgs/${pe_hostname}_${epoch}" |
| 301 | + fi |
| 302 | + |
| 303 | + if [[ "${pe_bundle_if}" =~ ${pe_bundle_if_rgx1} ]]; then |
| 304 | + gen_level1_lst_rgx1 |
| 305 | + elif [[ "${pe_bundle_if}" =~ ${pe_bundle_if_rgx2} ]]; then |
| 306 | + gen_level1_lst_rgx2 |
| 307 | + fi |
| 308 | + |
| 309 | + gen_level2_lst |
| 310 | + gen_level1_cf |
| 311 | + gen_level2_cf |
| 312 | +else |
| 313 | + die "error - mind your options/arguments - [ -h ] to know more" "${error_missing_options}" |
| 314 | +fi |
| 315 | + |
| 316 | +exit 0 |
0 commit comments