Skip to content

Commit 3c977ea

Browse files
arlakshmanand-kumar-subramanian
authored andcommitted
[multi-asic] show ip interface changes for multi asic (sonic-net#1396)
Signed-off-by: Arvindsrinivasan Lakshmi Narasimhan <[email protected]> - What I did The PR has changes to support command show ip interface for multi asic platform. - How I did it The following changes are done in this PR The code for show ip interface has been moved from show/main.py to scripts\ipintutil - Modify the code to support both single and multi asic platform - To do this the library pyroute2 is used to get interface information in each namespace - Add the following options for multi asic [-n, --namespace] to allow user to display the information for given namespaces If this option is not present the information from all the namespaces will be displayed [-d, --display] to allow user to display information related both internal and external interfaces If this option is not present only external interfaces/neighbors will be display - Add unit tests for single and multi asic
1 parent cd65200 commit 3c977ea

File tree

9 files changed

+636
-171
lines changed

9 files changed

+636
-171
lines changed

scripts/ipintutil

+273
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import subprocess
5+
import sys
6+
7+
import netaddr
8+
import netifaces
9+
from natsort import natsorted
10+
from tabulate import tabulate
11+
12+
from sonic_py_common import multi_asic
13+
from swsssdk import ConfigDBConnector, SonicDBConfig
14+
from utilities_common import constants
15+
from utilities_common import multi_asic as multi_asic_util
16+
17+
18+
try:
19+
if os.environ["UTILITIES_UNIT_TESTING"] == "2":
20+
21+
modules_path = os.path.join(os.path.dirname(__file__), "..")
22+
tests_path = os.path.join(modules_path, "tests")
23+
sys.path.insert(0, modules_path)
24+
sys.path.insert(0, tests_path)
25+
import mock_tables.dbconnector
26+
if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic":
27+
import mock_tables.mock_multi_asic
28+
mock_tables.dbconnector.load_namespace_config()
29+
else:
30+
import mock_tables.mock_single_asic
31+
except KeyError:
32+
pass
33+
34+
35+
def get_bgp_peer():
36+
"""
37+
collects local and bgp neighbor ip along with device name in below format
38+
{
39+
'local_addr1':['neighbor_device1_name', 'neighbor_device1_ip'],
40+
'local_addr2':['neighbor_device2_name', 'neighbor_device2_ip']
41+
}
42+
"""
43+
bgp_peer = {}
44+
config_db = ConfigDBConnector()
45+
config_db.connect()
46+
data = config_db.get_table('BGP_NEIGHBOR')
47+
48+
for neighbor_ip in data.keys():
49+
local_addr = data[neighbor_ip]['local_addr']
50+
neighbor_name = data[neighbor_ip]['name']
51+
bgp_peer.setdefault(local_addr, [neighbor_name, neighbor_ip])
52+
return bgp_peer
53+
54+
55+
def skip_ip_intf_display(interface, display_option):
56+
if display_option != constants.DISPLAY_ALL:
57+
if interface.startswith('Ethernet') and multi_asic.is_port_internal(interface):
58+
return True
59+
elif interface.startswith('PortChannel') and multi_asic.is_port_channel_internal(interface):
60+
return True
61+
elif interface.startswith('Loopback4096'):
62+
return True
63+
elif interface.startswith('eth0'):
64+
return True
65+
elif interface.startswith('veth'):
66+
return True
67+
return False
68+
69+
70+
def get_if_admin_state(iface, namespace):
71+
"""
72+
Given an interface name, return its admin state reported by the kernel
73+
"""
74+
cmd = "cat /sys/class/net/{0}/flags".format(iface)
75+
if namespace != constants.DEFAULT_NAMESPACE:
76+
cmd = "sudo ip netns exec {} {}".format(namespace, cmd)
77+
try:
78+
proc = subprocess.Popen(
79+
cmd,
80+
shell=True,
81+
stderr=subprocess.STDOUT,
82+
stdout=subprocess.PIPE,
83+
text=True)
84+
state_file = proc.communicate()[0]
85+
proc.wait()
86+
87+
except OSError:
88+
print("Error: unable to get admin state for {}".format(iface))
89+
return "error"
90+
91+
try:
92+
content = state_file.rstrip()
93+
flags = int(content, 16)
94+
except ValueError:
95+
return "error"
96+
97+
if flags & 0x1:
98+
return "up"
99+
else:
100+
return "down"
101+
102+
103+
def get_if_oper_state(iface, namespace):
104+
"""
105+
Given an interface name, return its oper state reported by the kernel.
106+
"""
107+
cmd = "cat /sys/class/net/{0}/carrier".format(iface)
108+
if namespace != constants.DEFAULT_NAMESPACE:
109+
cmd = "sudo ip netns exec {} {}".format(namespace, cmd)
110+
try:
111+
proc = subprocess.Popen(
112+
cmd,
113+
shell=True,
114+
stderr=subprocess.STDOUT,
115+
stdout=subprocess.PIPE,
116+
text=True)
117+
state_file = proc.communicate()[0]
118+
proc.wait()
119+
120+
except OSError:
121+
print("Error: unable to get oper state for {}".format(iface))
122+
return "error"
123+
124+
oper_state = state_file.rstrip()
125+
if oper_state == "1":
126+
return "up"
127+
else:
128+
return "down"
129+
130+
131+
def get_if_master(iface):
132+
"""
133+
Given an interface name, return its master reported by the kernel.
134+
"""
135+
oper_file = "/sys/class/net/{0}/master"
136+
if os.path.exists(oper_file.format(iface)):
137+
real_path = os.path.realpath(oper_file.format(iface))
138+
return os.path.basename(real_path)
139+
else:
140+
return ""
141+
142+
143+
def get_ip_intfs_in_namespace(af, namespace, display):
144+
"""
145+
Get all the ip intefaces from the kernel for the given namespace
146+
"""
147+
ip_intfs = {}
148+
interfaces = multi_asic_util.multi_asic_get_ip_intf_from_ns(namespace)
149+
bgp_peer = get_bgp_peer()
150+
for iface in interfaces:
151+
ip_intf_attr = []
152+
if namespace != constants.DEFAULT_NAMESPACE and skip_ip_intf_display(iface, display):
153+
continue
154+
ipaddresses = multi_asic_util.multi_asic_get_ip_intf_addr_from_ns(namespace, iface)
155+
if af in ipaddresses:
156+
ifaddresses = []
157+
bgp_neighs = {}
158+
ip_intf_attr = []
159+
for ipaddr in ipaddresses[af]:
160+
neighbor_name = 'N/A'
161+
neighbor_ip = 'N/A'
162+
local_ip = str(ipaddr['addr'])
163+
if af == netifaces.AF_INET:
164+
netmask = netaddr.IPAddress(ipaddr['netmask']).netmask_bits()
165+
else:
166+
netmask = ipaddr['netmask'].split('/', 1)[-1]
167+
local_ip_with_mask = "{}/{}".format(local_ip, str(netmask))
168+
ifaddresses.append(["", local_ip_with_mask])
169+
try:
170+
neighbor_name = bgp_peer[local_ip][0]
171+
neighbor_ip = bgp_peer[local_ip][1]
172+
except KeyError:
173+
pass
174+
175+
bgp_neighs.update({local_ip_with_mask: [neighbor_name, neighbor_ip]})
176+
177+
if len(ifaddresses) > 0:
178+
admin = get_if_admin_state(iface, namespace)
179+
oper = get_if_oper_state(iface, namespace)
180+
master = get_if_master(iface)
181+
182+
ip_intf_attr = {
183+
"vrf": master,
184+
"ipaddr": natsorted(ifaddresses),
185+
"admin": admin,
186+
"oper": oper,
187+
"bgp_neighs": bgp_neighs,
188+
"ns": namespace
189+
}
190+
191+
ip_intfs[iface] = ip_intf_attr
192+
return ip_intfs
193+
194+
195+
def display_ip_intfs(ip_intfs):
196+
header = ['Interface', 'Master', 'IPv4 address/mask',
197+
'Admin/Oper', 'BGP Neighbor', 'Neighbor IP']
198+
data = []
199+
for ip_intf, v in natsorted(ip_intfs.items()):
200+
ip_address = v['ipaddr'][0][1]
201+
neighbour_name = v['bgp_neighs'][ip_address][0]
202+
neighbour_ip = v['bgp_neighs'][ip_address][1]
203+
data.append([ip_intf, v['vrf'], v['ipaddr'][0][1], v['admin'] + "/" + v['oper'], neighbour_name, neighbour_ip])
204+
for ifaddr in v['ipaddr'][1:]:
205+
neighbour_name = v['bgp_neighs'][ifaddr[1]][0]
206+
neighbour_ip = v['bgp_neighs'][ifaddr[1]][1]
207+
data.append(["", "", ifaddr[1], "", neighbour_name, neighbour_ip])
208+
print(tabulate(data, header, tablefmt="simple", stralign='left', missingval=""))
209+
210+
211+
def get_ip_intfs(af, namespace, display):
212+
'''
213+
Get all the ip interface present on the device.
214+
This include ip interfaces on the host as well as ip
215+
interfaces in each network namespace
216+
'''
217+
device = multi_asic_util.MultiAsic(namespace_option=namespace,
218+
display_option=display)
219+
namespace_list = device.get_ns_list_based_on_options()
220+
221+
# for single asic devices there is one namespace DEFAULT_NAMESPACE
222+
# for multi asic devices, there is one network namespace
223+
# for each asic and one on the host
224+
if device.is_multi_asic:
225+
namespace_list.append(constants.DEFAULT_NAMESPACE)
226+
227+
ip_intfs = {}
228+
for namespace in namespace_list:
229+
ip_intfs_in_ns = get_ip_intfs_in_namespace(af, namespace, display)
230+
# multi asic device can have same ip interface in different namespace
231+
# so remove the duplicates
232+
if device.is_multi_asic:
233+
for ip_intf, v in ip_intfs_in_ns.items():
234+
if ip_intf in ip_intfs:
235+
if v['ipaddr'] != ip_intfs[ip_intf]['ipaddr']:
236+
ip_intfs[ip_intf]['ipaddr'] += (v['ipaddr'])
237+
ip_intfs[ip_intf]['bgp_neighs'].update(v['bgp_neighs'])
238+
continue
239+
else:
240+
ip_intfs[ip_intf] = v
241+
else:
242+
ip_intfs.update(ip_intfs_in_ns)
243+
return ip_intfs
244+
245+
246+
def main():
247+
# This script gets the ip interfaces from different linux
248+
# network namespaces. This can be only done from root user.
249+
if os.geteuid() != 0 and os.environ.get("UTILITIES_UNIT_TESTING", "0") != "2":
250+
sys.exit("Root privileges required for this operation")
251+
252+
parser = multi_asic_util.multi_asic_args()
253+
parser.add_argument('-a', '--address_family', type=str, help='ipv4 or ipv6 interfaces', default="ipv4")
254+
args = parser.parse_args()
255+
namespace = args.namespace
256+
display = args.display
257+
258+
if args.address_family == "ipv4":
259+
af = netifaces.AF_INET
260+
elif args.address_family == "ipv6":
261+
af = netifaces.AF_INET6
262+
else:
263+
sys.exit("Invalid argument -a {}".format(args.address_family))
264+
265+
SonicDBConfig.load_sonic_global_db_config()
266+
ip_intfs = get_ip_intfs(af, namespace, display)
267+
display_ip_intfs(ip_intfs)
268+
269+
sys.exit(0)
270+
271+
272+
if __name__ == "__main__":
273+
main()

setup.py

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
'scripts/generate_dump',
9191
'scripts/intfutil',
9292
'scripts/intfstat',
93+
'scripts/ipintutil',
9394
'scripts/lldpshow',
9495
'scripts/log_ssd_health',
9596
'scripts/mellanox_buffer_migrator.py',
@@ -158,6 +159,7 @@
158159
'netaddr==0.8.0',
159160
'netifaces==0.10.7',
160161
'pexpect==4.8.0',
162+
'pyroute2==0.5.14',
161163
'requests==2.25.0',
162164
'sonic-platform-common',
163165
'sonic-py-common',

0 commit comments

Comments
 (0)