Skip to content

Commit cb4d72e

Browse files
committed
Implement BgpInfo
1 parent 69ff620 commit cb4d72e

File tree

12 files changed

+403
-1
lines changed

12 files changed

+403
-1
lines changed

__init__.py

Whitespace-only changes.

src/sonic_ax_impl/lib/__init__.py

Whitespace-only changes.

src/sonic_ax_impl/lib/vtysh_helper.py

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import re
2+
import ipaddress
3+
import socket
4+
5+
HOST = '127.0.0.1'
6+
PORT = 2605
7+
8+
def union_bgp_sessions():
9+
bgpsumm_ipv4 = show_bgp_summary('ip')
10+
sessions_ipv4 = parse_bgp_summary(bgpsumm_ipv4)
11+
12+
bgpsumm_ipv6 = show_bgp_summary('ipv6')
13+
sessions_ipv6 = parse_bgp_summary(bgpsumm_ipv6)
14+
15+
# Note: sessions_ipv4 will overwrite sessions_ipv6 if key is the same
16+
sessions = {}
17+
for ses in sessions_ipv6 + sessions_ipv4:
18+
nei = ses['Neighbor']
19+
sessions[nei] = ses
20+
return sessions
21+
22+
def vtysh_run(command):
23+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
24+
s.connect((HOST, PORT))
25+
26+
cmd = b"zebra\n" + command.encode() + b"\nexit\n"
27+
s.send(cmd)
28+
29+
acc = b""
30+
while True:
31+
data = s.recv(1024)
32+
if not data:
33+
break
34+
acc += data
35+
36+
s.close()
37+
return acc.decode('ascii', 'ignore')
38+
39+
def show_bgp_summary(ipver):
40+
assert(ipver in ['ip', 'ipv6'])
41+
try:
42+
result = vtysh_run('show %s bgp summary' % ipver)
43+
44+
except ConnectionRefusedError as e:
45+
raise RuntimeError('Failed to connect quagga socket') from e
46+
except OSError as e:
47+
raise RuntimeError('Socket error when talking with quagga') from e
48+
return result
49+
50+
def parse_bgp_summary(summ):
51+
ls = summ.splitlines()
52+
bgpinfo = []
53+
54+
## Read until the table header
55+
n = len(ls)
56+
li = 0
57+
while li < n:
58+
l = ls[li]
59+
if l.startswith('Neighbor '): break
60+
if l.startswith('No IPv'): # eg. No IPv6 neighbor is configured
61+
return bgpinfo
62+
if l.endswith('> exit'): # last command in the lines
63+
return bgpinfo
64+
li += 1
65+
66+
## Read and store the table header
67+
if li >= n:
68+
raise ValueError('No table header found')
69+
hl = ls[li]
70+
li += 1
71+
ht = re.split('\s+', hl.rstrip())
72+
hn = len(ht)
73+
74+
## Read rows in the table
75+
while li < n:
76+
l = ls[li]
77+
li += 1
78+
if l == '': break
79+
80+
## Handle line wrap
81+
## ref: bgp_show_summary in https://github.com/Azure/sonic-quagga/blob/debian/0.99.24.1/bgpd/bgp_vty.c
82+
if ' ' not in l:
83+
## Read next line
84+
if li >= n:
85+
raise ValueError('Unexpected line wrap')
86+
l += ls[li]
87+
li += 1
88+
89+
## Note: State/PfxRcd field may be 'Idle (Admin)'
90+
lt = re.split('\s+', l.rstrip(), maxsplit = hn - 1)
91+
if len(lt) != hn:
92+
raise ValueError('Unexpected row in the table')
93+
dic = dict(zip(ht, lt))
94+
bgpinfo.append(dic)
95+
return bgpinfo
96+
97+
STATE_CODE = {
98+
"Idle": 1,
99+
"Idle (Admin)": 1,
100+
"Connect": 2,
101+
"Active": 3,
102+
"OpenSent": 4,
103+
"OpenConfirm": 5,
104+
"Established": 6
105+
};
106+
107+
def bgp_peer_tuple(dic):
108+
nei = dic['Neighbor']
109+
ver = dic['V']
110+
sta = dic['State/PfxRcd']
111+
112+
# prefix '*' appears if the entry is a dynamic neighbor
113+
nei = nei[1:] if nei[0] == '*' else nei
114+
ip = ipaddress.ip_address(nei)
115+
if type(ip) is ipaddress.IPv4Address:
116+
oid_head = (1, 4)
117+
else:
118+
oid_head = (2, 16)
119+
120+
oid_ip = tuple(i for i in ip.packed)
121+
122+
if sta.isdigit():
123+
status = 6
124+
elif sta in STATE_CODE:
125+
status = STATE_CODE[sta]
126+
else:
127+
return None, None
128+
129+
return oid_head + oid_ip, status
130+

