From 8fe9bee4534c3ecfb42a6ad14ba8c308fb05aaac Mon Sep 17 00:00:00 2001 From: Gabriel Felipe Souza Santos Date: Tue, 27 Oct 2026 15:39:00 -0300 Subject: [PATCH] fix(network): priorizar Ethernet e APIPA no multicast LocalNetworkInfo prefere cabo antes de Wi-Fi e 169.254.x na interface; documenta escolha de interface no apendice de multicast. Co-authored-by: Cursor --- .../appendix_portas_e_multicast.md | 4 + .../presentation/shell/LocalNetworkInfo.java | 86 ++++++++++++++++--- 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/docs/documentation/appendix_portas_e_multicast.md b/docs/documentation/appendix_portas_e_multicast.md index 4b71711..2be7e1c 100644 --- a/docs/documentation/appendix_portas_e_multicast.md +++ b/docs/documentation/appendix_portas_e_multicast.md @@ -29,6 +29,10 @@ Executar o Pitiupi no WSL e peers no Windows (ou vice-versa) pode falhar em mult - todos os peers no mesmo ambiente (Linux nativo ou VMs), ou - dois PCs físicos na mesma sub-rede. +## Escolha de interface (multicast) + +O núcleo associa o socket multicast à interface de rede preferida: **cabo (Ethernet) antes de Wi‑Fi**; em cabo com link-local APIPA, prioriza IPv4 **`169.254.*`**. Isso não substitui a exigência de **mesma sub-rede** entre os dois PCs — `169.254.x` e `192.168.x` ao mesmo tempo em hosts diferentes não compartilham heartbeat. + ## Checklist — dois PCs na mesma Wi‑Fi 1. **Mesma porta no login** (ex.: `5000` nos dois). Portas diferentes isolam a sala. diff --git a/pitiupi/src/pitiupi/presentation/shell/LocalNetworkInfo.java b/pitiupi/src/pitiupi/presentation/shell/LocalNetworkInfo.java index 4a4cce8..7e1afcc 100644 --- a/pitiupi/src/pitiupi/presentation/shell/LocalNetworkInfo.java +++ b/pitiupi/src/pitiupi/presentation/shell/LocalNetworkInfo.java @@ -11,14 +11,19 @@ import java.util.Locale; */ public final class LocalNetworkInfo { + private static final String APIPA_PREFIX = "169.254."; + private LocalNetworkInfo() { } /** - * Interface preferida para LAN (Wi‑Fi/cabo), ignorando bridges Docker/WSL/VPN virtuais quando possível. + * Interface preferida para LAN: cabo antes de Wi‑Fi; APIPA (169.254.x) antes de outro IPv4 no cabo. */ public static NetworkInterface preferredNetworkInterface() { try { + NetworkInterface wiredApipa = null; + NetworkInterface wiredOther = null; + NetworkInterface wireless = null; NetworkInterface fallback = null; Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { @@ -26,12 +31,30 @@ public final class LocalNetworkInfo { if (!isCandidateInterface(ni)) { continue; } - if (isPreferredLanInterfaceName(ni.getName())) { - return ni; - } if (fallback == null) { fallback = ni; } + String name = ni.getName(); + if (isWiredLanInterfaceName(name)) { + if (hasApipaIpv4(ni)) { + if (wiredApipa == null) { + wiredApipa = ni; + } + } else if (wiredOther == null) { + wiredOther = ni; + } + } else if (isWirelessLanInterfaceName(name) && wireless == null) { + wireless = ni; + } + } + if (wiredApipa != null) { + return wiredApipa; + } + if (wiredOther != null) { + return wiredOther; + } + if (wireless != null) { + return wireless; } return fallback; } catch (Exception ex) { @@ -43,7 +66,7 @@ public final class LocalNetworkInfo { try { NetworkInterface preferred = preferredNetworkInterface(); if (preferred != null) { - String fromPreferred = firstIpv4OnInterface(preferred); + String fromPreferred = bestIpv4OnInterface(preferred); if (fromPreferred != null) { return fromPreferred; } @@ -54,7 +77,7 @@ public final class LocalNetworkInfo { if (!isCandidateInterface(ni)) { continue; } - String ip = firstIpv4OnInterface(ni); + String ip = bestIpv4OnInterface(ni); if (ip != null) { return ip; } @@ -80,7 +103,7 @@ public final class LocalNetworkInfo { if (ni.isVirtual() || isVirtualInterfaceName(ni.getName())) { return false; } - return firstIpv4OnInterface(ni) != null; + return bestIpv4OnInterface(ni) != null; } private static boolean isVirtualInterfaceName(String name) { @@ -97,18 +120,55 @@ public final class LocalNetworkInfo { || n.startsWith("tailscale"); } - private static boolean isPreferredLanInterfaceName(String name) { + private static boolean isWiredLanInterfaceName(String name) { + if (name == null) { + return false; + } + String n = name.toLowerCase(Locale.ROOT); + return n.startsWith("eth") + || n.startsWith("eno") + || n.startsWith("enp") + || n.startsWith("ens"); + } + + private static boolean isWirelessLanInterfaceName(String name) { if (name == null) { return false; } String n = name.toLowerCase(Locale.ROOT); return n.startsWith("wlan") || n.startsWith("wlp") - || n.startsWith("wl") - || n.startsWith("eth") - || n.startsWith("eno") - || n.startsWith("enp") - || n.startsWith("ens"); + || n.startsWith("wl"); + } + + private static boolean hasApipaIpv4(NetworkInterface ni) { + return apipaIpv4OnInterface(ni) != null; + } + + private static String apipaIpv4OnInterface(NetworkInterface ni) { + try { + Enumeration addresses = ni.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) { + String host = addr.getHostAddress(); + if (host != null && host.startsWith(APIPA_PREFIX)) { + return host; + } + } + } + } catch (Exception ignored) { + return null; + } + return null; + } + + private static String bestIpv4OnInterface(NetworkInterface ni) { + String apipa = apipaIpv4OnInterface(ni); + if (apipa != null) { + return apipa; + } + return firstIpv4OnInterface(ni); } private static String firstIpv4OnInterface(NetworkInterface ni) {