Skip to content

Commit

Permalink
Add tooling to capture route table as a list of RouteEntry objects (A…
Browse files Browse the repository at this point in the history
…zure#1221)

* Add method to capture route table as a list of route entry objects

* Change mocking strategy for route table

* Make get_route_table() non-static. All osutil callables must be so.

* Refactor reading the route table file itself; str, not ustr.
  • Loading branch information
jasonzio authored Jun 21, 2018
1 parent 279be6c commit c54af5d
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 0 deletions.
60 changes: 60 additions & 0 deletions azurelinuxagent/common/osutil/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import azurelinuxagent.common.logger as logger
import azurelinuxagent.common.conf as conf
import azurelinuxagent.common.utils.fileutil as fileutil
import azurelinuxagent.common.utils.networkutil as networkutil
import azurelinuxagent.common.utils.shellutil as shellutil
import azurelinuxagent.common.utils.textutil as textutil

Expand Down Expand Up @@ -761,6 +762,65 @@ def get_first_if(self):

return '', ''

@staticmethod
def _build_route_list(proc_net_route):
"""
Construct a list of network route entries
:param list(str) proc_net_route: List of lines in /proc/net/route, containing at least one route
:return: List of network route objects
:rtype: list(networkutil.RouteEntry)
"""
idx = 0
column_index = {}
header_line = proc_net_route[0]
for header in filter(lambda h: len(h) > 0, header_line.split("\t")):
column_index[header.strip()] = idx
idx += 1
try:
idx_iface = column_index["Iface"]
idx_dest = column_index["Destination"]
idx_gw = column_index["Gateway"]
idx_flags = column_index["Flags"]
idx_metric = column_index["Metric"]
idx_mask = column_index["Mask"]
except KeyError:
msg = "/proc/net/route is missing key information; headers are [{0}]".format(header_line)
logger.periodic(logger.EVERY_HALF_DAY, msg)
return []

route_list = []
for entry in proc_net_route[1:]:
route = entry.split("\t")
if len(route) > 0:
route_obj = networkutil.RouteEntry(route[idx_iface], route[idx_dest], route[idx_gw], route[idx_mask],
route[idx_flags], route[idx_metric])
route_list.append(route_obj)
return route_list

def get_route_table(self):
"""
Construct a list of all network routes known to this system.
:return: a list of network routes
:rtype: list(networkutil.RouteEntry)
"""
route_list = []
try:
with open('/proc/net/route') as routing_table:
proc_net_route = list(map(str.strip, routing_table.readlines()))
except OSError as e:
logger.periodic(logger.EVERY_HALF_DAY, "Can't read route table. {0}", e)
return route_list

count = len(proc_net_route)

if count < 1:
logger.periodic(logger.EVERY_HALF_DAY, "/proc/net/route is missing headers")
elif count == 1:
logger.periodic(logger.EVERY_HALF_DAY, "/proc/net/route contains no routes")
else:
route_list = DefaultOSUtil._build_route_list(proc_net_route)
return route_list

def get_primary_interface(self):
"""
Expand Down
58 changes: 58 additions & 0 deletions azurelinuxagent/common/utils/networkutil.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#
# Copyright 2018 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Requires Python 2.6+ and Openssl 1.0+
#


class RouteEntry(object):
"""
Represents a single route. The destination, gateway, and mask members are hex representations of the IPv4 address in
network byte order.
"""
def __init__(self, interface, destination, gateway, mask, flags, metric):
self.interface = interface
self.destination = destination
self.gateway = gateway
self.mask = mask
self.flags = int(flags, 16)
self.metric = int(metric)

@staticmethod
def _net_hex_to_dotted_quad(value):
if len(value) != 8:
raise Exception("String to dotted quad conversion must be 8 characters")
octets = []
for idx in range(6, -2, -2):
octets.append(str(int(value[idx:idx+2], 16)))
return ".".join(octets)

def destination_quad(self):
return self._net_hex_to_dotted_quad(self.destination)

def gateway_quad(self):
return self._net_hex_to_dotted_quad(self.gateway)

def mask_quad(self):
return self._net_hex_to_dotted_quad(self.mask)

def __str__(self):
return "Iface: {0}\tDestination: {1}\tGateway: {2}\tMask: {3}\tFlags: {4}\tMetric: {5}" \
.format(self.interface, self.destination_quad(), self.gateway_quad(), self.mask_quad(),
self.flags, self.metric)

def __repr__(self):
return 'RouteEntry("{0}", "{1}", "{2}", "{3}", "{4}", "{5}")'\
.format(self.interface, self.destination, self.gateway, self.mask, self.flags, self.metric)
48 changes: 48 additions & 0 deletions tests/common/osutil/test_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
from tests.tools import *


actual_get_proc_net_route = 'azurelinuxagent.common.osutil.default.DefaultOSUtil._get_proc_net_route'

def fake_is_loopback(_, iface):
return iface.startswith('lo')

Expand Down Expand Up @@ -91,6 +93,52 @@ def test_mount_dvd_failure(self, _):
self.assertTrue(msg in ustr(ose))
self.assertTrue(patch_run.call_count == 6)

def test_empty_proc_net_route(self):
routing_table = ""

mo = mock.mock_open(read_data=routing_table)
with patch(open_patch(), mo):
self.assertEqual(len(osutil.DefaultOSUtil().get_route_table()), 0)

def test_no_routes(self):
routing_table = 'Iface\tDestination\tGateway \tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT \n'

mo = mock.mock_open(read_data=routing_table)
with patch(open_patch(), mo):
self.assertEqual(len(osutil.DefaultOSUtil().get_route_table()), 0)

def test_bogus_proc_net_route(self):
routing_table = 'Iface\tDestination\tGateway \tFlags\t\tUse\tMetric\t\neth0\t00000000\t00000000\t0001\t\t0\t0\n'

mo = mock.mock_open(read_data=routing_table)
with patch(open_patch(), mo):
self.assertEqual(len(osutil.DefaultOSUtil().get_route_table()), 0)

def test_valid_routes(self):
routing_table = \
'Iface\tDestination\tGateway \tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT \n' \
'eth0\t00000000\tC1BB910A\t0003\t0\t0\t0\t00000000\t0\t0\t0 \n' \
'eth0\tC0BB910A\t00000000\t0001\t0\t0\t0\tC0FFFFFF\t0\t0\t0 \n' \
'eth0\t10813FA8\tC1BB910A\t000F\t0\t0\t0\tFFFFFFFF\t0\t0\t0 \n' \
'eth0\tFEA9FEA9\tC1BB910A\t0007\t0\t0\t0\tFFFFFFFF\t0\t0\t0 \n' \
'docker0\t002BA8C0\t00000000\t0001\t0\t0\t10\t00FFFFFF\t0\t0\t0 \n'

mo = mock.mock_open(read_data=routing_table)
with patch(open_patch(), mo):
route_list = osutil.DefaultOSUtil().get_route_table()

self.assertEqual(len(route_list), 5)
self.assertEqual(route_list[0].gateway_quad(), '10.145.187.193')
self.assertEqual(route_list[1].gateway_quad(), '0.0.0.0')
self.assertEqual(route_list[1].mask_quad(), '255.255.255.192')
self.assertEqual(route_list[2].destination_quad(), '168.63.129.16')
self.assertEqual(route_list[1].flags, 1)
self.assertEqual(route_list[2].flags, 15)
self.assertEqual(route_list[3].flags, 7)
self.assertEqual(route_list[3].metric, 0)
self.assertEqual(route_list[4].metric, 10)
self.assertEqual(route_list[0].interface, 'eth0')
self.assertEqual(route_list[4].interface, 'docker0')

@patch('azurelinuxagent.common.osutil.default.DefaultOSUtil.get_primary_interface', return_value='eth0')
@patch('azurelinuxagent.common.osutil.default.DefaultOSUtil._get_all_interfaces', return_value={'eth0':'10.0.0.1'})
Expand Down
43 changes: 43 additions & 0 deletions tests/utils/test_network_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2018 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Requires Python 2.6+ and Openssl 1.0+
#

import errno
from socket import htonl


import azurelinuxagent.common.utils.fileutil as fileutil
import azurelinuxagent.common.utils.networkutil as networkutil


from azurelinuxagent.common.future import ustr
from tests.tools import *


class TestNetworkOperations(AgentTestCase):
def test_route_entry(self):
interface = "eth0"
mask = "C0FFFFFF" # 255.255.255.192
destination = "C0BB910A" #
gateway = "C1BB910A"
flags = "1"
metric = "0"

expected = 'Iface: eth0\tDestination: 10.145.187.192\tGateway: 10.145.187.193\tMask: 255.255.255.192\tFlags: 1\tMetric: 0'

entry = networkutil.RouteEntry(interface, destination, gateway, mask, flags, metric)

self.assertEqual(str(entry), expected)

0 comments on commit c54af5d

Please sign in to comment.