diff --git a/docs/documentation/appendix_portas_e_multicast.md b/docs/documentation/appendix_portas_e_multicast.md index d6d6597..4b71711 100644 --- a/docs/documentation/appendix_portas_e_multicast.md +++ b/docs/documentation/appendix_portas_e_multicast.md @@ -29,10 +29,18 @@ 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. +## Checklist — dois PCs na mesma Wi‑Fi + +1. **Mesma porta no login** (ex.: `5000` nos dois). Portas diferentes isolam a sala. +2. **Configurações → Sua conexão:** IPs na mesma sub-rede (ex. ambos `192.168.0.x` ou `192.168.1.x`). Se um for `172.x` (WSL/Docker) e outro `192.168.x`, o multicast da aula não cruza. +3. **Firewall:** liberar **UDP entrada e saída** na porta do login (Windows Defender / `ufw`). +4. **Roteador:** desativar *isolamento de clientes* / *AP isolation* / Wi‑Fi de visitantes. +5. Aguardar até **~15 s** após abrir os dois apps (heartbeat a cada 5 s). + ## Sintomas | Sintoma | Verificar | | --- | --- | -| Lista de usuários vazia | Porta igual entre peers? Mesma sub-rede? Firewall? | -| Plugin não recebe | Desserialização / SPI / JAR em `plugins/` | -| TCP falha | Peer ativo no heartbeat? IP correto? | +| Lista só com **Todos**, sem peers | Porta igual? Mesma sub-rede? Firewall UDP? AP isolation? WSL num dos PCs? | +| Plugin não recebe | Desserialização / SPI / JAR em `PITIUPI_HOME/plugins` | +| TCP falha | Peer visível na lista? IP correto? | diff --git a/pitiupi/src/pitiupi/net/udp/SocketUDP.java b/pitiupi/src/pitiupi/net/udp/SocketUDP.java index fee201f..0c150af 100644 --- a/pitiupi/src/pitiupi/net/udp/SocketUDP.java +++ b/pitiupi/src/pitiupi/net/udp/SocketUDP.java @@ -13,6 +13,7 @@ import java.net.*; import java.util.logging.Level; import java.util.logging.Logger; +import pitiupi.presentation.shell.LocalNetworkInfo; import pitiupi.control.ConnectionManager; import pitiupi.control.plugins.PluginManager; import pitiupi.models.Messages.GoodbyeMessage; @@ -39,6 +40,7 @@ public class SocketUDP extends Thread { private MulticastSocket multicastSocket; private InetAddress address; + private NetworkInterface multicastInterface; private final ConnectionManager connectionManager; private final PluginManager pluginManager; @@ -62,11 +64,19 @@ public class SocketUDP extends Thread { try { this.address = InetAddress.getByName(SocketUDP.INET_ADDR); - multicastSocket = new MulticastSocket(connectionManager.getPort()); + int port = connectionManager.getPort(); + multicastSocket = new MulticastSocket(port); multicastSocket.setSendBufferSize(256000); multicastSocket.setReceiveBufferSize(256000); multicastSocket.setReuseAddress(true); - multicastSocket.joinGroup(address); + multicastSocket.setTimeToLive(1); + multicastInterface = LocalNetworkInfo.preferredNetworkInterface(); + if (multicastInterface != null) { + multicastSocket.setNetworkInterface(multicastInterface); + multicastSocket.joinGroup(new InetSocketAddress(address, port), multicastInterface); + } else { + multicastSocket.joinGroup(address); + } } catch (UnknownHostException ex) { System.getLogger(SocketUDP.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex); @@ -118,7 +128,12 @@ public class SocketUDP extends Thread { running = false; if (multicastSocket != null) { try { - multicastSocket.leaveGroup(address); + int port = connectionManager.getPort(); + if (multicastInterface != null) { + multicastSocket.leaveGroup(new InetSocketAddress(address, port), multicastInterface); + } else { + multicastSocket.leaveGroup(address); + } } catch (IOException ex) { System.out.println(ex); } diff --git a/pitiupi/src/pitiupi/presentation/shell/LocalNetworkInfo.java b/pitiupi/src/pitiupi/presentation/shell/LocalNetworkInfo.java index 5d3493e..4a4cce8 100644 --- a/pitiupi/src/pitiupi/presentation/shell/LocalNetworkInfo.java +++ b/pitiupi/src/pitiupi/presentation/shell/LocalNetworkInfo.java @@ -4,6 +4,7 @@ import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumeration; +import java.util.Locale; /** * Endereço IPv4 local preferencial para compartilhar com peers. @@ -13,20 +14,49 @@ public final class LocalNetworkInfo { private LocalNetworkInfo() { } - public static String bestLocalIpv4() { + /** + * Interface preferida para LAN (Wi‑Fi/cabo), ignorando bridges Docker/WSL/VPN virtuais quando possível. + */ + public static NetworkInterface preferredNetworkInterface() { try { + NetworkInterface fallback = null; Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface ni = interfaces.nextElement(); - if (!ni.isUp() || ni.isLoopback() || ni.isVirtual()) { + if (!isCandidateInterface(ni)) { continue; } - Enumeration addresses = ni.getInetAddresses(); - while (addresses.hasMoreElements()) { - InetAddress addr = addresses.nextElement(); - if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) { - return addr.getHostAddress(); - } + if (isPreferredLanInterfaceName(ni.getName())) { + return ni; + } + if (fallback == null) { + fallback = ni; + } + } + return fallback; + } catch (Exception ex) { + return null; + } + } + + public static String bestLocalIpv4() { + try { + NetworkInterface preferred = preferredNetworkInterface(); + if (preferred != null) { + String fromPreferred = firstIpv4OnInterface(preferred); + if (fromPreferred != null) { + return fromPreferred; + } + } + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface ni = interfaces.nextElement(); + if (!isCandidateInterface(ni)) { + continue; + } + String ip = firstIpv4OnInterface(ni); + if (ip != null) { + return ip; } } InetAddress local = InetAddress.getLocalHost(); @@ -36,6 +66,66 @@ public final class LocalNetworkInfo { } } + private static boolean isCandidateInterface(NetworkInterface ni) { + if (ni == null) { + return false; + } + try { + if (!ni.isUp() || ni.isLoopback()) { + return false; + } + } catch (java.net.SocketException ex) { + return false; + } + if (ni.isVirtual() || isVirtualInterfaceName(ni.getName())) { + return false; + } + return firstIpv4OnInterface(ni) != null; + } + + private static boolean isVirtualInterfaceName(String name) { + if (name == null) { + return true; + } + String n = name.toLowerCase(Locale.ROOT); + return n.equals("lo") + || n.startsWith("docker") + || n.startsWith("br-") + || n.startsWith("veth") + || n.startsWith("vir") + || n.contains("wsl") + || n.startsWith("tailscale"); + } + + private static boolean isPreferredLanInterfaceName(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"); + } + + private static String firstIpv4OnInterface(NetworkInterface ni) { + try { + Enumeration addresses = ni.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) { + return addr.getHostAddress(); + } + } + } catch (Exception ignored) { + return null; + } + return null; + } + /** * {@code true} se o IP informado é deste host (inclui loopback e interfaces ativas). */