Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ipNetToMediaPhysAddress (ARP table) #19

Merged
merged 6 commits into from
Mar 28, 2017
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
dependencies = [
'sswsdk>=2.0.1',
'psutil>=4.0',
'python_arptable>=0.0.1',
]

test_deps = [
Expand Down
6 changes: 4 additions & 2 deletions src/ax_interface/encodings.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,14 @@ def size(self):
return 4 + self.length + util.pad4(self.length)

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

@classmethod
def from_string(cls, string):
length = len(string)
_string = bytes(string, 'ascii') if type(string) is str else string
_string = bytes(string, 'latin-1') if type(string) is str else string
return cls(length, _string, util.pad4bytes(len(_string)))

def to_bytes(self, endianness):
Expand Down
7 changes: 7 additions & 0 deletions src/ax_interface/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,10 @@ def mac_decimals(mac):
"""
return tuple(int(h, 16) for h in mac.split(":"))

def ip2tuple_v4(ip):
"""
>>> ip2tuple_v4("192.168.1.253")
(192, 168, 1, 253)
"""
return tuple(int(bs) for bs in str(ip).split('.'))

1 change: 1 addition & 0 deletions src/sonic_ax_impl/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

class SonicMIB(
rfc1213.InterfacesMIB,
rfc1213.IpMib,
rfc2863.InterfaceMIBObjects,
rfc4363.QBridgeMIBObjects,
rfc4292.IpCidrRouteTable,
Expand Down
9 changes: 8 additions & 1 deletion src/sonic_ax_impl/mibs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@ def get_index(if_name):
OIDs are 1-based, interfaces are 0-based, return the 1-based index
Ethernet N = N + 1
"""
match = re.match(SONIC_ETHERNET_RE_PATTERN, if_name.decode())
return get_index_from_str(if_name.decode())

def get_index_from_str(if_name):
"""
OIDs are 1-based, interfaces are 0-based, return the 1-based index
Ethernet N = N + 1
"""
match = re.match(SONIC_ETHERNET_RE_PATTERN, if_name)
Copy link
Contributor

@pavel-shirshov pavel-shirshov Mar 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably it's better to compile this regexp in init?
Also is it possible to extract this value from string using something like
if_name.replace('Ethernet', '') ? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep it for readability and optimize if we find it on critical path. re library has the opportunity to hide the optimization without users changing code.


In reply to: 108313222 [](ancestors = 108313222)

if match:
n = match.group(1)
return int(n) + 1
Expand Down
52 changes: 51 additions & 1 deletion src/sonic_ax_impl/mibs/ietf/rfc1213.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import python_arptable
from enum import unique, Enum
from bisect import bisect_right

from sonic_ax_impl import mibs
from ax_interface import MIBMeta, ValueType, MIBUpdater, MIBEntry, ContextualMIBEntry
from ax_interface import MIBMeta, ValueType, MIBUpdater, MIBEntry, ContextualMIBEntry, SubtreeMIBEntry
from ax_interface.encodings import ObjectIdentifier
from ax_interface.util import mac_decimals, ip2tuple_v4


@unique
Expand Down Expand Up @@ -41,6 +44,53 @@ class DbTables(int, Enum):
# ifOutQLen ::= { ifEntry 21 }
SAI_PORT_STAT_IF_OUT_QLEN = 21

class ArpUpdater(MIBUpdater):
def __init__(self):
super().__init__()
self.arp_dest_map = {}
self.arp_dest_list = []
# call our update method once to "seed" data before the "Agent" starts accepting requests.
self.update_data()

def update_data(self):
self.arp_dest_map = {}
self.arp_dest_list = []
for entry in python_arptable.get_arp_table():
dev = entry['Device']
mac = entry['HW address']
ip = entry['IP address']

if_index = mibs.get_index_from_str(dev)
if if_index is None: continue

mactuple = mac_decimals(mac)
machex = ''.join(chr(b) for b in mactuple)
# if MAC is all zero
#if not any(mac): continue
Copy link
Contributor

@pavel-shirshov pavel-shirshov Mar 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove this line? What to do if the mac is 0? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The correct behavior of incomplete ARP entry (an IP without MAC) is not clear in the protocol. ref: http://cric.grenoble.cnrs.fr/Administrateurs/Outils/MIBS/?oid=1.3.6.1.2.1.4.22.1.2
Just keep the code here for further refinement, if needed.


In reply to: 108499363 [](ancestors = 108499363)


iptuple = ip2tuple_v4(ip)

subid = (if_index,) + iptuple
self.arp_dest_map[subid] = machex
self.arp_dest_list.append(subid)

# print(subid, dev, mac, ip)
Copy link
Contributor

@pavel-shirshov pavel-shirshov Mar 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove it? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed


In reply to: 108499446 [](ancestors = 108499446)

self.arp_dest_list.sort()

def arp_dest(self, sub_id):
return self.arp_dest_map.get(sub_id, None)

def get_next(self, sub_id):
right = bisect_right(self.arp_dest_list, sub_id)
if right >= len(self.arp_dest_list):
return None
return self.arp_dest_list[right]

class IpMib(metaclass=MIBMeta, prefix='.1.3.6.1.2.1.4'):
arp_updater = ArpUpdater()

ipNetToMediaPhysAddress = \
SubtreeMIBEntry('22.1.2', arp_updater, ValueType.OCTET_STRING, arp_updater.arp_dest)

class InterfacesUpdater(MIBUpdater):
def __init__(self):
Expand Down
7 changes: 2 additions & 5 deletions src/sonic_ax_impl/mibs/ietf/rfc4292.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@
from sonic_ax_impl import mibs
from ax_interface import MIBMeta, ValueType, MIBUpdater, ContextualMIBEntry, SubtreeMIBEntry
from ax_interface.encodings import OctetString
from ax_interface.util import mac_decimals
from ax_interface.util import mac_decimals, ip2tuple_v4
from bisect import bisect_right

def ip2tuple(ip):
return tuple(int(bs) for bs in str(ip).split('.'))

class RouteUpdater(MIBUpdater):
def __init__(self):
super().__init__()
Expand All @@ -37,7 +34,7 @@ def update_data(self):
ent = self.db_conn.get_all(mibs.APPL_DB, routestr, blocking=True)
nexthops = ent[b"nexthop"].decode()
for nh in nexthops.split(','):
sub_id = ip2tuple(ipn.network_address) + ip2tuple(ipn.netmask) + (self.tos,) + ip2tuple(nh)
sub_id = ip2tuple_v4(ipn.network_address) + ip2tuple_v4(ipn.netmask) + (self.tos,) + ip2tuple_v4(nh)
self.route_dest_list.append(sub_id)
self.route_dest_map[sub_id] = ipn.network_address.packed

Expand Down
2 changes: 1 addition & 1 deletion src/sonic_ax_impl/mibs/ietf/rfc4363.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def update_data(self):
vlanmac = fdb_vlanmac(fdb)
self.vlanmac_ifindex_map[vlanmac] = mibs.get_index(self.if_id_map[port_oid])
self.vlanmac_ifindex_list.append(vlanmac)
self.vlanmac_ifindex_list.sort()
self.vlanmac_ifindex_list.sort()


def fdb_ifindex(self, sub_id):
Expand Down
78 changes: 78 additions & 0 deletions tests/mock_tables/arp.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
IP address HW type Flags HW address Mask Device
10.3.146.86 0x1 0x2 00:a0:a5:75:35:8d * eth0
10.0.0.35 0x1 0x2 52:54:00:a5:70:47 * Ethernet68
10.0.0.19 0x1 0x2 52:54:00:04:52:5d * Ethernet36
10.0.0.57 0x1 0x2 52:54:00:b4:59:59 * Ethernet112
10.3.146.1 0x1 0x2 00:00:5e:00:01:64 * eth0
10.3.146.244 0x1 0x2 e4:d3:f1:51:3c:80 * eth0
10.3.147.97 0x1 0x2 3c:94:d5:69:b5:02 * eth0
10.0.0.25 0x1 0x2 52:54:00:55:2d:fe * Ethernet48
10.3.146.75 0x1 0x2 00:a0:a5:76:17:70 * eth0
10.3.146.131 0x1 0x2 00:15:c7:21:f7:40 * eth0
10.0.0.21 0x1 0x2 52:54:00:d0:a0:8c * Ethernet40
10.3.147.223 0x1 0x2 00:17:0f:ac:c4:40 * eth0
10.3.146.78 0x1 0x2 3c:94:d5:68:9d:82 * eth0
10.3.146.184 0x1 0x2 e4:d3:f1:51:38:60 * eth0
10.3.146.170 0x1 0x2 30:e4:db:a4:c9:3f * eth0
10.0.0.33 0x1 0x2 7c:fe:90:5e:6b:a6 * Ethernet64
10.3.146.134 0x1 0x2 00:23:04:18:8e:c0 * eth0
10.3.146.15 0x1 0x2 00:1e:f7:f7:0a:80 * eth0
10.3.146.85 0x1 0x2 00:a0:a5:80:38:22 * eth0
10.0.0.45 0x1 0x2 52:54:00:d0:23:2b * Ethernet88
10.3.147.40 0x1 0x2 3c:94:d5:68:4a:82 * eth0
10.0.0.59 0x1 0x2 52:54:00:ae:c8:01 * Ethernet116
10.3.146.74 0x1 0x2 00:a0:a5:7a:39:ea * eth0
10.0.0.5 0x1 0x2 52:54:00:fc:50:3c * Ethernet8
10.3.146.130 0x1 0x2 00:15:c7:21:dd:00 * eth0
10.3.146.81 0x1 0x2 5c:5e:ab:de:70:ff * eth0
10.0.0.55 0x1 0x2 52:54:00:1b:d6:95 * Ethernet108
10.0.0.11 0x1 0x0 00:00:00:00:00:00 * Ethernet20
10.3.146.14 0x1 0x2 00:1e:f7:f7:14:40 * eth0
10.0.0.61 0x1 0x0 00:00:00:00:00:00 * Ethernet120
10.0.0.3 0x1 0x0 00:00:00:00:00:00 * Ethernet4
10.3.146.70 0x1 0x2 00:a0:a5:77:ef:f1 * eth0
10.3.146.162 0x1 0x2 58:8d:09:8c:3c:bf * eth0
10.3.146.172 0x1 0x2 a4:93:4c:da:f7:bf * eth0
10.0.0.29 0x1 0x0 00:00:00:00:00:00 * Ethernet56
10.3.146.95 0x1 0x2 00:a0:a5:85:f8:98 * eth0
10.3.146.187 0x1 0x2 6c:20:56:cb:20:40 * eth0
10.0.0.31 0x1 0x0 00:00:00:00:00:00 * Ethernet60
10.3.146.10 0x1 0x2 4c:76:25:eb:52:42 * eth0
10.3.147.225 0x1 0x2 00:22:91:86:10:00 * eth0
10.0.0.53 0x1 0x2 52:54:00:c3:a1:2d * Ethernet104
10.0.0.41 0x1 0x2 52:54:00:08:de:c3 * Ethernet80
10.3.146.190 0x1 0x2 e4:d3:f1:51:33:20 * eth0
10.0.0.15 0x1 0x2 52:54:00:c6:31:42 * Ethernet28
10.0.0.43 0x1 0x2 52:54:00:44:73:a4 * Ethernet84
10.3.146.3 0x1 0x2 f4:b5:2f:72:bf:f0 * eth0
10.3.147.250 0x1 0x2 4c:76:25:f4:c6:02 * eth0
10.0.0.49 0x1 0x2 52:54:00:74:c5:38 * Ethernet96
10.3.146.91 0x1 0x2 54:e0:32:cf:6f:ff * eth0
10.0.0.63 0x1 0x0 00:00:00:00:00:00 * Ethernet124
10.0.0.27 0x1 0x2 52:54:00:1c:90:c4 * Ethernet52
10.0.0.9 0x1 0x2 52:54:00:71:ae:0e * Ethernet16
10.3.146.157 0x1 0x2 00:1e:be:38:44:ff * eth0
10.3.147.239 0x1 0x2 ec:f4:bb:fe:80:a1 * eth0
10.0.0.7 0x1 0x2 52:54:00:d1:75:b4 * Ethernet12
10.3.146.72 0x1 0x2 00:a0:a5:80:26:07 * eth0
10.3.146.164 0x1 0x2 00:15:c6:df:03:7f * eth0
10.3.146.150 0x1 0x2 00:05:9b:7e:61:00 * eth0
10.3.147.224 0x1 0x2 00:22:91:85:88:00 * eth0
10.3.146.87 0x1 0x2 00:a0:a5:80:2c:7a * eth0
10.0.0.37 0x1 0x2 52:54:00:30:a7:95 * Ethernet72
10.3.146.16 0x1 0x2 ec:bd:1d:f2:a6:00 * eth0
10.0.0.51 0x1 0x2 52:54:00:36:5b:05 * Ethernet100
10.3.146.2 0x1 0x2 f4:b5:2f:79:b3:f0 * eth0
10.0.0.1 0x1 0x2 7c:fe:90:5e:6b:a6 * Ethernet0
10.0.0.13 0x1 0x2 52:54:00:88:3c:5e * Ethernet24
10.3.146.90 0x1 0x2 54:e0:32:cf:76:ff * eth0
10.3.146.182 0x1 0x2 6c:20:56:ee:c0:80 * eth0
10.0.0.47 0x1 0x2 52:54:00:77:e3:91 * Ethernet92
10.3.146.156 0x1 0x2 00:18:73:b1:7d:bf * eth0
10.0.0.23 0x1 0x2 52:54:00:df:ec:bf * Ethernet44
10.3.146.83 0x1 0x2 00:a0:a5:87:1d:28 * eth0
10.3.146.93 0x1 0x2 54:e0:32:cf:77:ff * eth0
10.3.146.79 0x1 0x2 3c:94:d5:60:40:c2 * eth0
10.3.146.171 0x1 0x2 c8:9c:1d:ee:7f:7f * eth0
10.0.0.39 0x1 0x2 52:54:00:66:a7:29 * Ethernet76
10.0.0.17 0x1 0x2 52:54:00:e7:53:c2 * Ethernet32
25 changes: 25 additions & 0 deletions tests/mock_tables/python_arptable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import os
import csv
import unittest
from unittest import TestCase, mock
from unittest.mock import patch, mock_open, MagicMock

INPUT_DIR = os.path.dirname(os.path.abspath(__file__))

import python_arptable

# Backup original function
_get_arp_table = getattr(python_arptable, 'get_arp_table')

# Monkey patch
def get_arp_table():
with open(INPUT_DIR + '/arp.txt') as farp:
file_content = mock_open(read_data = farp.read())
file_content.return_value.__iter__ = lambda self : iter(self.readline, '')
# file_content = MagicMock(name = 'open', spec = open)
# file_content.return_value = iter(farp.readlines())
with patch('builtins.open', file_content):
return _get_arp_table()

# Replace the function with mocked one
python_arptable.get_arp_table = get_arp_table
111 changes: 111 additions & 0 deletions tests/test_arp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import os
import sys
import mock

modules_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(modules_path, 'src'))

