Skip to content

Commit 84aa0b3

Browse files
qiluo-msftssithaia-ebay
authored andcommitted
Implement ipNetToMediaPhysAddress (ARP table) (sonic-net#19)
* Implement ipNetToMediaPhysAddress (ARP table) * Fix: use latin-1 as OctetString encoding * Add test data, mock function * Rename ip2tuple_v4 function * Refine list comprehension * (comment)
1 parent babb431 commit 84aa0b3

11 files changed

Lines changed: 287 additions & 10 deletions

File tree

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
dependencies = [
44
'sswsdk>=2.0.1',
55
'psutil>=4.0',
6+
'python_arptable>=0.0.1',
67
]
78

89
test_deps = [

src/ax_interface/encodings.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,14 @@ def size(self):
143143
return 4 + self.length + util.pad4(self.length)
144144

145145
def __str__(self):
146-
return self.string.decode('ascii')
146+
# Note: ascii encoding (0-0x7F) is not enough to decode the internal bytes (self.string)
147+
# “latin-1” encoding maps byte values directly to the first 256 Unicode code points
148+
return self.string.decode('latin-1')
147149

148150
@classmethod
149151
def from_string(cls, string):
150152
length = len(string)
151-
_string = bytes(string, 'ascii') if type(string) is str else string
153+
_string = bytes(string, 'latin-1') if type(string) is str else string
152154
return cls(length, _string, util.pad4bytes(len(_string)))
153155

154156
def to_bytes(self, endianness):

src/ax_interface/util.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,10 @@ def mac_decimals(mac):
9898
"""
9999
return tuple(int(h, 16) for h in mac.split(":"))
100100

101+
def ip2tuple_v4(ip):
102+
"""
103+
>>> ip2tuple_v4("192.168.1.253")
104+
(192, 168, 1, 253)
105+
"""
106+
return tuple(int(bs) for bs in str(ip).split('.'))
107+

src/sonic_ax_impl/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
class SonicMIB(
2525
rfc1213.InterfacesMIB,
26+
rfc1213.IpMib,
2627
rfc2863.InterfaceMIBObjects,
2728
rfc4363.QBridgeMIBObjects,
2829
rfc4292.IpCidrRouteTable,

src/sonic_ax_impl/mibs/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@ def get_index(if_name):
3434
OIDs are 1-based, interfaces are 0-based, return the 1-based index
3535
Ethernet N = N + 1
3636
"""
37-
match = re.match(SONIC_ETHERNET_RE_PATTERN, if_name.decode())
37+
return get_index_from_str(if_name.decode())
38+
39+
def get_index_from_str(if_name):
40+
"""
41+
OIDs are 1-based, interfaces are 0-based, return the 1-based index
42+
Ethernet N = N + 1
43+
"""
44+
match = re.match(SONIC_ETHERNET_RE_PATTERN, if_name)
3845
if match:
3946
n = match.group(1)
4047
return int(n) + 1

src/sonic_ax_impl/mibs/ietf/rfc1213.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import python_arptable
12
from enum import unique, Enum
3+
from bisect import bisect_right
24

35
from sonic_ax_impl import mibs
4-
from ax_interface import MIBMeta, ValueType, MIBUpdater, MIBEntry, ContextualMIBEntry
6+
from ax_interface import MIBMeta, ValueType, MIBUpdater, MIBEntry, ContextualMIBEntry, SubtreeMIBEntry
57
from ax_interface.encodings import ObjectIdentifier
8+
from ax_interface.util import mac_decimals, ip2tuple_v4
69

710

811
@unique
@@ -41,6 +44,51 @@ class DbTables(int, Enum):
4144
# ifOutQLen ::= { ifEntry 21 }
4245
SAI_PORT_STAT_IF_OUT_QLEN = 21
4346

47+
class ArpUpdater(MIBUpdater):
48+
def __init__(self):
49+
super().__init__()
50+
self.arp_dest_map = {}
51+
self.arp_dest_list = []
52+
# call our update method once to "seed" data before the "Agent" starts accepting requests.
53+
self.update_data()
54+
55+
def update_data(self):
56+
self.arp_dest_map = {}
57+
self.arp_dest_list = []
58+
for entry in python_arptable.get_arp_table():
59+
dev = entry['Device']
60+
mac = entry['HW address']
61+
ip = entry['IP address']
62+
63+
if_index = mibs.get_index_from_str(dev)
64+
if if_index is None: continue
65+
66+
mactuple = mac_decimals(mac)
67+
machex = ''.join(chr(b) for b in mactuple)
68+
# if MAC is all zero
69+
#if not any(mac): continue
70+
71+
iptuple = ip2tuple_v4(ip)
72+
73+
subid = (if_index,) + iptuple
74+
self.arp_dest_map[subid] = machex
75+
self.arp_dest_list.append(subid)
76+
self.arp_dest_list.sort()
77+
78+
def arp_dest(self, sub_id):
79+
return self.arp_dest_map.get(sub_id, None)
80+
81+
def get_next(self, sub_id):
82+
right = bisect_right(self.arp_dest_list, sub_id)
83+
if right >= len(self.arp_dest_list):
84+
return None
85+
return self.arp_dest_list[right]
86+
87+
class IpMib(metaclass=MIBMeta, prefix='.1.3.6.1.2.1.4'):
88+
arp_updater = ArpUpdater()
89+
90+
ipNetToMediaPhysAddress = \
91+
SubtreeMIBEntry('22.1.2', arp_updater, ValueType.OCTET_STRING, arp_updater.arp_dest)
4492

4593
class InterfacesUpdater(MIBUpdater):
4694
def __init__(self):

src/sonic_ax_impl/mibs/ietf/rfc4292.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@
55
from sonic_ax_impl import mibs
66
from ax_interface import MIBMeta, ValueType, MIBUpdater, ContextualMIBEntry, SubtreeMIBEntry
77
from ax_interface.encodings import OctetString
8-
from ax_interface.util import mac_decimals
8+
from ax_interface.util import mac_decimals, ip2tuple_v4
99
from bisect import bisect_right
1010

11-
def ip2tuple(ip):
12-
return tuple(int(bs) for bs in str(ip).split('.'))
13-
1411
class RouteUpdater(MIBUpdater):
1512
def __init__(self):
1613
super().__init__()
@@ -37,7 +34,7 @@ def update_data(self):
3734
ent = self.db_conn.get_all(mibs.APPL_DB, routestr, blocking=True)
3835
nexthops = ent[b"nexthop"].decode()
3936
for nh in nexthops.split(','):
40-
sub_id = ip2tuple(ipn.network_address) + ip2tuple(ipn.netmask) + (self.tos,) + ip2tuple(nh)
37+
sub_id = ip2tuple_v4(ipn.network_address) + ip2tuple_v4(ipn.netmask) + (self.tos,) + ip2tuple_v4(nh)
4138
self.route_dest_list.append(sub_id)
4239
self.route_dest_map[sub_id] = ipn.network_address.packed
4340

src/sonic_ax_impl/mibs/ietf/rfc4363.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def update_data(self):
4848
vlanmac = fdb_vlanmac(fdb)
4949
self.vlanmac_ifindex_map[vlanmac] = mibs.get_index(self.if_id_map[port_oid])
5050
self.vlanmac_ifindex_list.append(vlanmac)
51-
self.vlanmac_ifindex_list.sort()
51+
self.vlanmac_ifindex_list.sort()
5252

5353

5454
def fdb_ifindex(self, sub_id):

tests/mock_tables/arp.txt

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
IP address HW type Flags HW address Mask Device
2+
10.3.146.86 0x1 0x2 00:a0:a5:75:35:8d * eth0
3+
10.0.0.35 0x1 0x2 52:54:00:a5:70:47 * Ethernet68
4+
10.0.0.19 0x1 0x2 52:54:00:04:52:5d * Ethernet36
5+
10.0.0.57 0x1 0x2 52:54:00:b4:59:59 * Ethernet112
6+
10.3.146.1 0x1 0x2 00:00:5e:00:01:64 * eth0
7+
10.3.146.244 0x1 0x2 e4:d3:f1:51:3c:80 * eth0
8+
10.3.147.97 0x1 0x2 3c:94:d5:69:b5:02 * eth0
9+
10.0.0.25 0x1 0x2 52:54:00:55:2d:fe * Ethernet48
10+
10.3.146.75 0x1 0x2 00:a0:a5:76:17:70 * eth0
11+
10.3.146.131 0x1 0x2 00:15:c7:21:f7:40 * eth0
12+
10.0.0.21 0x1 0x2 52:54:00:d0:a0:8c * Ethernet40
13+
10.3.147.223 0x1 0x2 00:17:0f:ac:c4:40 * eth0
14+
10.3.146.78 0x1 0x2 3c:94:d5:68:9d:82 * eth0
15+
10.3.146.184 0x1 0x2 e4:d3:f1:51:38:60 * eth0
16+
10.3.146.170 0x1 0x2 30:e4:db:a4:c9:3f * eth0
17+
10.0.0.33 0x1 0x2 7c:fe:90:5e:6b:a6 * Ethernet64
18+
10.3.146.134 0x1 0x2 00:23:04:18:8e:c0 * eth0
19+
10.3.146.15 0x1 0x2 00:1e:f7:f7:0a:80 * eth0
20+
10.3.146.85 0x1 0x2 00:a0:a5:80:38:22 * eth0
21+
10.0.0.45 0x1 0x2 52:54:00:d0:23:2b * Ethernet88
22+
10.3.147.40 0x1 0x2 3c:94:d5:68:4a:82 * eth0
23+
10.0.0.59 0x1 0x2 52:54:00:ae:c8:01 * Ethernet116
24+
10.3.146.74 0x1 0x2 00:a0:a5:7a:39:ea * eth0
25+
10.0.0.5 0x1 0x2 52:54:00:fc:50:3c * Ethernet8
26+
10.3.146.130 0x1 0x2 00:15:c7:21:dd:00 * eth0
27+
10.3.146.81 0x1 0x2 5c:5e:ab:de:70:ff * eth0
28+
10.0.0.55 0x1 0x2 52:54:00:1b:d6:95 * Ethernet108
29+
10.0.0.11 0x1 0x0 00:00:00:00:00:00 * Ethernet20
30+
10.3.146.14 0x1 0x2 00:1e:f7:f7:14:40 * eth0
31+
10.0.0.61 0x1 0x0 00:00:00:00:00:00 * Ethernet120
32+
10.0.0.3 0x1 0x0 00:00:00:00:00:00 * Ethernet4
33+
10.3.146.70 0x1 0x2 00:a0:a5:77:ef:f1 * eth0
34+
10.3.146.162 0x1 0x2 58:8d:09:8c:3c:bf * eth0
35+
10.3.146.172 0x1 0x2 a4:93:4c:da:f7:bf * eth0
36+
10.0.0.29 0x1 0x0 00:00:00:00:00:00 * Ethernet56
37+
10.3.146.95 0x1 0x2 00:a0:a5:85:f8:98 * eth0
38+
10.3.146.187 0x1 0x2 6c:20:56:cb:20:40 * eth0
39+
10.0.0.31 0x1 0x0 00:00:00:00:00:00 * Ethernet60
40+
10.3.146.10 0x1 0x2 4c:76:25:eb:52:42 * eth0
41+
10.3.147.225 0x1 0x2 00:22:91:86:10:00 * eth0
42+
10.0.0.53 0x1 0x2 52:54:00:c3:a1:2d * Ethernet104
43+
10.0.0.41 0x1 0x2 52:54:00:08:de:c3 * Ethernet80
44+
10.3.146.190 0x1 0x2 e4:d3:f1:51:33:20 * eth0
45+
10.0.0.15 0x1 0x2 52:54:00:c6:31:42 * Ethernet28
46+
10.0.0.43 0x1 0x2 52:54:00:44:73:a4 * Ethernet84
47+
10.3.146.3 0x1 0x2 f4:b5:2f:72:bf:f0 * eth0
48+
10.3.147.250 0x1 0x2 4c:76:25:f4:c6:02 * eth0
49+
10.0.0.49 0x1 0x2 52:54:00:74:c5:38 * Ethernet96
50+
10.3.146.91 0x1 0x2 54:e0:32:cf:6f:ff * eth0
51+
10.0.0.63 0x1 0x0 00:00:00:00:00:00 * Ethernet124
52+
10.0.0.27 0x1 0x2 52:54:00:1c:90:c4 * Ethernet52
53+
10.0.0.9 0x1 0x2 52:54:00:71:ae:0e * Ethernet16
54+
10.3.146.157 0x1 0x2 00:1e:be:38:44:ff * eth0
55+
10.3.147.239 0x1 0x2 ec:f4:bb:fe:80:a1 * eth0
56+
10.0.0.7 0x1 0x2 52:54:00:d1:75:b4 * Ethernet12
57+
10.3.146.72 0x1 0x2 00:a0:a5:80:26:07 * eth0
58+
10.3.146.164 0x1 0x2 00:15:c6:df:03:7f * eth0
59+
10.3.146.150 0x1 0x2 00:05:9b:7e:61:00 * eth0
60+
10.3.147.224 0x1 0x2 00:22:91:85:88:00 * eth0
61+
10.3.146.87 0x1 0x2 00:a0:a5:80:2c:7a * eth0
62+
10.0.0.37 0x1 0x2 52:54:00:30:a7:95 * Ethernet72
63+
10.3.146.16 0x1 0x2 ec:bd:1d:f2:a6:00 * eth0
64+
10.0.0.51 0x1 0x2 52:54:00:36:5b:05 * Ethernet100
65+
10.3.146.2 0x1 0x2 f4:b5:2f:79:b3:f0 * eth0
66+
10.0.0.1 0x1 0x2 7c:fe:90:5e:6b:a6 * Ethernet0
67+
10.0.0.13 0x1 0x2 52:54:00:88:3c:5e * Ethernet24
68+
10.3.146.90 0x1 0x2 54:e0:32:cf:76:ff * eth0
69+
10.3.146.182 0x1 0x2 6c:20:56:ee:c0:80 * eth0
70+
10.0.0.47 0x1 0x2 52:54:00:77:e3:91 * Ethernet92
71+
10.3.146.156 0x1 0x2 00:18:73:b1:7d:bf * eth0
72+
10.0.0.23 0x1 0x2 52:54:00:df:ec:bf * Ethernet44
73+
10.3.146.83 0x1 0x2 00:a0:a5:87:1d:28 * eth0
74+
10.3.146.93 0x1 0x2 54:e0:32:cf:77:ff * eth0
75+
10.3.146.79 0x1 0x2 3c:94:d5:60:40:c2 * eth0
76+
10.3.146.171 0x1 0x2 c8:9c:1d:ee:7f:7f * eth0
77+
10.0.0.39 0x1 0x2 52:54:00:66:a7:29 * Ethernet76
78+
10.0.0.17 0x1 0x2 52:54:00:e7:53:c2 * Ethernet32
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import os
2+
import csv
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 python_arptable
10+
11+
# Backup original function
12+
_get_arp_table = getattr(python_arptable, 'get_arp_table')
13+
14+
# Monkey patch
15+
def get_arp_table():
16+
with open(INPUT_DIR + '/arp.txt') as farp:
17+
file_content = mock_open(read_data = farp.read())
18+
file_content.return_value.__iter__ = lambda self : iter(self.readline, '')
19+
# file_content = MagicMock(name = 'open', spec = open)
20+
# file_content.return_value = iter(farp.readlines())
21+
with patch('builtins.open', file_content):
22+
return _get_arp_table()
23+
24+
# Replace the function with mocked one
25+
python_arptable.get_arp_table = get_arp_table

0 commit comments

Comments
 (0)