summaryrefslogtreecommitdiffstats
path: root/tools/frr-reload.py
diff options
context:
space:
mode:
authorDinesh G Dutt <ddutt@cumulusnetworks.com>2017-01-08 16:08:12 +0100
committerDonald Sharp <sharpd@cumulusnetworks.com>2017-01-30 19:40:53 +0100
commitbb972e4465394f6ae319594b9b21c6f4726bf2e8 (patch)
treeeff7b413a239c72588731db6221d4faca179dd62 /tools/frr-reload.py
parentbgpd: do not allow prefix length on update-source (diff)
downloadfrr-bb972e4465394f6ae319594b9b21c6f4726bf2e8.tar.xz
frr-bb972e4465394f6ae319594b9b21c6f4726bf2e8.zip
tools: Normalize prefix-lists and IP networks for avoiding unnecessary reload
Ticket: CM-14280, CM-14281, CM-14286 Reviewed By: CCR-5546 Testing Done: quagga_service_test, bgp_enhe, bgp_vrf etc. If the user specifies a network statement such as "network 1.1.1.1/24", the running config shows this as "network 1.1.1.0/24" which causes unnecessary withdrawl of the prefix and re-advertisement causing perturbations. The same thing applies to prefix-lists and of course, IPv6 addresses. IPv6 addresses were being normalized already, and so we use that same function to handle the IPv6 portion of the issue. Interestingly community strings were also getting ensnared in the normalized IPv6 function due to the presence of ':', but thats OK. quagga's running config changes 'null0' and 'blackhole' keywords into 'Null0'. For example: ip route 10.1.1.0/24 blackhole' is displayed as 'ip route 10.1.1.0/24 Null0'. Reload mistakes this and issues a delete of the Null0 route followed by an add of the "blackhole" route. Unnecessary, and results in unexpected routing perturabations. Also fix prefix-list's le/ge behavior: It always prints ge first even if the user has specified le followed by ge, and it doesn't print l3 32/128 if ge is also specified, else it prints them. Signed-off-by: Dinesh Dutt <ddutt@cumulusnetworks.com>
Diffstat (limited to 'tools/frr-reload.py')
-rwxr-xr-xtools/frr-reload.py113
1 files changed, 107 insertions, 6 deletions
diff --git a/tools/frr-reload.py b/tools/frr-reload.py
index 57e9f026e..d26758ab1 100755
--- a/tools/frr-reload.py
+++ b/tools/frr-reload.py
@@ -38,7 +38,7 @@ import string
import subprocess
import sys
from collections import OrderedDict
-from ipaddr import IPv6Address
+from ipaddr import IPv6Address, IPNetwork
from pprint import pformat
@@ -173,6 +173,98 @@ class Config(object):
if not key:
return
+ '''
+ IP addresses specified in "network" statements, "ip prefix-lists"
+ etc. can differ in the host part of the specification the user
+ provides and what the running config displays. For example, user
+ can specify 11.1.1.1/24, and the running config displays this as
+ 11.1.1.0/24. Ensure we don't do a needless operation for such
+ lines. IS-IS & OSPFv3 have no "network" support.
+ '''
+ re_key_rt = re.match(r'ip\s+route\s+([A-Fa-f:.0-9/]+)(.*)$', key[0])
+ if re_key_rt:
+ addr = re_key_rt.group(2)
+ if '/' in addr:
+ try:
+ newaddr = IPNetwork(addr)
+ key[0] = 'ip %s %s/%s%s' % (re_key_rt.group(1),
+ newaddr.network,
+ newaddr.prefixlen,
+ re_key_rt.group(3))
+ except ValueError:
+ pass
+
+ re_key_rt = re.match(
+ r'(ip|ipv6)\s+prefix-list(.*)(permit|deny)\s+([A-Fa-f:.0-9/]+)(.*)$',
+ key[0]
+ )
+ if re_key_rt:
+ addr = re_key_rt.group(4)
+ if '/' in addr:
+ try:
+ newaddr = '%s/%s' % (IPNetwork(addr).network,
+ IPNetwork(addr).prefixlen)
+ except ValueError:
+ newaddr = addr
+
+ legestr = re_key_rt.group(5)
+ re_lege = re.search(r'(.*)le\s+(\d+)\s+ge\s+(\d+)(.*)', legestr)
+ if re_lege:
+ legestr = '%sge %s le %s%s' % (re_lege.group(1),
+ re_lege.group(3),
+ re_lege.group(2),
+ re_lege.group(4))
+ re_lege = re.search(r'(.*)ge\s+(\d+)\s+le\s+(\d+)(.*)', legestr)
+
+ if (re_lege and ((re_key_rt.group(1) == "ip" and
+ re_lege.group(3) == "32") or
+ (re_key_rt.group(1) == "ipv6" and
+ re_lege.group(3) == "128"))):
+ legestr = '%sge %s%s' % (re_lege.group(1),
+ re_lege.group(2),
+ re_lege.group(4))
+
+ key[0] = '%s prefix-list%s%s %s%s' % (re_key_rt.group(1),
+ re_key_rt.group(2),
+ re_key_rt.group(3),
+ newaddr,
+ legestr)
+
+ if lines and key[0].startswith('router bgp'):
+ newlines = []
+ for line in lines:
+ re_net = re.match(r'network\s+([A-Fa-f:.0-9/]+)(.*)$', line)
+ if re_net:
+ addr = re_net.group(1)
+ if '/' not in addr and key[0].startswith('router bgp'):
+ # This is most likely an error because with no
+ # prefixlen, BGP treats the prefixlen as 8
+ addr = addr + '/8'
+
+ try:
+ newaddr = IPNetwork(addr)
+ line = 'network %s/%s %s' % (newaddr.network,
+ newaddr.prefixlen,
+ re_net.group(2))
+ newlines.append(line)
+ except:
+ # Really this should be an error. Whats a network
+ # without an IP Address following it ?
+ newlines.append(line)
+ else:
+ newlines.append(line)
+ lines = newlines
+
+ '''
+ More fixups in user specification and what running config shows.
+ "null0" in routes must be replaced by Null0, and "blackhole" must
+ be replaced by Null0 as well.
+ '''
+ if (key[0].startswith('ip route') or key[0].startswith('ipv6 route') and
+ 'null0' in key[0] or 'blackhole' in key[0]):
+ key[0] = re.sub(r'\s+null0(\s*$)', ' Null0', key[0])
+ key[0] = re.sub(r'\s+blackhole(\s*$)', ' Null0', key[0])
+
if lines:
if tuple(key) not in self.contexts:
ctx = Context(tuple(key), lines)
@@ -437,16 +529,25 @@ def get_normalized_ipv6_line(line):
"""
Return a normalized IPv6 line as produced by frr,
with all letters in lower case and trailing and leading
- zeros removed
+ zeros removed, and only the network portion present if
+ the IPv6 word is a network
"""
norm_line = ""
words = line.split(' ')
for word in words:
if ":" in word:
- try:
- norm_word = str(IPv6Address(word)).lower()
- except:
- norm_word = word
+ norm_word = None
+ if "/" in word:
+ try:
+ v6word = IPNetwork(word)
+ norm_word = '%s/%s' % (v6word.network, v6word.prefixlen)
+ except ValueError:
+ pass
+ if not norm_word:
+ try:
+ norm_word = '%s' % IPv6Address(word)
+ except:
+ norm_word = word
else:
norm_word = word
norm_line = norm_line + " " + norm_word