Skip to content
Open
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
36 changes: 36 additions & 0 deletions core/services/cable_guy/api/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from networksetup import AbstractNetworkHandler, NetworkHandlerDetector
from pyroute2 import IW, NDB, IPRoute
from pyroute2.netlink.exceptions import NetlinkError
from pyroute2.netlink.rtnl import rtprotos
from pyroute2.netlink.rtnl.ifaddrmsg import ifaddrmsg
from typedefs import (
AddressMode,
Expand Down Expand Up @@ -380,8 +381,38 @@ def remove_ip(self, interface_name: str, ip_address: str) -> None:
address for address in saved_interface.addresses if address.mode != AddressMode.Client
]

self._remove_orphaned_subnet_route(interface_name, ip_address, saved_interface)

self._update_interface_settings(interface_name, saved_interface)

def _remove_orphaned_subnet_route(
self, interface_name: str, ip_address: str, saved_interface: NetworkInterface
) -> None:
# A subnet route previously adopted will outlives its IP and can hijack traffic
if not self.weak_is_ip_address(ip_address) or ip_address == "0.0.0.0":
return
Comment on lines +392 to +393

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if not self.weak_is_ip_address(ip_address) or ip_address == "0.0.0.0":
return
# parse
try:
addr = ip_address(ip_address)
except ValueError:
return
# check
if addr.is_unspecified:
return


# /24 mirrors the prefix used when adding/removing static IPs
subnet = ip_network(f"{ip_address}/24", strict=False)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
subnet = ip_network(f"{ip_address}/24", strict=False)
prefixlen = 24 if addr.version == 4 else 64
subnet = ip_network((addr, prefixlen), strict=False)

try:
remaining = self.get_interface_by_name(interface_name).addresses
except Exception as error:
logger.error(f"Could not check remaining addresses on {interface_name}: {error}")
return
if any(self.weak_is_ip_address(address.ip) and IPv4Address(address.ip) in subnet for address in remaining):
return
Comment on lines +402 to +403

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if any(self.weak_is_ip_address(address.ip) and IPv4Address(address.ip) in subnet for address in remaining):
return
for address in remaining:
# parse
try:
other = ipaddress.ip_address(address.ip)
except ValueError:
continue
# check
if other.version == addr.version and other in subnet:
return


try:
self.ipr.route("del", dst=str(subnet), oif=self._get_interface_index(interface_name))
logger.info(f"Removed orphaned route {subnet} from interface {interface_name}.")
except NetlinkError as error:
# the kernel already removes its own connected routes on address deletion, so a missing route is fine
if error.code not in (errno.ESRCH, errno.ENOENT):
logger.error(f"Failed to remove orphaned route {subnet} on {interface_name}: {error}")
return

saved_interface.routes = [route for route in saved_interface.routes if route.destination != str(subnet)]

def get_interface_by_name(self, name: str, include_dhcp_markers: bool = False) -> NetworkInterface:
"""Get interface by name.

Expand Down Expand Up @@ -689,6 +720,11 @@ def get_routes(self, interface_name: str, ignore_unmanaged: bool = True) -> Set[

routes: Set[Route] = set()
for raw_route in raw_routes:
# Kernel-maintained connected routes are created/removed automatically alongside their
# interface addresses. Adopting them would turn them into persistent routes
# that outlive their IP, so we ignore them entirely.
if raw_route["proto"] == rtprotos["RTPROT_KERNEL"]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I remember seeing a forever-growing list in some scenarios, and I think this must be it.

continue
try:
route = self._parse_route(raw_route)

Expand Down