|
| 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 | + |
0 commit comments