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

Log network configuration on service start #2157

Merged
merged 2 commits into from
Feb 4, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
105 changes: 58 additions & 47 deletions azurelinuxagent/common/osutil/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -1369,55 +1369,57 @@ def get_total_cpu_ticks_since_boot():
break
return system_cpu

def get_nic_state(self):
def get_nic_state(self, as_string=False):
"""
Capture NIC state (IPv4 and IPv6 addresses plus link state).

:return: Dictionary of NIC state objects, with the NIC name as key
:return: By default returns a dictionary of NIC state objects, with the NIC name as key. If as_string is True
returns the state as a string
:rtype: dict(str,NetworkInformationCard)
"""
state = {}

status, output = shellutil.run_get_output("ip -a -o link", chk_err=False, log_cmd=False)
# pylint: disable=W1401
# pylint: disable=W0105
"""
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0 addrgenmode eui64
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000\ link/ether 00:0d:3a:30:c3:5a brd ff:ff:ff:ff:ff:ff promiscuity 0 addrgenmode eui64
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default \ link/ether 02:42:b5:d5:00:1d brd ff:ff:ff:ff:ff:ff promiscuity 0 \ bridge forward_delay 1500 hello_time 200 max_age 2000 ageing_time 30000 stp_state 0 priority 32768 vlan_filtering 0 vlan_protocol 802.1Q addrgenmode eui64
all_command = ["ip", "-a", "-o", "link"]
inet_command = ["ip", "-4", "-a", "-o", "address"]
inet6_command = ["ip", "-6", "-a", "-o", "address"]

"""
# pylint: enable=W0105
if status != 0:
logger.verbose("Could not fetch NIC link info; status {0}, {1}".format(status, output))
return {}
try:
all_output = shellutil.run_command(all_command)
except shellutil.CommandError as command_error:
logger.verbose("Could not fetch NIC link info: {0}", ustr(command_error))
return "" if as_string else {}

if as_string:
def run_command(command):
try:
return shellutil.run_command(command)
except shellutil.CommandError as command_error:
return str(command_error)

inet_output = run_command(inet_command)
inet6_output = run_command(inet6_command)

for entry in output.splitlines():
return "Executing {0}:\n{1}\nExecuting {2}:\n{3}\nExecuting {4}:\n{5}\n".format(all_command, all_output, inet_command, inet_output, inet6_command, inet6_output)
else:
self._update_nic_state_all(state, all_output)
self._update_nic_state(state, inet_command, NetworkInterfaceCard.add_ipv4, "an IPv4 address")
self._update_nic_state(state, inet6_command, NetworkInterfaceCard.add_ipv6, "an IPv6 address")
return state

@staticmethod
def _update_nic_state_all(state, command_output):
for entry in command_output.splitlines():
# Sample output:
# 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0 addrgenmode eui64
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000\ link/ether 00:0d:3a:30:c3:5a brd ff:ff:ff:ff:ff:ff promiscuity 0 addrgenmode eui64
# 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default \ link/ether 02:42:b5:d5:00:1d brd ff:ff:ff:ff:ff:ff promiscuity 0 \ bridge forward_delay 1500 hello_time 200 max_age 2000 ageing_time 30000 stp_state 0 priority 32768 vlan_filtering 0 vlan_protocol 802.1Q addrgenmode eui64
result = IP_COMMAND_OUTPUT.match(entry)
if result:
name = result.group(1)
state[name] = NetworkInterfaceCard(name, result.group(2))

self._update_nic_state(state, "ip -4 -a -o address", NetworkInterfaceCard.add_ipv4, "an IPv4 address")
# pylint: disable=W0105
""" # pylint: disable=W1401
1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever
2: eth0 inet 10.145.187.220/26 brd 10.145.187.255 scope global eth0\ valid_lft forever preferred_lft forever
3: docker0 inet 192.168.43.1/24 brd 192.168.43.255 scope global docker0\ valid_lft forever preferred_lft forever
"""
# pylint: enable=W0105

self._update_nic_state(state, "ip -6 -a -o address", NetworkInterfaceCard.add_ipv6, "an IPv6 address")
# pylint: disable=W0105
""" # pylint: disable=W1401
1: lo inet6 ::1/128 scope host \ valid_lft forever preferred_lft forever
2: eth0 inet6 fe80::20d:3aff:fe30:c35a/64 scope link \ valid_lft forever preferred_lft forever
"""
# pylint: enable=W0105

return state

