Skip to content

Commit fe7c6c6

Browse files
fix: Refine MAC validation using interface name
When a user provides both an interface name and a MAC address, the current validation process retrieves sysfs link info separately using the interface name and the MAC address, then compares the results. If the information doesn't match, an error is raised. However, this approach may trigger false alarms because retrieving the link info by MAC might return the link info that only matches the current MAC instead of the permanent MAC. Since the interface name is unique within the kernel, a more robust validation method is to fetch the MAC address using the interface name and then compare it directly with the user-provided MAC address. Resolves: https://issues.redhat.com/browse/RHEL-84362 Signed-off-by: Wen Liang <[email protected]>
1 parent cac2bbb commit fe7c6c6

File tree

5 files changed

+129
-9
lines changed

5 files changed

+129
-9
lines changed

library/network_connections.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595

9696
DEFAULT_ACTIVATION_TIMEOUT = 90
9797
DEFAULT_TIMEOUT = 10
98+
NULL_MAC = "00:00:00:00:00:00"
9899

99100

100101
class CheckMode:
@@ -166,6 +167,20 @@ def _link_read_address(ifname):
166167
c = SysUtil._sysctl_read("/sys/class/net/" + ifname + "/address")
167168
return Util.mac_norm(c.strip())
168169

170+
@staticmethod
171+
def _link_read_bond_port_perm_hwaddr(ifname):
172+
filename = os.path.join(
173+
"/sys/class/net",
174+
ifname,
175+
# wokeignore:rule=slave
176+
"bonding_slave",
177+
"perm_hwaddr",
178+
)
179+
if not os.path.exists(filename):
180+
return None
181+
c = SysUtil._sysctl_read(filename)
182+
return Util.mac_norm(c.strip())
183+
169184
@staticmethod
170185
def _link_read_permaddress(ifname):
171186
return ethtool.get_perm_addr(ifname)
@@ -186,6 +201,13 @@ def _link_infos_fetch():
186201
"ifname": ifname,
187202
"address": SysUtil._link_read_address(ifname),
188203
"perm-address": SysUtil._link_read_permaddress(ifname),
204+
# When an interface is added as a port of a bonding device, its MAC
205+
# address might change, we need to retrieve and preserve the original
206+
# MAC address to ensure the user-provided interface name and MAC match
207+
# correctly.
208+
"bond-port-perm-hwaddr": SysUtil._link_read_bond_port_perm_hwaddr(
209+
ifname
210+
),
189211
}
190212
return links
191213

@@ -2130,14 +2152,13 @@ def run_prepare(self):
21302152
for idx, connection in enumerate(self.connections):
21312153
if "type" in connection and connection["check_iface_exists"]:
21322154
# when the profile is tied to a certain interface via
2133-
# 'interface_name' or 'mac', check that such an interface
2155+
# 'interface_name' and 'mac', check that such an interface
21342156
# exists.
21352157
#
21362158
# This check has many flaws, as we don't check whether the
21372159
# existing interface has the right device type. Also, there is
21382160
# some ambiguity between the current MAC address and the
21392161
# permanent MAC address.
2140-
li_mac = None
21412162
li_ifname = None
21422163
if connection["interface_name"]:
21432164
li_ifname = SysUtil.link_info_find(connection["interface_name"])
@@ -2156,13 +2177,23 @@ def run_prepare(self):
21562177
"infiniband interface exists"
21572178
% (connection["interface_name"]),
21582179
)
2159-
if li_mac and li_ifname and li_mac != li_ifname:
2160-
self.log_fatal(
2161-
idx,
2162-
"profile specifies interface_name '%s' and mac '%s' but no "
2163-
"such interface exists"
2164-
% (connection["interface_name"], connection["mac"]),
2165-
)
2180+
elif connection["mac"]:
2181+
perm_address = li_ifname.get("perm-address", NULL_MAC)
2182+
current_address = li_ifname.get("address", NULL_MAC)
2183+
bond_port_perm_hwaddr = li_ifname.get(
2184+
"bond-port-perm-hwaddr", NULL_MAC
2185+
)
2186+
if (perm_address not in (NULL_MAC, connection["mac"])) or (
2187+
perm_address == NULL_MAC
2188+
and connection["mac"]
2189+
not in (current_address, bond_port_perm_hwaddr)
2190+
):
2191+
self.log_fatal(
2192+
idx,
2193+
"profile specifies interface_name '%s' and mac '%s' "
2194+
"but no such interface exists"
2195+
% (connection["interface_name"], connection["mac"]),
2196+
)
21662197