from unittest import TestCase
from unittest.mock import patch, mock_open

# noinspection PyUnresolvedReferences
import tests.mock_tables.dbconnector
import tests.mock_tables.python_arptable
from ax_interface.mib import MIBTable
from ax_interface.pdu import PDUHeader
from ax_interface.pdu_implementations import GetPDU, GetNextPDU
from ax_interface import ValueType
from ax_interface.encodings import ObjectIdentifier
from ax_interface.constants import PduTypes
from sonic_ax_impl.mibs.ietf import rfc4363
from sonic_ax_impl.main import SonicMIB

class TestSonicMIB(TestCase):
@classmethod
def setUpClass(cls):
cls.lut = MIBTable(SonicMIB)

def test_getpdu(self):
oid = ObjectIdentifier(20, 0, 0, 0, (1, 3, 6, 1, 2, 1, 4, 22, 1, 2, 37, 10, 0, 0, 19))
get_pdu = GetPDU(
header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0),
oids=[oid]
)

encoded = get_pdu.encode()
response = get_pdu.make_response(self.lut)
print(response)

value0 = response.values[0]
self.assertEqual(value0.type_, ValueType.OCTET_STRING)
self.assertEqual(str(value0.name), str(oid))
self.assertEqual(value0.data.string, b'\x52\x54\x00\x04\x52\x5d')

