fix loop endless
All checks were successful
Build and Deploy MITM Webserver / traffic_target (push) Successful in 0s
Build and Deploy MITM Webserver / build (push) Successful in 10s

This commit is contained in:
2026-03-09 17:48:39 +01:00
parent ce53ef5101
commit 0e8f08251e

View File

@@ -199,12 +199,17 @@ class BridgeLinkStateWatcher:
self._evaluate_bridge_state(reason="recovery_deadline")
def _evaluate_bridge_state(self, reason: str, source_ifname: Optional[str] = None) -> None:
with self._lock:
previous_states = dict(self._member_states)
members = [iface for iface in get_bridge_ports_once(self.bridge_name) if check_interface_exists(iface)]
states = {iface: self._read_member_state(iface) for iface in members}
states = {
iface: self._read_member_state(iface, previous_states.get(iface))
for iface in members
}
now = time.time()
with self._lock:
previous_states = dict(self._member_states)
self._last_event_ts = now
self._last_error = None
self._member_states = states
@@ -389,7 +394,10 @@ class BridgeLinkStateWatcher:
if current is None or previous is None:
return False
return current.mtu != previous.mtu or current.ethernet_profile != previous.ethernet_profile
return current.mtu != previous.mtu or self._profiles_differ(
current.ethernet_profile,
previous.ethernet_profile,
)
def _state_has_usable_config(self, state: MemberLinkState) -> bool:
"""Return whether this snapshot contains configuration that can be mirrored."""
@@ -469,28 +477,128 @@ class BridgeLinkStateWatcher:
except OSError:
return
def _read_member_state(self, ifname: str) -> MemberLinkState:
def _read_member_state(
self,
ifname: str,
previous_state: Optional[MemberLinkState] = None,
) -> MemberLinkState:
admin_up = read_interface_admin_up(ifname)
carrier_up = read_interface_carrier(ifname)
operstate = read_interface_operstate(ifname)
link_ready = self._link_looks_ready(admin_up, carrier_up, operstate)
return MemberLinkState(
ifname=ifname,
admin_up=read_interface_admin_up(ifname),
carrier_up=read_interface_carrier(ifname),
operstate=read_interface_operstate(ifname),
admin_up=admin_up,
carrier_up=carrier_up,
operstate=operstate,
mtu=read_interface_mtu(ifname),
ethernet_profile=self._read_ethernet_profile(ifname),
ethernet_profile=self._read_ethernet_profile(
ifname,
previous_state=previous_state,
link_ready=link_ready,
),
)
def _read_ethernet_profile(self, ifname: str) -> Optional[EthernetProfile]:
def _read_ethernet_profile(
self,
ifname: str,
previous_state: Optional[MemberLinkState] = None,
link_ready: Optional[bool] = None,
) -> Optional[EthernetProfile]:
"""Read ethtool speed/duplex/autoneg for one interface if supported."""
profile = read_interface_ethernet_profile(ifname)
if profile is None:
return None
return EthernetProfile(
current_profile = None
else:
current_profile = EthernetProfile(
speed_mbps=profile.get("speed_mbps"),
duplex=profile.get("duplex"),
autoneg=profile.get("autoneg"),
)
if self._should_keep_previous_profile(ifname, current_profile, previous_state, link_ready):
return previous_state.ethernet_profile if previous_state is not None else None
previous_profile = previous_state.ethernet_profile if previous_state is not None else None
return self._materialize_profile(current_profile, previous_profile)
def _should_keep_previous_profile(
self,
ifname: str,
current_profile: Optional[EthernetProfile],
previous_state: Optional[MemberLinkState],
link_ready: Optional[bool],
) -> bool:
"""Keep the previous non-null profile during transient renegotiation windows."""
if previous_state is None or previous_state.ethernet_profile is None:
return False
if current_profile is not None and self._partial_profile_is_usable(current_profile):
return False
if link_ready is True:
return False
now = time.time()
with self._lock:
settling = self._settle_deadlines.get(ifname, 0.0) > now
managed = self._managed_event_deadlines.get(ifname, 0.0) > now
return settling or managed or link_ready is False
def _link_looks_ready(
self,
admin_up: Optional[bool],
carrier_up: Optional[bool],
operstate: Optional[str],
) -> bool:
"""Evaluate link health from sysfs fields before the MemberLinkState is built."""
if admin_up is not True:
return False
if carrier_up is False:
return False
if operstate in {"down", "lowerlayerdown", "notpresent"}:
return False
return True
def _profiles_differ(
self,
current_profile: Optional[EthernetProfile],
previous_profile: Optional[EthernetProfile],
) -> bool:
"""Ignore transient non-null to null drops when detecting config changes."""
if current_profile is None:
return False
if previous_profile is None:
return True
return current_profile != previous_profile
def _partial_profile_is_usable(self, profile: Optional[EthernetProfile]) -> bool:
"""Return whether a profile contains enough data to drive synchronization."""
if profile is None:
return False
if profile.autoneg is True:
return True
return profile.autoneg is False and profile.speed_mbps is not None and profile.duplex is not None
def _materialize_profile(
self,
current_profile: Optional[EthernetProfile],
previous_profile: Optional[EthernetProfile],
) -> Optional[EthernetProfile]:
"""Fill transiently missing profile fields from the last stable snapshot."""
if current_profile is None:
return previous_profile
if previous_profile is None:
return current_profile
if self._partial_profile_is_usable(current_profile):
return current_profile
return EthernetProfile(
speed_mbps=current_profile.speed_mbps if current_profile.speed_mbps is not None else previous_profile.speed_mbps,
duplex=current_profile.duplex if current_profile.duplex is not None else previous_profile.duplex,
autoneg=current_profile.autoneg if current_profile.autoneg is not None else previous_profile.autoneg,
)
def _sync_member_configuration(self, source_ifname: str, states: dict[str, MemberLinkState]) -> None:
"""Mirror MTU and ethtool link settings from one healthy member to its siblings."""
source = states[source_ifname]