def _update_nic_state(self, state, ip_command, handler, description):
@staticmethod
def _update_nic_state(state, ip_command, handler, description):
"""
Update the state of NICs based on the output of a specified ip subcommand.

Expand All @@ -1426,18 +1428,27 @@ def _update_nic_state(self, state, ip_command, handler, description):
:param handler: A method on the NetworkInterfaceCard class
:param str description: Description of the particular information being added to the state
"""
status, output = shellutil.run_get_output(ip_command, chk_err=True)
if status != 0:
return

for entry in output.splitlines():
result = IP_COMMAND_OUTPUT.match(entry)
if result:
interface_name = result.group(1)
if interface_name in state:
handler(state[interface_name], result.group(2))
else:
logger.error("Interface {0} has {1} but no link state".format(interface_name, description))
try:
output = shellutil.run_command(ip_command)

for entry in output.splitlines():
# family inet sample output:
# 1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever
# 2: eth0 inet 10.145.187.220/26 brd 10.145.187.255 scope global eth0\ valid_lft forever preferred_lft forever
# 3: docker0 inet 192.168.43.1/24 brd 192.168.43.255 scope global docker0\ valid_lft forever preferred_lft forever
#
# family inet6 sample output:
# 1: lo inet6 ::1/128 scope host \ valid_lft forever preferred_lft forever
# 2: eth0 inet6 fe80::20d:3aff:fe30:c35a/64 scope link \ valid_lft forever preferred_lft forever
result = IP_COMMAND_OUTPUT.match(entry)
if result:
interface_name = result.group(1)
if interface_name in state:
handler(state[interface_name], result.group(2))
else:
logger.error("Interface {0} has {1} but no link state".format(interface_name, description))
except shellutil.CommandError as command_error:
logger.error("[{0}] failed: {1}", ' '.join(ip_command), str(command_error))

@staticmethod
def _run_command_without_raising(cmd, log_error=True):
Expand Down
5 changes: 4 additions & 1 deletion azurelinuxagent/common/osutil/openwrt.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,16 @@ def useradd(self, username, expiration=None, comment=None):
def get_dhcp_pid(self):
return self._get_dhcp_pid(["pidof", self.dhclient_name])

def get_nic_state(self):
def get_nic_state(self, as_string=False):
"""
Capture NIC state (IPv4 and IPv6 addresses plus link state).

:return: Dictionary of NIC state objects, with the NIC name as key
:rtype: dict(str,NetworkInformationCard)
"""
if as_string: # as_string not supported on open wrt
return ''

state = {}
status, output = shellutil.run_get_output("ip -o link", chk_err=False, log_cmd=False)

Expand Down
25 changes: 23 additions & 2 deletions azurelinuxagent/ga/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#

import datetime
import os
import threading

import azurelinuxagent.common.conf as conf
Expand Down Expand Up @@ -109,6 +110,24 @@ def __init__(self):
self.last_route_table_hash = b''
self.last_nic_state = {}

def log_network_configuration(self):
try:
route_file = '/proc/net/route'
if os.path.exists(route_file):
lines = []
with open(route_file) as file_object:
for line in file_object:
lines.append(line)
if len(lines) >= 100:
lines.append("<TRUNCATED TO {0} LINES".format(len(lines)))
break
logger.info("Routing table from {0}:\n{1}", route_file, ''.join(lines))
network_interfaces = self.osutil.get_nic_state(as_string=True)
if network_interfaces != '':
logger.info("Network interfaces:\n{0}", network_interfaces)
except Exception as exception:
logger.warn("Error fetching the network configuration: {0}", ustr(exception))

def _operation_impl(self):
raw_route_list = self.osutil.read_route_table()
digest = hash_strings(raw_route_list)
Expand Down Expand Up @@ -270,10 +289,12 @@ def daemon(self):
SendImdsHeartbeatOperation(protocol_util, health_service)
]

report_network_configuration_changes = ReportNetworkConfigurationChangesOperation()
if conf.get_monitor_network_configuration_changes():
periodic_operations.append(ReportNetworkConfigurationChangesOperation())
periodic_operations.append(report_network_configuration_changes)
else:
logger.info("Monitor.NetworkConfigurationChanges is set to disabled.")
logger.info("Monitor.NetworkConfigurationChanges is disabled.")
report_network_configuration_changes.log_network_configuration()

while not self.stopped():
try:
Expand Down
3 changes: 3 additions & 0 deletions tests/common/osutil/test_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,9 @@ def test_get_nic_state(self):
another_state[name].add_ipv4("xyzzy")
self.assertNotEqual(state, another_state)

as_string = osutil.DefaultOSUtil().get_nic_state(as_string=True)
self.assertNotEqual(as_string, '')

def test_get_dhcp_pid_should_return_a_list_of_pids(self):
osutil_get_dhcp_pid_should_return_a_list_of_pids(self, osutil.DefaultOSUtil())

Expand Down