Files
bambuddy/backend/app/services/network_utils.py
T
maziggy 81d18cb8bd Virtual Printer Proxy Mode Improvements
- SSDP proxy for cross-network setups: select slicer network interface for automatic printer discovery via SSDP relay
- FTP proxy now listens on privileged port 990 (matching Bambu Studio expectations) instead of 9990
- For systemd: requires `AmbientCapabilities=CAP_NET_BIND_SERVICE` capability
- Automatic directory permission checking at startup with clear error messages for Docker/bare metal
2026-02-04 12:29:20 +01:00

120 lines
3.4 KiB
Python

"""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]