21672198
def start_transaction(self):
21682199
"""Hook before making changes"""

tests/ensure_provider_tests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
},
7575
"playbooks/tests_ignore_auto_dns.yml": {},
7676
"playbooks/tests_bond_options.yml": {},
77+
"playbooks/tests_bond_port_match_by_mac.yml": {},
7778
"playbooks/tests_eth_dns_support.yml": {},
7879
"playbooks/tests_dummy.yml": {}, # wokeignore:rule=dummy
7980
"playbooks/tests_infiniband.yml": {},
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
---
3+
- name: Play for creating the connection to match the port device
4+
based on the perm_hwaddr
5+
hosts: all
6+
vars:
7+
controller_profile: bond0
8+
controller_device: nm-bond
9+
port1_profile: bond0.0
10+
dhcp_interface1: test1
11+
port2_profile: bond0.1
12+
dhcp_interface2: test2
13+
profile: test2conn
14+
interface: test2
15+
tasks:
16+
- name: Test creating the connection to match the port device
17+
based on the perm_hwaddr
18+
tags:
19+
- tests::bond:create
20+
block:
21+
- name: Include the task 'run_test.yml'
22+
include_tasks: tasks/run_test.yml
23+
vars:
24+
lsr_description: Given two DHCP-enabled network interfaces,
25+
when creating a bond profile with them,
26+
then we can still create the connection to match the port device
27+
based on the perm_hwaddr
28+
lsr_setup:
29+
- tasks/create_test_interfaces_with_dhcp.yml
30+
- tasks/assert_dhcp_device_present.yml
31+
lsr_test:
32+
- tasks/create_bond_profile.yml
33+
- tasks/create_bond_port_match_by_mac.yml
34+
lsr_assert:
35+
- tasks/assert_controller_device_present.yml
36+
- tasks/assert_profile_present.yml
37+
lsr_cleanup:
38+
- tasks/cleanup_bond_profile+device.yml
39+
- tasks/remove_test_interfaces_with_dhcp.yml
40+
- tasks/check_network_dns.yml
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
---
3+
- name: Retrieve perm_hwaddr using ethtool
4+
# wokeignore:rule=slave
5+
command: cat /sys/class/net/{{ interface }}/bonding_slave/perm_hwaddr
6+
register: mac_address_result
7+
changed_when: false
8+
failed_when: mac_address_result.rc != 0
9+
- name: Set the MAC address variable
10+
set_fact:
11+
mac: "{{ mac_address_result.stdout_lines[-1].split(' ')[-1] }}"
12+
- name: Display the retrieved MAC address
13+
debug:
14+
msg: "Retrieved MAC address for {{ interface }}: {{ mac }}"
15+
- name: Test matching the port device based on the perm_hwaddr
16+
import_role:
17+
name: linux-system-roles.network
18+
vars:
19+
network_connections:
20+
- name: "{{ profile }}"
21+
state: up
22+
type: ethernet
23+
interface_name: "{{ interface }}"
24+
mac: "{{ mac }}"
25+
...
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
# This file was generated by ensure_provider_tests.py
3+
---
4+
# set network provider and gather facts
5+
# yamllint disable rule:line-length
6+
- name: Run playbook 'playbooks/tests_bond_port_match_by_mac.yml' with nm as provider
7+
hosts: all
8+
tasks:
9+
- name: Include the task 'el_repo_setup.yml'
10+
include_tasks: tasks/el_repo_setup.yml
11+
- name: Set network provider to 'nm'
12+
set_fact:
13+
network_provider: nm
14+
tags:
15+
- always
16+
17+
18+
# The test requires or should run with NetworkManager, therefore it cannot run
19+
# on RHEL/CentOS 6
20+
- name: Import the playbook 'playbooks/tests_bond_port_match_by_mac.yml'
21+
import_playbook: playbooks/tests_bond_port_match_by_mac.yml
22+
when:
23+
- ansible_distribution_major_version != '6'

0 commit comments

Comments
 (0)