mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
- 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
120 lines
3.4 KiB
Python
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]
|