| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- """Network utility functions for interface detection."""
- import ipaddress
- import logging
- import socket
- import struct
- logger = logging.getLogger(__name__)
- # Interfaces to exclude from selection
- EXCLUDED_INTERFACE_PREFIXES = ("lo", "docker", "br-", "veth", "virbr")
- def get_network_interfaces() -> list[dict]:
- """Get all network interfaces with their IPs and subnets.
- Returns:
- List of dicts with name, ip, netmask, subnet, broadcast
- """
- interfaces = []
- try:
- import fcntl
- for iface in socket.if_nameindex():
- name = iface[1]
- # Skip excluded interfaces
- if any(name.startswith(prefix) for prefix in EXCLUDED_INTERFACE_PREFIXES):
- continue
- try:
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- # Get IP address
- ip_bytes = fcntl.ioctl(
- s.fileno(),
- 0x8915, # SIOCGIFADDR
- struct.pack("256s", name[:15].encode()),
- )[20:24]
- ip = socket.inet_ntoa(ip_bytes)
- # Get netmask
- netmask_bytes = fcntl.ioctl(
- s.fileno(),
- 0x891B, # SIOCGIFNETMASK
- struct.pack("256s", name[:15].encode()),
- )[20:24]
- netmask = socket.inet_ntoa(netmask_bytes)
- # Calculate subnet
- network = ipaddress.IPv4Network(f"{ip}/{netmask}", strict=False)
- interfaces.append(
- {
- "name": name,
- "ip": ip,
- "netmask": netmask,
- "subnet": str(network),
- }
- )
- s.close()
- except OSError:
- # Interface doesn't have an IP or other error
- pass
- except Exception as e:
- logger.debug(f"Error getting info for interface {name}: {e}")
- except ImportError:
- # fcntl not available (Windows)
- logger.warning("fcntl not available, interface detection limited")
- except Exception as e:
- logger.error(f"Error enumerating interfaces: {e}")
- return interfaces
- def find_interface_for_ip(target_ip: str) -> dict | None:
- """Find which interface is on the same subnet as the target IP.
- Args:
- target_ip: IP address to find the matching interface for
- Returns:
- Interface dict or None if not found
- """
- try:
- target = ipaddress.IPv4Address(target_ip)
- except ValueError:
- logger.error(f"Invalid target IP: {target_ip}")
- return None
- interfaces = get_network_interfaces()
- for iface in interfaces:
- try:
- network = ipaddress.IPv4Network(iface["subnet"], strict=False)
- if target in network:
- logger.debug(f"Found interface {iface['name']} ({iface['ip']}) for target {target_ip}")
- return iface
- except ValueError:
- continue
- logger.warning(f"No interface found for target IP {target_ip}")
- return None
- def get_other_interfaces(exclude_ip: str) -> list[dict]:
- """Get all interfaces except the one with the given IP.
- Args:
- exclude_ip: IP address of interface to exclude
- Returns:
- List of interface dicts
- """
- interfaces = get_network_interfaces()
- return [iface for iface in interfaces if iface["ip"] != exclude_ip]
|