def test_getnextpdu(self):
get_pdu = GetNextPDU(
header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0),
oids=(
ObjectIdentifier(20, 0, 0, 0, (1, 3, 6, 1, 2, 1, 4, 22, 1, 2, 37, 10, 0, 0, 19)),
)
)

encoded = get_pdu.encode()
response = get_pdu.make_response(self.lut)
print(response)

n = len(response.values)
value0 = response.values[0]
self.assertEqual(value0.type_, ValueType.OCTET_STRING)
self.assertEqual(value0.data.string, b'\x52\x54\x00\xd0\xa0\x8c')

def test_getnextpdu_exactmatch(self):
# oid.include = 1
oid = ObjectIdentifier(20, 0, 1, 0, (1, 3, 6, 1, 2, 1, 4, 22, 1, 2, 37, 10, 0, 0, 19))
get_pdu = GetNextPDU(
header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0),
oids=[oid]
)

encoded = get_pdu.encode()
response = get_pdu.make_response(self.lut)
print(response)

n = len(response.values)
value0 = response.values[0]
self.assertEqual(value0.type_, ValueType.OCTET_STRING)
print("test_getnextpdu_exactmatch: ", str(oid))
self.assertEqual(str(value0.name), str(oid))
self.assertEqual(value0.data.string, b'\x52\x54\x00\x04\x52\x5d')

def test_getpdu_noinstance(self):
get_pdu = GetPDU(
header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0),
oids=(
ObjectIdentifier(20, 0, 0, 0, (1, 3, 6, 1, 2, 1, 4, 22, 1, 2, 39)),
)
)

encoded = get_pdu.encode()
response = get_pdu.make_response(self.lut)
print(response)

n = len(response.values)
value0 = response.values[0]
self.assertEqual(value0.type_, ValueType.NO_SUCH_INSTANCE)

def test_getnextpdu_empty(self):
get_pdu = GetNextPDU(
header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0),
oids=(
ObjectIdentifier(20, 0, 0, 0, (1, 3, 6, 1, 2, 1, 4, 22, 1, 3)),
)
)

encoded = get_pdu.encode()
response = get_pdu.make_response(self.lut)
print(response)

n = len(response.values)
value0 = response.values[0]
self.assertEqual(value0.type_, ValueType.END_OF_MIB_VIEW)