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"),