Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 231 additions & 8 deletions show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@
import subprocess
import sys
import ipaddress
import commands

import click
from natsort import natsorted
from pkg_resources import parse_version
from sonic_py_common import device_info
from sonic_py_common import multi_asic
from swsssdk import ConfigDBConnector
from swsssdk import SonicV2Connector
from tabulate import tabulate
from utilities_common.db import Db
from utilities_common import constants
import utilities_common.cli as clicommon
import utilities_common.multi_asic as multi_asic_util

import mlnx
import feature
Expand All @@ -35,7 +39,7 @@
# location (configdb?), so that we prevent the continous execution of this
# bash oneliner. To be revisited once routing-stack info is tracked somewhere.
def get_routing_stack():
command = "sudo docker ps | grep bgp | awk '{print$2}' | cut -d'-' -f3 | cut -d':' -f1"
command = "sudo docker ps | grep bgp | awk '{print$2}' | cut -d'-' -f3 | cut -d':' -f1 | head -1"

try:
proc = subprocess.Popen(command,
Expand Down Expand Up @@ -888,23 +892,242 @@ def get_bgp_peer():
bgp_peer.setdefault(local_addr, [neighbor_name, neighbor_ip])
return bgp_peer

def print_ip_routes(route_info):
"""
Sample Entry output
B>*172.16.8.2/32 [20/0] via 10.0.0.47, Ethernet92, 03:33:01
B>*192.168.114.96/32 [20/0] via 10.0.0.1, PortChannel0002, 03:30:39
* via 10.0.0.5, PortChannel0005, 03:30:39
* via 10.0.0.9, PortChannel0008, 03:30:39
* via 10.0.0.13, PortChannel0011, 03:30:39
* via 10.0.0.17, PortChannel0014, 03:30:39
* via 10.0.0.21, PortChannel0017, 03:30:39
* via 10.0.0.25, PortChannel0020, 03:30:39
* via 10.0.0.29, PortChannel0023, 03:30:39
B 10.0.107.0/31 [200/0] via 10.0.107.1, inactive 00:10:15
K>*0.0.0.0/0 [0/0] via 10.3.146.1, eth0, 00:25:22
B 0.0.0.0/0 [20/0] via 10.0.0.1, PortChannel0002, 03:31:52
via 10.0.0.5, PortChannel0005, 03:31:52
via 10.0.0.9, PortChannel0008, 03:31:52
via 10.0.0.13, PortChannel0011, 03:31:52
via 10.0.0.17, PortChannel0014, 03:31:52
via 10.0.0.21, PortChannel0017, 03:31:52
via 10.0.0.25, PortChannel0020, 03:31:52
via 10.0.0.29, PortChannel0023, 03:31:52
S 0.0.0.0/0 [200/0] via 10.3.146.1, eth0, 03:35:18
C>*10.0.0.62/31 is directly connected, Ethernet124, 03:34:00
"""
# This method is following what zebra_vty.c does when handling the route parsing printing
# The route_info is a json file which is treated as a dictionary of a bunch of route + info
# This interpretation is based on FRR 7.2 branch. If we later moved on to a new branch, we may
# have to rexamine if there are any changes made that may impact the parsing logic
proto_code = {"system":'X', "kernel":'K', "connected":'C', "static":'S', "rip":'R', "ripng":'R',
"ospf":'O', "ospf6":'O', "isis":'I', "bgp":'B', "pim":'P', "hsls":'H', "olsr":'o', "babel":'A'}
mpls_label_code = { 0:"IPv4 Explicit Null", 1:"Router Alert", 2:"IPv6 Explicit Null", 3:"implicit-null", 4:"Reserved (4)",
5:"Reserved (5)", 6:"Reserved (6)", 7:"Entropy Label Indicator", 8:"Reserved (8)", 9:"Reserved (9)", 10:"Reserved (10)",
11:"Reserved (11)", 12:"Reserved (12)", 13:"Generic Associated Channel", 14:"OAM Alert", 15:"Extension"}
NH_F_IS_RECURSIVE = 2
NH_F_IS_DUPLICATE = 5
for route, info in route_info.items():
for i in range(0, len(info)):
str_2_print = ""
str_2_print += proto_code[info[i]['protocol']]
if "instance" in info[i]:
str_2_print += "[" + str(info[i]['instance']) + "]"
if "selected" in info[i]:
str_2_print += ">"
else:
str_2_print += " "
for j in range(0, len(info[i]['nexthops'])):
if j != 0:
str_2_print = " "
if "queued" in info[i]:
str_2_print += "q"
elif "failed" in info[i]:
str_2_print += "r"
elif "installed" in info[i]:
if info[i]['nexthops'][j]['flags'] & (1 << NH_F_IS_RECURSIVE) or info[i]['nexthops'][j]['flags'] & (1 << NH_F_IS_DUPLICATE):
str_2_print += " "
else:
str_2_print += "*"
# on 1st nexhop print the prefix and distance/metric if appropriate.
# on all subsequent nexthop replace the prefix and distance/metric by empty spaces only.
if j == 0:
str_2_print += info[i]['prefix']
if info[i]['protocol'] != "connected":
str_2_print += " [" + str(info[i]['distance']) + "/" + str(info[i]['metric']) + "]"
elif info[i]['distance'] > 0 or info[i]['metric'] > 0:
str_2_print += " [" + str(info[i]['distance']) + "/" + str(info[i]['metric']) + "]"
str_length = len(str_2_print)
else:
str_2_print += " "*(str_length - 3)
if "ip" in info[i]['nexthops'][j]:
str_2_print += " via " + info[i]['nexthops'][j]['ip'] + ","
if "interfaceName" in info[i]['nexthops'][j]:
str_2_print += " " + info[i]['nexthops'][j]['interfaceName'] + ","
elif "directlyConnected" in info[i]['nexthops'][j]:
str_2_print += " is directly connected,"
if "interfaceName" in info[i]['nexthops'][j]:
str_2_print += " " + info[i]['nexthops'][j]['interfaceName'] + ","
elif "unreachable" in info[i]['nexthops'][j]:
if "reject" in info[i]['nexthops'][j]:
str_2_print += " (ICMP unreachable)"
elif "admin-prohibited" in info[i]['nexthops'][j]:
str_2_print += " (ICMP admin-prohibited)"
elif "blackhole" in info[i]['nexthops'][j]:
str_2_print += " (blackhole)"
if "vrf" in info[i]['nexthops'][j]:
str_2_print += "(vrf " + info[i]['nexthops'][j]['vrf'] + ", " + info[i]['nexthops'][j]['interfaceName'] + ","
if "active" not in info[i]['nexthops'][j]:
str_2_print += " inactive"
if "onLink" in info[i]['nexthops'][j]:
str_2_print += " onlink"
if "recursive" in info[i]['nexthops'][j]:
str_2_print += " (recursive)"
if "source" in info[i]['nexthops'][j]:
str_2_print += ", src " + info[i]['nexthops'][j]['source']
if "labels" in info[i]['nexthops'][j]:
# MPLS labels are stored as an array (list) in json if present. Need to print through each one in list
str_2_print += ", label "
for k in range(0, len(info[i]['nexthops'][j]['labels'])):
# MPLS labels that has value 15 or lower has special interpretation
if info[i]['nexthops'][j]['labels'][k] > 15:
label_string = str(info[i]['nexthops'][j]['labels'][k])
else:
label_string = mpls_label_code[info[i]['nexthops'][j]['labels'][k]]
if k == 0:
str_2_print += label_string
else:
str_2_print += "/" + label_string
# add uptime at the end of the string
str_2_print += " " + info[i]['uptime']
# print out this string
print str_2_print

def print_show_ip_route_hdr():
# This prints out the show ip route header based on FRR 7.2 version.
# Please note that if we moved to future versions, we may heva to make changes to this
print("Codes: K - kernel route, C - connected, S - static, R - RIP,")
print(" O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,")
print(" T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,")
print(" F - PBR, f - OpenFabric,")
print(" > - selected route, * - FIB route, q - queued route, r - rejected route\n")

#
# 'route' subcommand ("show ip route")
#

@ip.command()
@click.argument('args', metavar='[IPADDRESS] [vrf <vrf_name>] [...]', nargs=-1, required=False)
@multi_asic_util.multi_asic_click_options
@click.option('--verbose', is_flag=True, help="Enable verbose output")
def route(args, verbose):
def route(args, namespace, display, verbose):
"""Show IP (IPv4) routing table"""
cmd = 'sudo vtysh -c "show ip route'

is_multi_asic = multi_asic.is_multi_asic()
device = multi_asic_util.MultiAsic(display, namespace)
arg_strg = ""
found_json = 0
hdr_printed = False
del_cnt = 0
if is_multi_asic:
# handling multi-ASIC by gathering the output from specified/all name space into a dictionary via
# jason option and then filter out the json entries (by removing those next Hop that are
# back-end ASIC if display is for front-end only). If the entry itself has no more next Hop after filtering,
# then skip over that particular route entry. Once completed, if the user chose "json" option,
# then just print out the dictionary in Json format accordingly. But if no "json" option specified,
# then print out the header and the decoded entry representation for each route accordingly.
# This code is based on FRR 7.2 branch. If we moved to a new version we may need to change here as well
asic_cnt = multi_asic.get_num_asics()
if display == "all":
filter_back_end = False
else:
filter_back_end = True
if str(namespace) == "None":
start_range = 0
end_range = asic_cnt
else:
# -n asic0|asic1|...|asicN So skip the first 4 chars to get the namespace number only
start_range = int(namespace[4:])
end_range = start_range + 1
else:
asic_cnt = 1
filter_back_end = False
start_range = 0
end_range = asic_cnt
# get all the other arguments except json that needs to be the last argument of the cmd if present
for arg in args:
cmd += " " + str(arg)

cmd += '"'
arg_strg += " " + str(arg)
if str(arg) == "json":
found_json = 1
if not found_json:
arg_strg += " json"

for ns in range(start_range, end_range):
filtered_route = {}
# Need to add "ns" to form bgpX so it is sent to the correct bgpX docker to handle the request
# If not MultiASIC, use space instad of ns number
if is_multi_asic:
ns_str = str(ns)
else:
ns_str = " "
cmd = "sudo docker exec bgp{0} vtysh -c 'show ip route {1}'".format(ns_str, arg_strg)
output = commands.getoutput(cmd)
# in case no output or something went wrong with user specified cmd argument(s) error it out
# error from FRR always start with character "%"
if output == "":
return
if output[0] == "%":
# remove the "json" keyword that was added by this handler so it wont confuse the user
error_msg = output[:-4]
print error_msg
return
route_info = json.loads(output)
if filter_back_end:
# clean up the dictionary to remove all the nexthops that are back-end interface
for route, info in route_info.items():
new_info_l = []
new_info_cnt = 0
for i in range(0, len(info)):
new_info = info[i]
new_nhop_l = []
del_cnt = 0
while len(info[i]['nexthops']):
nh = info[i]['nexthops'].pop(0)
if "interfaceName" in nh:
obj_type = constants.PORT_CHANNEL_OBJ
if nh['interfaceName'].find('Ethernet') != -1:
obj_type = constants.PORT_OBJ
if device.skip_display(obj_type, nh['interfaceName']):
del_cnt += 1
else:
new_nhop_l.append(nh)
else:
new_nhop_l.append(nh)
# use the new filtered nhop list if it is not empty. if empty nexthop , this route is filtered out completely
if len(new_nhop_l) > 0:
new_info['nexthops'] = new_nhop_l
new_info_cnt += 1
# in case there are any nexthop that were deleted, we will need to adjust the nexhopt counts as well
if del_cnt > 0:
internalNextHopNum = info[i]['internalNextHopNum'] - del_cnt
new_info['internalNextHopNum'] = internalNextHopNum
internalNextHopActiveNum = info[i]['internalNextHopActiveNum'] - del_cnt
new_info['internalNextHopActiveNum'] = internalNextHopActiveNum
new_info_l.append(new_info)
if new_info_cnt:
filtered_route[route] = new_info_l
else:
filtered_route = route_info
if not found_json:
#print out the header if this is not a json request
if not hdr_printed:
print_show_ip_route_hdr()
hdr_printed = True
print_ip_routes(filtered_route)
else:
new_string = json.dumps(filtered_route, indent=2)
print(new_string)

run_command(cmd, display_cmd=verbose)

#
# 'prefix-list' subcommand ("show ip prefix-list")
Expand Down