src/java.base/share/classes/sun/net/util/IPAddressUtil.java
changeset 55375 96c7427456f9
parent 52499 768b1c612100
child 55711 0c143aaa2c99
--- a/src/java.base/share/classes/sun/net/util/IPAddressUtil.java	Thu Jun 13 12:22:28 2019 +0530
+++ b/src/java.base/share/classes/sun/net/util/IPAddressUtil.java	Thu Jun 13 09:10:51 2019 +0100
@@ -25,6 +25,20 @@
 
 package sun.net.util;
 
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+import java.security.PrivilegedActionException;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
 public class IPAddressUtil {
     private static final int INADDR4SZ = 4;
     private static final int INADDR16SZ = 16;
@@ -287,4 +301,75 @@
         }
         return false;
     }
+    /**
+     * Mapping from unscoped local Inet(6)Address to the same address
+     * including the correct scope-id, determined from NetworkInterface.
+     */
+    private final static ConcurrentHashMap<InetAddress,InetAddress>
+        cache = new ConcurrentHashMap<>();
+
+    /**
+     * Returns a scoped version of the supplied local, link-local ipv6 address
+     * if that scope-id can be determined from local NetworkInterfaces.
+     * If the address already has a scope-id or if the address is not local, ipv6
+     * or link local, then the original address is returned.
+     *
+     * @param addr
+     * @exception SocketException if the given ipv6 link local address is found
+     *            on more than one local interface
+     * @return
+     */
+    public static InetAddress toScopedAddress(InetAddress address)
+        throws SocketException {
+
+        if (address instanceof Inet6Address && address.isLinkLocalAddress()
+            && ((Inet6Address) address).getScopeId() == 0) {
+
+            InetAddress cached = null;
+            try {
+                cached = cache.computeIfAbsent(address, k -> findScopedAddress(k));
+            } catch (UncheckedIOException e) {
+                throw (SocketException)e.getCause();
+            }
+            return cached != null ? cached : address;
+        } else {
+            return address;
+        }
+    }
+
+    /**
+     * Same as above for InetSocketAddress
+     */
+    public static InetSocketAddress toScopedAddress(InetSocketAddress address)
+        throws SocketException {
+        InetAddress addr;
+        InetAddress orig = address.getAddress();
+        if ((addr = toScopedAddress(orig)) == orig) {
+            return address;
+        } else {
+            return new InetSocketAddress(addr, address.getPort());
+        }
+    }
+
+    private static InetAddress findScopedAddress(InetAddress address) {
+        PrivilegedExceptionAction<List<InetAddress>> pa = () -> NetworkInterface.networkInterfaces()
+                .flatMap(NetworkInterface::inetAddresses)
+                .filter(a -> (a instanceof Inet6Address)
+                        && address.equals(a)
+                        && ((Inet6Address) a).getScopeId() != 0)
+                .collect(Collectors.toList());
+        List<InetAddress> result;
+        try {
+            result = AccessController.doPrivileged(pa);
+            var sz = result.size();
+            if (sz == 0)
+                return null;
+            if (sz > 1)
+                throw new UncheckedIOException(new SocketException(
+                    "Duplicate link local addresses: must specify scope-id"));
+            return result.get(0);
+        } catch (PrivilegedActionException pae) {
+            return null;
+        }
+    }
 }