summaryrefslogtreecommitdiff
path: root/tools/frr-reload.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/frr-reload.py')
-rwxr-xr-xtools/frr-reload.py178
1 files changed, 171 insertions, 7 deletions
diff --git a/tools/frr-reload.py b/tools/frr-reload.py
index 463784de11..1cad55ac34 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,100 @@ 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|ipv6)\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] = '%s route %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
+ else:
+ 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 ValueError:
+ # 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 +531,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 ValueError:
+ norm_word = word
else:
norm_word = word
norm_line = norm_line + " " + norm_word
@@ -579,6 +682,60 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
lines_to_add_to_del.append((ctx_keys, swpx_interface))
lines_to_add_to_del.append((tmp_ctx_keys, swpx_remoteas))
+ '''
+ In 3.0, we made bgp bestpath multipath as-relax command
+ automatically assume no-as-set since the lack of this option caused
+ weird routing problems and this problem was peculiar to this
+ implementation. When the running config is shown in relases after
+ 3.0, the no-as-set is not shown as its the default. This causes
+ reload to unnecessarily unapply this option to only apply it back
+ again, causing unnecessary session resets. Handle this.
+ '''
+ if ctx_keys[0].startswith('router bgp') and line and 'multipath-relax' in line:
+ re_asrelax_new = re.search('^bgp\s+bestpath\s+as-path\s+multipath-relax$', line)
+ old_asrelax_cmd = 'bgp bestpath as-path multipath-relax no-as-set'
+ found_asrelax_old = line_exist(lines_to_add, ctx_keys, old_asrelax_cmd)
+
+ if re_asrelax_new and found_asrelax_old:
+ deleted = True
+ lines_to_del_to_del.append((ctx_keys, line))
+ lines_to_add_to_del.append((ctx_keys, old_asrelax_cmd))
+
+ '''
+ More old-to-new config handling. ip import-table no longer accepts
+ distance, but we honor the old syntax. But 'show running' shows only
+ the new syntax. This causes an unnecessary 'no import-table' followed
+ by the same old 'ip import-table' which causes perturbations in
+ announced routes leading to traffic blackholes. Fix this issue.
+ '''
+ re_importtbl = re.search('^ip\s+import-table\s+(\d+)$', ctx_keys[0])
+ if re_importtbl:
+ table_num = re_importtbl.group(1)
+ for ctx in lines_to_add:
+ if ctx[0][0].startswith('ip import-table %s distance' % table_num):
+ lines_to_del_to_del.append((('ip import-table %s' % table_num,), None))
+ lines_to_add_to_del.append((ctx[0], None))
+
+ '''
+ ip/ipv6 prefix-list can be specified without a seq number. However,
+ the running config always adds 'seq x', where x is a number incremented
+ by 5 for every element, to the prefix list. So, ignore such lines as
+ well. Sample prefix-list lines:
+ ip prefix-list PR-TABLE-2 seq 5 permit 20.8.2.0/24 le 32
+ ip prefix-list PR-TABLE-2 seq 10 permit 20.8.2.0/24 le 32
+ ipv6 prefix-list vrfdev6-12 permit 2000:9:2::/64 gt 64
+ '''
+ re_ip_pfxlst = re.search('^(ip|ipv6)(\s+prefix-list\s+)(\S+\s+)(seq \d+\s+)(permit|deny)(.*)$',
+ ctx_keys[0])
+ if re_ip_pfxlst:
+ tmpline = (re_ip_pfxlst.group(1) + re_ip_pfxlst.group(2) +
+ re_ip_pfxlst.group(3) + re_ip_pfxlst.group(5) +
+ re_ip_pfxlst.group(6))
+ for ctx in lines_to_add:
+ if ctx[0][0] == tmpline:
+ lines_to_del_to_del.append((ctx_keys, None))
+ lines_to_add_to_del.append(((tmpline,), None))
+
if not deleted:
found_add_line = line_exist(lines_to_add, ctx_keys, line)
@@ -646,6 +803,11 @@ def compare_context_objects(newconf, running):
delete_bgpd = True
lines_to_del.append((running_ctx_keys, None))
+ # We cannot do 'no interface' in quagga, and so deal with it
+ elif running_ctx_keys[0].startswith('interface'):
+ for line in running_ctx.lines:
+ lines_to_del.append((running_ctx_keys, line))
+
# If this is an address-family under 'router bgp' and we are already deleting the
# entire 'router bgp' context then ignore this sub-context
elif "router bgp" in running_ctx_keys[0] and len(running_ctx_keys) > 1 and delete_bgpd:
@@ -697,6 +859,7 @@ if __name__ == '__main__':
parser.add_argument('--debug', action='store_true', help='Enable debugs', default=False)
parser.add_argument('--stdout', action='store_true', help='Log to STDOUT', default=False)
parser.add_argument('filename', help='Location of new frr config file')
+ parser.add_argument('--overwrite', action='store_true', help='Overwrite Quagga.conf with running config output', default=False)
args = parser.parse_args()
# Logging
@@ -904,5 +1067,6 @@ if __name__ == '__main__':
subprocess.call(['/usr/bin/vtysh', '-f', filename])
os.unlink(filename)
- # Make these changes persistent
+ # Make these changes persistent
+ if args.overwrite or args.filename != '/etc/quagga/Quagga.conf':
subprocess.call(['/usr/bin/vtysh', '-c', 'write'])