diff options
author | Lorenzo Colitti <lorenzo@google.com> | 2015-03-16 11:12:51 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2015-03-16 11:12:51 +0000 |
commit | 1461a444bc7ef309a2499485199303e7e93bfdd0 (patch) | |
tree | 6dffba22b091cec3a22cf6b708cd3e35c9dd4c02 /luni | |
parent | ca97cdd527eceb0edb28a69f5769b3c7d701adff (diff) | |
parent | 4beb74e0ae832698c94fb298be6e122e70bebcaa (diff) | |
download | libcore-1461a444bc7ef309a2499485199303e7e93bfdd0.zip libcore-1461a444bc7ef309a2499485199303e7e93bfdd0.tar.gz libcore-1461a444bc7ef309a2499485199303e7e93bfdd0.tar.bz2 |
am 4beb74e0: am db419d06: Merge "Make it possible to use AF_INET sockets created by libcore."
* commit '4beb74e0ae832698c94fb298be6e122e70bebcaa':
Make it possible to use AF_INET sockets created by libcore.
Diffstat (limited to 'luni')
-rw-r--r-- | luni/src/main/native/libcore_io_Posix.cpp | 76 | ||||
-rw-r--r-- | luni/src/test/java/libcore/io/OsTest.java | 94 |
2 files changed, 144 insertions, 26 deletions
diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp index a1113ab..0f12e06 100644 --- a/luni/src/main/native/libcore_io_Posix.cpp +++ b/luni/src/main/native/libcore_io_Posix.cpp @@ -78,6 +78,52 @@ struct addrinfo_deleter { } }; +static bool isIPv4MappedAddress(const sockaddr *sa) { + const sockaddr_in6 *sin6 = reinterpret_cast<const sockaddr_in6*>(sa); + return sa != NULL && sa->sa_family == AF_INET6 && + (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) || + IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)); // We map 0.0.0.0 to ::, so :: is mapped. +} + +/** + * Perform a socket operation that specifies an IP address, possibly falling back from specifying + * the address as an IPv4-mapped IPv6 address in a struct sockaddr_in6 to specifying it as an IPv4 + * address in a struct sockaddr_in. + * + * This is needed because all sockets created by the java.net APIs are IPv6 sockets, and on those + * sockets, IPv4 operations use IPv4-mapped addresses stored in a struct sockaddr_in6. But sockets + * created using Posix.socket(AF_INET, ...) are IPv4 sockets and only support operations using IPv4 + * socket addresses structures. + */ +#define NET_IPV4_FALLBACK(jni_env, return_type, syscall_name, java_fd, java_addr, port, null_addr_ok, args...) ({ \ + return_type _rc = -1; \ + do { \ + sockaddr_storage _ss; \ + socklen_t _salen; \ + if (java_addr == NULL && null_addr_ok) { \ + /* No IP address specified (e.g., sendto() on a connected socket). */ \ + _salen = 0; \ + } else if (!inetAddressToSockaddr(jni_env, java_addr, port, _ss, _salen)) { \ + /* Invalid socket address, return -1. inetAddressToSockaddr has already thrown. */ \ + break; \ + } \ + sockaddr* _sa = _salen ? reinterpret_cast<sockaddr*>(&_ss) : NULL; \ + /* inetAddressToSockaddr always returns an IPv6 sockaddr. Assume that java_fd was created \ + * by Java API calls, which always create IPv6 socket fds, and pass it in as is. */ \ + _rc = NET_FAILURE_RETRY(jni_env, return_type, syscall_name, java_fd, ##args, _sa, _salen); \ + if (_rc == -1 && errno == EAFNOSUPPORT && _salen && isIPv4MappedAddress(_sa)) { \ + /* We passed in an IPv4 address in an IPv6 sockaddr and the kernel told us that we got \ + * the address family wrong. Pass in the same address in an IPv4 sockaddr. */ \ + jni_env->ExceptionClear(); \ + if (!inetAddressToSockaddrVerbatim(jni_env, java_addr, port, _ss, _salen)) { \ + break; \ + } \ + _sa = reinterpret_cast<sockaddr*>(&_ss); \ + _rc = NET_FAILURE_RETRY(jni_env, return_type, syscall_name, java_fd, ##args, _sa, _salen); \ + } \ + } while (0); \ + _rc; }) \ + /** * Used to retry networking system calls that can be interrupted with a signal. Unlike * TEMP_FAILURE_RETRY, this also handles the case where @@ -149,6 +195,9 @@ struct addrinfo_deleter { } while (_rc == -1); /* && _syscallErrno == EINTR && !_wasSignaled */ \ _rc; }) +#define NULL_ADDR_OK true +#define NULL_ADDR_FORBIDDEN false + static void throwException(JNIEnv* env, jclass exceptionClass, jmethodID ctor3, jmethodID ctor2, const char* functionName, int error) { jthrowable cause = NULL; @@ -532,14 +581,8 @@ static jboolean Posix_access(JNIEnv* env, jobject, jstring javaPath, jint mode) } static void Posix_bind(JNIEnv* env, jobject, jobject javaFd, jobject javaAddress, jint port) { - sockaddr_storage ss; - socklen_t sa_len; - if (!inetAddressToSockaddr(env, javaAddress, port, ss, sa_len)) { - return; - } - const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss); // We don't need the return value because we'll already have thrown. - (void) NET_FAILURE_RETRY(env, int, bind, javaFd, sa, sa_len); + (void) NET_IPV4_FALLBACK(env, int, bind, javaFd, javaAddress, port, NULL_ADDR_FORBIDDEN); } static void Posix_bindSocketAddress( @@ -584,14 +627,7 @@ static void Posix_close(JNIEnv* env, jobject, jobject javaFd) { } static void Posix_connect(JNIEnv* env, jobject, jobject javaFd, jobject javaAddress, jint port) { - sockaddr_storage ss; - socklen_t sa_len; - if (!inetAddressToSockaddr(env, javaAddress, port, ss, sa_len)) { - return; - } - const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss); - // We don't need the return value because we'll already have thrown. - (void) NET_FAILURE_RETRY(env, int, connect, javaFd, sa, sa_len); + (void) NET_IPV4_FALLBACK(env, int, connect, javaFd, javaAddress, port, NULL_ADDR_FORBIDDEN); } static void Posix_connectSocketAddress( @@ -1360,13 +1396,9 @@ static jint Posix_sendtoBytes(JNIEnv* env, jobject, jobject javaFd, jobject java if (bytes.get() == NULL) { return -1; } - sockaddr_storage ss; - socklen_t sa_len = 0; - if (javaInetAddress != NULL && !inetAddressToSockaddr(env, javaInetAddress, port, ss, sa_len)) { - return -1; - } - const sockaddr* to = (javaInetAddress != NULL) ? reinterpret_cast<const sockaddr*>(&ss) : NULL; - return NET_FAILURE_RETRY(env, ssize_t, sendto, javaFd, bytes.get() + byteOffset, byteCount, flags, to, sa_len); + + return NET_IPV4_FALLBACK(env, ssize_t, sendto, javaFd, javaInetAddress, port, + NULL_ADDR_OK, bytes.get() + byteOffset, byteCount, flags); } static void Posix_setegid(JNIEnv* env, jobject, jint egid) { diff --git a/luni/src/test/java/libcore/io/OsTest.java b/luni/src/test/java/libcore/io/OsTest.java index c0a18b9..e6c946c 100644 --- a/luni/src/test/java/libcore/io/OsTest.java +++ b/luni/src/test/java/libcore/io/OsTest.java @@ -16,12 +16,15 @@ package libcore.io; +import android.system.ErrnoException; import android.system.NetlinkSocketAddress; import android.system.StructUcred; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.InetUnixAddress; @@ -29,6 +32,7 @@ import java.net.ServerSocket; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Locale; import junit.framework.TestCase; import static android.system.OsConstants.*; @@ -211,9 +215,10 @@ public class OsTest extends TestCase { fis.close(); } - public void test_byteBufferPositions_sendto_recvfrom() throws Exception { - final FileDescriptor serverFd = Libcore.os.socket(AF_INET6, SOCK_STREAM, 0); - Libcore.os.bind(serverFd, InetAddress.getLoopbackAddress(), 0); + static void checkByteBufferPositions_sendto_recvfrom( + int family, InetAddress loopback) throws Exception { + final FileDescriptor serverFd = Libcore.os.socket(family, SOCK_STREAM, 0); + Libcore.os.bind(serverFd, loopback, 0); Libcore.os.listen(serverFd, 5); InetSocketAddress address = (InetSocketAddress) Libcore.os.getsockname(serverFd); @@ -247,7 +252,7 @@ public class OsTest extends TestCase { server.start(); - FileDescriptor clientFd = Libcore.os.socket(AF_INET6, SOCK_STREAM, 0); + FileDescriptor clientFd = Libcore.os.socket(family, SOCK_STREAM, 0); Libcore.os.connect(clientFd, address.getAddress(), address.getPort()); final byte[] bytes = "good bye, cruel black hole with fancy distortion".getBytes(StandardCharsets.US_ASCII); @@ -284,4 +289,85 @@ public class OsTest extends TestCase { assertEquals(0, nlPeer.getGroupsMask()); Libcore.os.close(nlSocket); } + + public void test_byteBufferPositions_sendto_recvfrom_af_inet() throws Exception { + checkByteBufferPositions_sendto_recvfrom(AF_INET, InetAddress.getByName("127.0.0.1")); + } + + public void test_byteBufferPositions_sendto_recvfrom_af_inet6() throws Exception { + checkByteBufferPositions_sendto_recvfrom(AF_INET6, InetAddress.getByName("::1")); + } + + public void test_socketFamilies() throws Exception { + FileDescriptor fd = Libcore.os.socket(AF_INET6, SOCK_STREAM, 0); + Libcore.os.bind(fd, InetAddress.getByName("::"), 0); + InetSocketAddress localSocketAddress = (InetSocketAddress) Libcore.os.getsockname(fd); + assertEquals(Inet6Address.ANY, localSocketAddress.getAddress()); + + fd = Libcore.os.socket(AF_INET6, SOCK_STREAM, 0); + Libcore.os.bind(fd, InetAddress.getByName("0.0.0.0"), 0); + localSocketAddress = (InetSocketAddress) Libcore.os.getsockname(fd); + assertEquals(Inet6Address.ANY, localSocketAddress.getAddress()); + + fd = Libcore.os.socket(AF_INET, SOCK_STREAM, 0); + Libcore.os.bind(fd, InetAddress.getByName("0.0.0.0"), 0); + localSocketAddress = (InetSocketAddress) Libcore.os.getsockname(fd); + assertEquals(Inet4Address.ANY, localSocketAddress.getAddress()); + try { + Libcore.os.bind(fd, InetAddress.getByName("::"), 0); + fail("Expected ErrnoException binding IPv4 socket to ::"); + } catch (ErrnoException expected) { + assertEquals("Expected EAFNOSUPPORT binding IPv4 socket to ::", EAFNOSUPPORT, expected.errno); + } + } + + private static void assertArrayEquals(byte[] expected, byte[] actual) { + assertTrue("Expected=" + Arrays.toString(expected) + ", actual=" + Arrays.toString(actual), + Arrays.equals(expected, actual)); + } + + private static void checkSocketPing(FileDescriptor fd, InetAddress to, byte[] packet, + byte type, byte responseType, boolean useSendto) throws Exception { + int len = packet.length; + packet[0] = type; + if (useSendto) { + assertEquals(len, Libcore.os.sendto(fd, packet, 0, len, 0, to, 0)); + } else { + Libcore.os.connect(fd, to, 0); + assertEquals(len, Libcore.os.sendto(fd, packet, 0, len, 0, null, 0)); + } + + int icmpId = ((InetSocketAddress) Libcore.os.getsockname(fd)).getPort(); + byte[] received = new byte[4096]; + InetSocketAddress srcAddress = new InetSocketAddress(); + assertEquals(len, Libcore.os.recvfrom(fd, received, 0, received.length, 0, srcAddress)); + assertEquals(to, srcAddress.getAddress()); + assertEquals(responseType, received[0]); + assertEquals(received[4], (byte) (icmpId >> 8)); + assertEquals(received[5], (byte) (icmpId & 0xff)); + + received = Arrays.copyOf(received, len); + received[0] = (byte) type; + received[2] = received[3] = 0; // Checksum. + received[4] = received[5] = 0; // ICMP ID. + assertArrayEquals(packet, received); + } + + public void test_socketPing() throws Exception { + final byte ICMP_ECHO = 8, ICMP_ECHOREPLY = 0; + final byte ICMPV6_ECHO_REQUEST = (byte) 128, ICMPV6_ECHO_REPLY = (byte) 129; + final byte[] packet = ("\000\000\000\000" + // ICMP type, code. + "\000\000\000\003" + // ICMP ID (== port), sequence number. + "Hello myself").getBytes(StandardCharsets.US_ASCII); + + FileDescriptor fd = Libcore.os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); + InetAddress ipv6Loopback = InetAddress.getByName("::1"); + checkSocketPing(fd, ipv6Loopback, packet, ICMPV6_ECHO_REQUEST, ICMPV6_ECHO_REPLY, true); + checkSocketPing(fd, ipv6Loopback, packet, ICMPV6_ECHO_REQUEST, ICMPV6_ECHO_REPLY, false); + + fd = Libcore.os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); + InetAddress ipv4Loopback = InetAddress.getByName("127.0.0.1"); + checkSocketPing(fd, ipv4Loopback, packet, ICMP_ECHO, ICMP_ECHOREPLY, true); + checkSocketPing(fd, ipv4Loopback, packet, ICMP_ECHO, ICMP_ECHOREPLY, false); + } } |