src/sonic_ax_impl/main.py

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class SonicMIB(
3232
ieee802_1ab.LLDPRemTable,
3333
dell.force10.SSeriesMIB,
3434
cisco.mgmt.CiscoSystemExtMIB,
35+
cisco.bgp4.CiscoBgp4MIB,
3536
cisco.ciscoPfcExtMIB.cpfcIfTable,
3637
cisco.ciscoPfcExtMIB.cpfcIfPriorityTable,
3738
cisco.ciscoSwitchQosMIB.csqIfQosGroupStatsTable,
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from . import mgmt
1+
from . import mgmt, bgp4
22
from . import ciscoPfcExtMIB
33
from . import ciscoSwitchQosMIB
44
from . import ciscoEntityFruControlMIB
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from bisect import bisect_right
2+
from sonic_ax_impl import mibs
3+
from sonic_ax_impl.lib import vtysh_helper
4+
from ax_interface import MIBMeta, ValueType, MIBUpdater, SubtreeMIBEntry
5+
from ax_interface.mib import MIBEntry
6+
7+
class BgpSessionUpdater(MIBUpdater):
8+
def __init__(self):
9+
super().__init__()
10+
self.session_status_map = {}
11+
self.session_status_list = []
12+
13+
def update_data(self):
14+
self.session_status_map = {}
15+
self.session_status_list = []
16+
17+
try:
18+
sessions = vtysh_helper.union_bgp_sessions()
19+
except RuntimeError as e:
20+
mibs.logger.error("Failed to union bgp sessions: {}.".format(e))
21+
return
22+
23+
for nei, ses in sessions.items():
24+
oid, status = vtysh_helper.bgp_peer_tuple(ses)
25+
if oid is None: continue
26+
self.session_status_list.append(oid)
27+
self.session_status_map[oid] = status
28+
29+
self.session_status_list.sort()
30+
31+
def sessionstatus(self, sub_id):
32+
return self.session_status_map.get(sub_id, None)
33+
34+
def get_next(self, sub_id):
35+
right = bisect_right(self.session_status_list, sub_id)
36+
if right >= len(self.session_status_list):
37+
return None
38+
39+
return self.session_status_list[right]
40+
41+
42+
class CiscoBgp4MIB(metaclass=MIBMeta, prefix='.1.3.6.1.4.1.9.9.187'):
43+
bgpsession_updater = BgpSessionUpdater()
44+
45+
cbgpPeer2State = SubtreeMIBEntry('1.2.5.1.3', bgpsession_updater, ValueType.INTEGER, bgpsession_updater.sessionstatus)

tests/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
import tests.mock_tables.socket
12
import tests.mock_tables.imp

tests/mock_tables/bgpsummary_ipv4.txt

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
Hello, this is Quagga (version 0.99.24.1).
3+
Copyright 1996-2005 Kunihiro Ishiguro, et al.
4+
5+
User Access Verification
6+
7+
"Password:
8+
str-msn2700-05> str-msn2700-05> show ip bgp summary
9+
BGP router identifier 10.1.0.32, local AS number 65100
10+
RIB entries 13025, using 1425 KiB of memory
11+
Peers 16, using 291 KiB of memory
12+
13+
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
14+
10.0.0.57 4 64600 3253 3255 0 0 0 00:48:54 6402
15+
10.0.0.59 4 64600 3252 3258 0 0 0 00:48:56 6402
16+
10.0.0.61 4 64600 3253 3256 0 0 0 00:48:55 6402
17+
10.0.0.63 4 64600 3252 56 0 0 0 00:48:57 6402
18+
10.0.0.65 4 64016 205 9956 0 0 0 13:21:23 Idle
19+
10.0.0.67 4 65200 3210 3246 0 0 0 00:00:11 Idle (Admin)
20+
fc00::72 4 64600 3253 3255 0 0 0 00:48:54 0
21+
fc00::76 4 64600 3253 3255 0 0 0 00:48:54 0
22+
fc00::7a 4 64600 3253 3255 0 0 0 00:48:55 0
23+
fc00::7e 4 64600 3253 54 0 0 0 00:48:55 0
24+
fc00::2 4 65200 6608 6790 0 0 0 13:21:22 Active
25+
fc00::4 4 65200 6608 6790 0 0 0 13:21:22 Connect
26+
fc00::6 4 65200 6608 6790 0 0 0 13:21:22 OpenSent
27+
fc00::8 4 65200 6608 6790 0 0 0 13:21:22 OpenConfirm
28+
fc00::10 4 65200 6608 6790 0 0 0 13:21:22 Clearing
29+
fc00::12 4 65200 6608 6790 0 0 0 13:21:22 Deleted
30+
31+
Total number of neighbors 15
32+
str-msn2700-05>
33+
str-msn2700-05> exit
34+
35+

tests/mock_tables/bgpsummary_ipv6.txt

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
Hello, this is Quagga (version 0.99.24.1).
3+
Copyright 1996-2005 Kunihiro Ishiguro, et al.
4+
5+
6+
User Access Verification
7+
8+
"Password:
9+
str-msn2700-05>
10+
BGP router identifier 10.1.0.32, local AS number 65100
11+
RIB entries 13025, using 1425 KiB of memory
12+
Peers 11, using 291 KiB of memory
13+
14+
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
15+
fc00::72 4 64600 3253 3255 0 0 0 00:48:54 Idle (Admin)
16+
fc00::76 4 64600 3253 3255 0 0 0 00:48:54 0
17+
fc00::7a 4 64600 3253 3255 0 0 0 00:48:55 0
18+
fc00::7e 4 64600 3253 54 0 0 0 00:48:55 0
19+
fc00::2 4 65200 6608 6790 0 0 0 13:21:22 Active
20+
fc00::4 4 65200 6608 6790 0 0 0 13:21:22 Connect
21+
fc00::6 4 65200 6608 6790 0 0 0 13:21:22 OpenSent
22+
fc00::8 4 65200 6608 6790 0 0 0 13:21:22 OpenConfirm
23+
fc00::10 4 65200 6608 6790 0 0 0 13:21:22 Clearing
24+
fc00::12 4 65200 6608 6790 0 0 0 13:21:22 Deleted
25+
2603:10b0:2800:cc1::2e
26+
4 64611 2919 92 0 0 0 01:18:59 0
27+
28+
Total number of neighbors 11
29+
str-msn2700-05>
30+
str-msn2700-05> exit
31+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Hello, this is Quagga (version 0.99.24.1). Copyright 1996-2005 Kunihiro Ishiguro, et al. User Access Verification
2+
"Password:
3+
str-msn2700-03> show ipv6 bgp summary
4+
str-msn2700-03> exit

tests/mock_tables/socket.py

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import os
2+
from collections import namedtuple
3+
import unittest
4+
from unittest import TestCase, mock
5+
from unittest.mock import patch, mock_open, MagicMock
6+
7+
INPUT_DIR = os.path.dirname(os.path.abspath(__file__))
8+
9+
import socket
10+
11+
# Backup original class
12+
_socket_class = socket.socket
13+
14+
# Monkey patch
15+
class MockSocket(_socket_class):
16+
17+
def __init__(self, *args, **kwargs):
18+
super(MockSocket, self).__init__(*args, **kwargs)
19+
self._string_sent = b''
20+
21+
def connect(self, *args, **kwargs):
22+
pass
23+
24+
def send(self, *args, **kwargs):
25+
string = args[0]
26+
self._string_sent = string
27+
pass
28+
29+
def recv(self, *args, **kwargs):
30+
if b'show ip bgp summary' in self._string_sent:
31+
filename = INPUT_DIR + '/bgpsummary_ipv4.txt'
32+
elif b'show ipv6 bgp summary' in self._string_sent:
33+
filename = INPUT_DIR + '/bgpsummary_ipv6.txt'
34+
else:
35+
return None
36+
37+
self._string_sent = b''
38+
ret = namedtuple('ret', ['returncode', 'stdout'])
39+
ret.returncode = 0
40+
with open(filename, 'rb') as f:
41+
ret = f.read()
42+
return ret
43+
44+
# Replace the function with mocked one
45+
socket.socket = MockSocket

0 commit comments

Comments
 (0)