diff --git a/moonlight-common/src/main/java/com/limelight/nvstream/http/ComputerDetails.java b/moonlight-common/src/main/java/com/limelight/nvstream/http/ComputerDetails.java index 3082ec25..2840821b 100644 --- a/moonlight-common/src/main/java/com/limelight/nvstream/http/ComputerDetails.java +++ b/moonlight-common/src/main/java/com/limelight/nvstream/http/ComputerDetails.java @@ -14,6 +14,7 @@ public class ComputerDetails { public String localAddress; public String remoteAddress; public String manualAddress; + public String ipv6Address; public String macAddress; public X509Certificate serverCert; @@ -23,17 +24,17 @@ public class ComputerDetails { public PairingManager.PairState pairState; public int runningGameId; public String rawAppList; - + public ComputerDetails() { // Use defaults state = State.UNKNOWN; } - + public ComputerDetails(ComputerDetails details) { // Copy details from the other computer update(details); } - + public void update(ComputerDetails details) { this.state = details.state; this.name = details.name; @@ -50,6 +51,9 @@ public class ComputerDetails { if (details.manualAddress != null) { this.manualAddress = details.manualAddress; } + if (details.ipv6Address != null) { + this.ipv6Address = details.ipv6Address; + } if (details.macAddress != null && !details.macAddress.equals("00:00:00:00:00:00")) { this.macAddress = details.macAddress; } @@ -60,7 +64,7 @@ public class ComputerDetails { this.runningGameId = details.runningGameId; this.rawAppList = details.rawAppList; } - + @Override public String toString() { StringBuilder str = new StringBuilder(); @@ -70,6 +74,7 @@ public class ComputerDetails { str.append("UUID: ").append(uuid).append("\n"); str.append("Local Address: ").append(localAddress).append("\n"); str.append("Remote Address: ").append(remoteAddress).append("\n"); + str.append("IPv6 Address: ").append(ipv6Address).append("\n"); str.append("Manual Address: ").append(manualAddress).append("\n"); str.append("MAC Address: ").append(macAddress).append("\n"); str.append("Pair State: ").append(pairState).append("\n"); diff --git a/moonlight-common/src/main/java/com/limelight/nvstream/mdns/MdnsComputer.java b/moonlight-common/src/main/java/com/limelight/nvstream/mdns/MdnsComputer.java index fe7761aa..921c02b2 100644 --- a/moonlight-common/src/main/java/com/limelight/nvstream/mdns/MdnsComputer.java +++ b/moonlight-common/src/main/java/com/limelight/nvstream/mdns/MdnsComputer.java @@ -1,44 +1,65 @@ package com.limelight.nvstream.mdns; import java.net.Inet4Address; +import java.net.Inet6Address; public class MdnsComputer { - private Inet4Address ipAddr; + private Inet4Address v4Addr; + private Inet6Address v6Addr; private String name; - - public MdnsComputer(String name, Inet4Address addr) { + + public MdnsComputer(String name, Inet4Address v4Addr, Inet6Address v6Addr) { this.name = name; - this.ipAddr = addr; + this.v4Addr = v4Addr; + this.v6Addr = v6Addr; } - + public String getName() { return name; } - - public Inet4Address getAddress() { - return ipAddr; + + public Inet4Address getAddressV4() { + return v4Addr; } - + + public Inet6Address getAddressV6() { + return v6Addr; + } + @Override public int hashCode() { return name.hashCode(); } - + @Override public boolean equals(Object o) { if (o instanceof MdnsComputer) { MdnsComputer other = (MdnsComputer)o; - if (other.ipAddr.equals(ipAddr) && - other.name.equals(name)) { - return true; + + if (!other.name.equals(name)) { + return false; } + + if ((other.v4Addr != null && v4Addr == null) || + (other.v4Addr == null && v4Addr != null) || + (other.v4Addr != null && !other.v4Addr.equals(v4Addr))) { + return false; + } + + if ((other.v6Addr != null && v6Addr == null) || + (other.v6Addr == null && v6Addr != null) || + (other.v6Addr != null && !other.v6Addr.equals(v6Addr))) { + return false; + } + + return true; } - + return false; } - + @Override public String toString() { - return "["+name+" - "+ipAddr+"]"; + return "["+name+" - "+v4Addr+" - "+v6Addr+"]"; } } diff --git a/moonlight-common/src/main/java/com/limelight/nvstream/mdns/MdnsDiscoveryAgent.java b/moonlight-common/src/main/java/com/limelight/nvstream/mdns/MdnsDiscoveryAgent.java index 0338aa76..87658a1a 100644 --- a/moonlight-common/src/main/java/com/limelight/nvstream/mdns/MdnsDiscoveryAgent.java +++ b/moonlight-common/src/main/java/com/limelight/nvstream/mdns/MdnsDiscoveryAgent.java @@ -87,7 +87,6 @@ public class MdnsDiscoveryAgent implements ServiceListener { public boolean useInetAddress(NetworkInterface networkInterface, InetAddress interfaceAddress) { // This is an copy of jmDNS's implementation, except we omit the multicast check, since // it seems at least some devices lie about interfaces not supporting multicast when they really do. - // We also will skip IPv6 addresses since GeForce Experience doesn't listen on IPv6 ports. try { if (!networkInterface.isUp()) { return false; @@ -99,10 +98,6 @@ public class MdnsDiscoveryAgent implements ServiceListener { } */ - if (interfaceAddress instanceof Inet6Address) { - return false; - } - if (networkInterface.isLoopback()) { return false; } @@ -136,7 +131,7 @@ public class MdnsDiscoveryAgent implements ServiceListener { return instance; } } - + private static void dereferenceResolver() { synchronized (MdnsDiscoveryAgent.class) { if (--resolverRefCount == 0) { @@ -146,11 +141,11 @@ public class MdnsDiscoveryAgent implements ServiceListener { } } } - + public MdnsDiscoveryAgent(MdnsDiscoveryListener listener) { this.listener = listener; } - + private void handleResolvedServiceInfo(ServiceInfo info) { synchronized (pendingResolution) { pendingResolution.remove(info.getName()); @@ -164,17 +159,85 @@ public class MdnsDiscoveryAgent implements ServiceListener { return; } } - - private void handleServiceInfo(ServiceInfo info) throws UnsupportedEncodingException { + + private Inet6Address getBestIpv6Address(Inet6Address[] addresses) { + // First try to find a link local address, so we can match the interface identifier + // with a global address (this will work for SLAAC but not DHCPv6). + Inet6Address linkLocalAddr = null; + for (Inet6Address addr : addresses) { + if (addr.isLinkLocalAddress()) { + LimeLog.info("Found link-local address: "+addr.getHostAddress()); + linkLocalAddr = addr; + break; + } + } + + // We will try once to match a SLAAC interface suffix, then + // pick the first matching address + for (int tries = 0; tries < 2; tries++) { + // We assume the addresses are already sorted in descending order + // of preference from Bonjour. + for (Inet6Address addr : addresses) { + if (addr.isLinkLocalAddress() || addr.isSiteLocalAddress() || addr.isLoopbackAddress()) { + // Link-local, site-local, and loopback aren't global + LimeLog.info("Ignoring non-global address: "+addr.getHostAddress()); + continue; + } + + byte[] addrBytes = addr.getAddress(); + + // 2002::/16 + if (addrBytes[0] == 0x20 && addrBytes[1] == 0x02) { + // 6to4 has horrible performance + LimeLog.info("Ignoring 6to4 address: "+addr.getHostAddress()); + continue; + } + // 2001::/32 + else if (addrBytes[0] == 0x20 && addrBytes[1] == 0x01 && addrBytes[2] == 0x00 && addrBytes[3] == 0x00) { + // Teredo also has horrible performance + LimeLog.info("Ignoring Teredo address: "+addr.getHostAddress()); + continue; + } + + // Compare the final 64-bit interface identifier and skip the address + // if it doesn't match our link-local address. + if (linkLocalAddr != null && tries == 0) { + boolean matched = true; + + for (int i = 8; i < 16; i++) { + if (linkLocalAddr.getAddress()[i] != addr.getAddress()[i]) { + matched = false; + break; + } + } + + if (!matched) { + LimeLog.info("Ignoring non-matching global address: "+addr.getHostAddress()); + continue; + } + } + + return addr; + } + } + + return null; + } + + private void handleServiceInfo(ServiceInfo info) throws UnsupportedEncodingException { Inet4Address addrs[] = info.getInet4Addresses(); - - LimeLog.info("mDNS: "+info.getName()+" has "+addrs.length+" addresses"); - + Inet6Address v6Addrs[] = info.getInet6Addresses(); + + LimeLog.info("mDNS: "+info.getName()+" has "+addrs.length+" IPv4 addresses"); + LimeLog.info("mDNS: "+info.getName()+" has "+v6Addrs.length+" IPv6 addresses"); + + Inet6Address v6Addr = getBestIpv6Address(v6Addrs); + // Add a computer object for each IPv4 address reported by the PC - for (Inet4Address addr : addrs) { + for (Inet4Address v4Addr : addrs) { synchronized (computers) { - MdnsComputer computer = new MdnsComputer(info.getName(), addr); - if (computers.put(computer.getAddress(), computer) == null) { + MdnsComputer computer = new MdnsComputer(info.getName(), v4Addr, v6Addr); + if (computers.put(computer.getAddressV4(), computer) == null) { // This was a new entry listener.notifyComputerAdded(computer); }