Set the sin6_scope_id field when translating an Inet6Address to a sockaddr.

This basically means link-local IPv6 addresses have never worked on Android.

Change-Id: I343493cbbde1e4441385487f054c4bdad5dcef52
diff --git a/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java b/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java
index e98b41cd..8dc82e1 100644
--- a/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java
+++ b/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java
@@ -20,9 +20,12 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.DatagramPacket;
+import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.SocketImpl;
+import java.net.UnknownHostException;
 
 /**
  * This wraps native code that implements the INetworkSystem interface.
@@ -41,7 +44,22 @@
     public native void accept(FileDescriptor serverFd, SocketImpl newSocket,
             FileDescriptor clientFd) throws IOException;
 
-    public native void bind(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException;
+    public void bind(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException {
+        if (inetAddress instanceof Inet6Address && ((Inet6Address) inetAddress).getScopeId() == 0) {
+            // Linux won't let you bind a link-local address without a scope id. Find one.
+            NetworkInterface nif = NetworkInterface.getByInetAddress(inetAddress);
+            if (nif == null) {
+                throw new SocketException("Can't bind to a link-local address without a scope id: " + inetAddress);
+            }
+            try {
+                inetAddress = Inet6Address.getByAddress(inetAddress.getHostName(), inetAddress.getAddress(), nif.getIndex());
+            } catch (UnknownHostException ex) {
+                throw new AssertionError(ex); // Can't happen.
+            }
+        }
+        bindImpl(fd, inetAddress, port);
+    }
+    private native void bindImpl(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException;
 
     public native boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws IOException;
     public native boolean isConnected(FileDescriptor fd, int timeout) throws IOException;
diff --git a/luni/src/main/native/JniConstants.cpp b/luni/src/main/native/JniConstants.cpp
index 2681b47..afd438d 100644
--- a/luni/src/main/native/JniConstants.cpp
+++ b/luni/src/main/native/JniConstants.cpp
@@ -33,6 +33,7 @@
 jclass JniConstants::fieldPositionIteratorClass;
 jclass JniConstants::fileDescriptorClass;
 jclass JniConstants::gaiExceptionClass;
+jclass JniConstants::inet6AddressClass;
 jclass JniConstants::inetAddressClass;
 jclass JniConstants::inetSocketAddressClass;
 jclass JniConstants::inflaterClass;
@@ -83,6 +84,7 @@
     fieldPositionIteratorClass = findClass(env, "libcore/icu/NativeDecimalFormat$FieldPositionIterator");
     fileDescriptorClass = findClass(env, "java/io/FileDescriptor");
     gaiExceptionClass = findClass(env, "libcore/io/GaiException");
+    inet6AddressClass = findClass(env, "java/net/Inet6Address");
     inetAddressClass = findClass(env, "java/net/InetAddress");
     inetSocketAddressClass = findClass(env, "java/net/InetSocketAddress");
     inflaterClass = findClass(env, "java/util/zip/Inflater");
diff --git a/luni/src/main/native/JniConstants.h b/luni/src/main/native/JniConstants.h
index af5056b..8325208 100644
--- a/luni/src/main/native/JniConstants.h
+++ b/luni/src/main/native/JniConstants.h
@@ -55,6 +55,7 @@
     static jclass fieldPositionIteratorClass;
     static jclass fileDescriptorClass;
     static jclass gaiExceptionClass;
+    static jclass inet6AddressClass;
     static jclass inetAddressClass;
     static jclass inetSocketAddressClass;
     static jclass inflaterClass;
diff --git a/luni/src/main/native/NetworkUtilities.cpp b/luni/src/main/native/NetworkUtilities.cpp
index 26df02d..d33d626 100644
--- a/luni/src/main/native/NetworkUtilities.cpp
+++ b/luni/src/main/native/NetworkUtilities.cpp
@@ -26,40 +26,6 @@
 #include <string.h>
 #include <sys/socket.h>
 
-static bool byteArrayToSocketAddress(JNIEnv* env, jbyteArray byteArray, int port, sockaddr_storage* ss) {
-    if (byteArray == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return false;
-    }
-
-    // Convert the IP address bytes to the proper IP address type.
-    size_t addressLength = env->GetArrayLength(byteArray);
-    memset(ss, 0, sizeof(*ss));
-    if (addressLength == 4) {
-        // IPv4 address.
-        sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(ss);
-        sin->sin_family = AF_INET;
-        sin->sin_port = htons(port);
-        jbyte* dst = reinterpret_cast<jbyte*>(&sin->sin_addr.s_addr);
-        env->GetByteArrayRegion(byteArray, 0, 4, dst);
-    } else if (addressLength == 16) {
-        // IPv6 address.
-        sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(ss);
-        sin6->sin6_family = AF_INET6;
-        sin6->sin6_port = htons(port);
-        jbyte* dst = reinterpret_cast<jbyte*>(&sin6->sin6_addr.s6_addr);
-        env->GetByteArrayRegion(byteArray, 0, 16, dst);
-    } else {
-        // We can't throw SocketException. We aren't meant to see bad addresses, so seeing one
-        // really does imply an internal error.
-        // TODO: fix the code (native and Java) so we don't paint ourselves into this corner.
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                "byteArrayToSocketAddress bad array length (%i)", addressLength);
-        return false;
-    }
-    return true;
-}
-
 static jbyteArray socketAddressToByteArray(JNIEnv* env, const sockaddr_storage* ss) {
     // Convert IPv4-mapped addresses to IPv4 addresses.
     // The RI states "Java will never return an IPv4-mapped address".
@@ -123,6 +89,8 @@
 }
 
 bool inetAddressToSocketAddress(JNIEnv* env, jobject inetAddress, int port, sockaddr_storage* ss) {
+    memset(ss, 0, sizeof(*ss));
+
     // Get the byte array that stores the IP address bytes in the InetAddress.
     if (inetAddress == NULL) {
         jniThrowNullPointerException(env, NULL);
@@ -130,7 +98,38 @@
     }
     static jfieldID fid = env->GetFieldID(JniConstants::inetAddressClass, "ipaddress", "[B");
     jbyteArray addressBytes = reinterpret_cast<jbyteArray>(env->GetObjectField(inetAddress, fid));
-    return byteArrayToSocketAddress(env, addressBytes, port, ss);
+    if (addressBytes == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return false;
+    }
+
+    // Convert the IP address bytes to the appropriate kind of sockaddr.
+    size_t addressLength = env->GetArrayLength(addressBytes);
+    if (addressLength == 4) {
+        // IPv4 address.
+        sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(ss);
+        sin->sin_family = AF_INET;
+        sin->sin_port = htons(port);
+        jbyte* dst = reinterpret_cast<jbyte*>(&sin->sin_addr.s_addr);
+        env->GetByteArrayRegion(addressBytes, 0, 4, dst);
+        return true;
+    } else if (addressLength == 16) {
+        // IPv6 address.
+        sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(ss);
+        sin6->sin6_family = AF_INET6;
+        sin6->sin6_port = htons(port);
+        jbyte* dst = reinterpret_cast<jbyte*>(&sin6->sin6_addr.s6_addr);
+        env->GetByteArrayRegion(addressBytes, 0, 16, dst);
+        static jfieldID fid = env->GetFieldID(JniConstants::inet6AddressClass, "scope_id", "I");
+        sin6->sin6_scope_id = env->GetIntField(inetAddress, fid);
+        return true;
+    }
+
+    // We can't throw SocketException. We aren't meant to see bad addresses, so seeing one
+    // really does imply an internal error.
+    // TODO: fix the code (native and Java) so we don't paint ourselves into this corner.
+    jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "inetAddressToSocketAddress bad array length (%i)", addressLength);
+    return false;
 }
 
 bool setBlocking(int fd, bool blocking) {
diff --git a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp b/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
index d7208b1..b032700 100644
--- a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
+++ b/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
@@ -97,6 +97,9 @@
     const sockaddr* get() const {
         return mCompatibleAddress;
     }
+    socklen_t size() const {
+        return (mTmp.ss_family == AF_INET) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6);
+    }
 private:
     const sockaddr* mCompatibleAddress;
     sockaddr_storage mTmp;
@@ -183,7 +186,7 @@
 
     // Initiate a connection attempt...
     const CompatibleSocketAddress compatibleAddress(ss, true);
-    int rc = connect(fd.get(), compatibleAddress.get(), sizeof(sockaddr_storage));
+    int rc = connect(fd.get(), compatibleAddress.get(), compatibleAddress.size());
     int connectErrno = errno;
 
     // Did we get interrupted?
@@ -251,7 +254,7 @@
     return JNI_FALSE;
 }
 
-static void OSNetworkSystem_bind(JNIEnv* env, jobject, jobject fileDescriptor,
+static void OSNetworkSystem_bindImpl(JNIEnv* env, jobject, jobject fileDescriptor,
         jobject inetAddress, jint port) {
     sockaddr_storage ss;
     if (!inetAddressToSocketAddress(env, inetAddress, port, &ss)) {
@@ -264,7 +267,7 @@
     }
 
     const CompatibleSocketAddress compatibleAddress(ss, false);
-    int rc = TEMP_FAILURE_RETRY(bind(fd.get(), compatibleAddress.get(), sizeof(sockaddr_storage)));
+    int rc = TEMP_FAILURE_RETRY(bind(fd.get(), compatibleAddress.get(), compatibleAddress.size()));
     if (rc == -1) {
         jniThrowExceptionWithErrno(env, "java/net/BindException", errno);
     }
@@ -661,7 +664,7 @@
 
 static JNINativeMethod gMethods[] = {
     NATIVE_METHOD(OSNetworkSystem, accept, "(Ljava/io/FileDescriptor;Ljava/net/SocketImpl;Ljava/io/FileDescriptor;)V"),
-    NATIVE_METHOD(OSNetworkSystem, bind, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V"),
+    NATIVE_METHOD(OSNetworkSystem, bindImpl, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V"),
     NATIVE_METHOD(OSNetworkSystem, close, "(Ljava/io/FileDescriptor;)V"),
     NATIVE_METHOD(OSNetworkSystem, connect, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)Z"),
     NATIVE_METHOD(OSNetworkSystem, disconnectDatagram, "(Ljava/io/FileDescriptor;)V"),