blob: 1edfe344cea10992af58ecaf97b476c85104a20e [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.net;
import android.system.ErrnoException;
import android.system.GaiException;
import android.system.StructAddrinfo;
import android.system.StructIcmpHdr;
import dalvik.system.BlockGuard;
import libcore.io.IoBridge;
import libcore.io.Libcore;
import java.io.FileDescriptor;
import java.io.IOException;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import static android.system.OsConstants.AF_UNSPEC;
import static android.system.OsConstants.AI_ADDRCONFIG;
import static android.system.OsConstants.EACCES;
import static android.system.OsConstants.ECONNREFUSED;
import static android.system.OsConstants.NI_NAMEREQD;
import static android.system.OsConstants.ICMP6_ECHO_REPLY;
import static android.system.OsConstants.ICMP_ECHOREPLY;
import static android.system.OsConstants.IPPROTO_ICMP;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOCK_STREAM;
// Android-note: Android-specific behavior and Linux-based implementation
// http://b/36933260 Implement root-less ICMP for isReachable()
// http://b/28609551 Rewrite getHostByAddr0 using POSIX library Libcore.os.
// http://b/25861497 Add BlockGuard checks.
// http://b/26700324 Fix odd dependency chains of the static InetAddress.
// anyLocalAddress() Let anyLocalAddress() always return an IPv6 address.
// Let loopbackAddresses() return both Inet4 and Inet6 loopbacks.
// Rewrote hostname lookup methods on top of Libcore.os. Merge implementation from InetAddress
// and remove native methods in this class
/*
* Package private implementation of InetAddressImpl for dual
* IPv4/IPv6 stack. {@code #anyLocalAddress()} will always return an IPv6 address.
*
* @since 1.4
*/
class Inet6AddressImpl implements InetAddressImpl {
// @GuardedBy(Inet6AddressImpl.class)
private static InetAddress anyLocalAddress;
// @GuardedBy(Inet6AddressImpl.class)
private static InetAddress[] loopbackAddresses;
private static final AddressCache addressCache = new AddressCache();
// BEGIN Android-changed: Rewrote hostname lookup methods on top of Libcore.os.
/*
public native String getLocalHostName() throws UnknownHostException;
public native InetAddress[]
lookupAllHostAddr(String hostname) throws UnknownHostException;
public native String getHostByAddr(byte[] addr) throws UnknownHostException;
private native boolean isReachable0(byte[] addr, int scope, int timeout, byte[] inf, int ttl, int if_scope) throws IOException;
*/
@Override
public InetAddress[] lookupAllHostAddr(String host, int netId) throws UnknownHostException {
if (host == null || host.isEmpty()) {
// Android-changed: Return both the Inet4 and Inet6 loopback addresses
// when host == null or empty.
return loopbackAddresses();
}
// Is it a numeric address?
InetAddress result = InetAddress.parseNumericAddressNoThrow(host);
if (result != null) {
result = InetAddress.disallowDeprecatedFormats(host, result);
if (result == null) {
throw new UnknownHostException("Deprecated IPv4 address format: " + host);
}
return new InetAddress[] { result };
}
return lookupHostByName(host, netId);
}
/**
* Resolves a hostname to its IP addresses using a cache.
*
* @param host the hostname to resolve.
* @param netId the network to perform resolution upon.
* @return the IP addresses of the host.
*/
private static InetAddress[] lookupHostByName(String host, int netId)
throws UnknownHostException {
BlockGuard.getThreadPolicy().onNetwork();
// Do we have a result cached?
Object cachedResult = addressCache.get(host, netId);
if (cachedResult != null) {
if (cachedResult instanceof InetAddress[]) {
// A cached positive result.
return (InetAddress[]) cachedResult;
} else {
// A cached negative result.
throw new UnknownHostException((String) cachedResult);
}
}
try {
StructAddrinfo hints = new StructAddrinfo();
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_family = AF_UNSPEC;
// If we don't specify a socket type, every address will appear twice, once
// for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
// anyway, just pick one.
hints.ai_socktype = SOCK_STREAM;
InetAddress[] addresses = Libcore.os.android_getaddrinfo(host, hints, netId);
// TODO: should getaddrinfo set the hostname of the InetAddresses it returns?
for (InetAddress address : addresses) {
address.holder().hostName = host;
address.holder().originalHostName = host;
}
addressCache.put(host, netId, addresses);
return addresses;
} catch (GaiException gaiException) {
// If the failure appears to have been a lack of INTERNET permission, throw a clear
// SecurityException to aid in debugging this common mistake.
// http://code.google.com/p/android/issues/detail?id=15722
if (gaiException.getCause() instanceof ErrnoException) {
if (((ErrnoException) gaiException.getCause()).errno == EACCES) {
throw new SecurityException("Permission denied (missing INTERNET permission?)", gaiException);
}
}
// Otherwise, throw an UnknownHostException.
String detailMessage = "Unable to resolve host \"" + host + "\": " + Libcore.os.gai_strerror(gaiException.error);
addressCache.putUnknownHost(host, netId, detailMessage);
throw gaiException.rethrowAsUnknownHostException(detailMessage);
}
}
@Override
public String getHostByAddr(byte[] addr) throws UnknownHostException {
BlockGuard.getThreadPolicy().onNetwork();
return getHostByAddr0(addr);
}
@Override
public void clearAddressCache() {
addressCache.clear();
}
// END Android-changed: Rewrote hostname lookup methods on top of Libcore.os.
@Override
public boolean isReachable(InetAddress addr, int timeout, NetworkInterface netif, int ttl) throws IOException {
// Android-changed: rewritten on the top of IoBridge and Libcore.os.
InetAddress sourceAddr = null;
if (netif != null) {
/*
* Let's make sure we bind to an address of the proper family.
* Which means same family as addr because at this point it could
* be either an IPv6 address or an IPv4 address (case of a dual
* stack system).
*/
java.util.Enumeration<InetAddress> it = netif.getInetAddresses();
InetAddress inetaddr = null;
while (it.hasMoreElements()) {
inetaddr = it.nextElement();
if (inetaddr.getClass().isInstance(addr)) {
sourceAddr = inetaddr;
break;
}
}
if (sourceAddr == null) {
// Interface doesn't support the address family of
// the destination
return false;
}
}
// Android-changed: http://b/36933260 Implement root-less ICMP for isReachable().
/*
if (addr instanceof Inet6Address)
scope = ((Inet6Address) addr).getScopeId();
return isReachable0(addr.getAddress(), scope, timeout, ifaddr, ttl, netif_scope);
*/
// Try ICMP first
if (icmpEcho(addr, timeout, sourceAddr, ttl)) {
return true;
}
// No good, let's fall back to TCP
return tcpEcho(addr, timeout, sourceAddr, ttl);
}
// BEGIN Android-added: http://b/36933260 Implement root-less ICMP for isReachable().
private boolean tcpEcho(InetAddress addr, int timeout, InetAddress sourceAddr, int ttl)
throws IOException {
FileDescriptor fd = null;
try {
fd = IoBridge.socket(AF_INET6, SOCK_STREAM, 0);
if (ttl > 0) {
IoBridge.setSocketOption(fd, IoBridge.JAVA_IP_TTL, ttl);
}
if (sourceAddr != null) {
IoBridge.bind(fd, sourceAddr, 0);
}
IoBridge.connect(fd, addr, 7 /* Echo-protocol port */, timeout);
return true;
} catch (IOException e) {
// Connection refused by remote (ECONNREFUSED) implies reachable. Otherwise silently
// ignore the exception and return false.
Throwable cause = e.getCause();
return cause instanceof ErrnoException
&& ((ErrnoException) cause).errno == ECONNREFUSED;
} finally {
IoBridge.closeAndSignalBlockedThreads(fd);
}
}
protected boolean icmpEcho(InetAddress addr, int timeout, InetAddress sourceAddr, int ttl)
throws IOException {
FileDescriptor fd = null;
try {
boolean isIPv4 = addr instanceof Inet4Address;
int domain = isIPv4 ? AF_INET : AF_INET6;
int icmpProto = isIPv4 ? IPPROTO_ICMP : IPPROTO_ICMPV6;
fd = IoBridge.socket(domain, SOCK_DGRAM, icmpProto);
if (ttl > 0) {
IoBridge.setSocketOption(fd, IoBridge.JAVA_IP_TTL, ttl);
}
if (sourceAddr != null) {
IoBridge.bind(fd, sourceAddr, 0);
}
byte[] packet;
// ICMP is unreliable, try sending requests every second until timeout.
for (int to = timeout, seq = 0; to > 0; ++seq) {
int sockTo = to >= 1000 ? 1000 : to;
IoBridge.setSocketOption(fd, SocketOptions.SO_TIMEOUT, sockTo);
packet = StructIcmpHdr.IcmpEchoHdr(isIPv4, seq).getBytes();
IoBridge.sendto(fd, packet, 0, packet.length, 0, addr, 0);
final int icmpId = IoBridge.getLocalInetSocketAddress(fd).getPort();
byte[] received = new byte[packet.length];
DatagramPacket receivedPacket = new DatagramPacket(received, packet.length);
int size = IoBridge
.recvfrom(true, fd, received, 0, received.length, 0, receivedPacket, false);
if (size == packet.length) {
byte expectedType = isIPv4 ? (byte) ICMP_ECHOREPLY
: (byte) ICMP6_ECHO_REPLY;
if (receivedPacket.getAddress().equals(addr)
&& received[0] == expectedType
&& received[4] == (byte) (icmpId >> 8)
&& received[5] == (byte) icmpId
&& received[6] == (byte) (seq >> 8)
&& received[7] == (byte) seq) {
// This is the packet we're expecting.
return true;
}
}
to -= sockTo;
}
} catch (IOException e) {
// Silently ignore and fall back.
} finally {
if (fd != null) {
try {
Libcore.os.close(fd);
} catch (ErrnoException e) { }
}
}
return false;
}
// END Android-added: http://b/36933260 Implement root-less ICMP for isReachable().
// BEGIN Android-changed: Let anyLocalAddress() always return an IPv6 address.
@Override
public InetAddress anyLocalAddress() {
synchronized (Inet6AddressImpl.class) {
// We avoid initializing anyLocalAddress during <clinit> to avoid issues
// caused by the dependency chains of these classes. InetAddress depends on
// InetAddressImpl, but Inet6Address & Inet4Address are its subclasses.
// Also see {@code loopbackAddresses). http://b/26700324
if (anyLocalAddress == null) {
Inet6Address anyAddress = new Inet6Address();
anyAddress.holder().hostName = "::";
anyLocalAddress = anyAddress;
}
return anyLocalAddress;
}
}
// END Android-changed: Let anyLocalAddress() always return an IPv6 address.
// BEGIN Android-changed: Let loopbackAddresses() return both Inet4 and Inet6 loopbacks.
@Override
public InetAddress[] loopbackAddresses() {
synchronized (Inet6AddressImpl.class) {
// We avoid initializing anyLocalAddress during <clinit> to avoid issues
// caused by the dependency chains of these classes. InetAddress depends on
// InetAddressImpl, but Inet6Address & Inet4Address are its subclasses.
// Also see {@code anyLocalAddress).
if (loopbackAddresses == null) {
loopbackAddresses = new InetAddress[]{Inet6Address.LOOPBACK, Inet4Address.LOOPBACK};
}
return loopbackAddresses;
}
}
// END Android-changed: Let loopbackAddresses() return both Inet4 and Inet6 loopbacks.
// BEGIN Android-changed: b/28609551 Rewrite getHostByAddr0 using POSIX library Libcore.os.
private String getHostByAddr0(byte[] addr) throws UnknownHostException {
// Android-changed: Rewritten on the top of Libcore.os
InetAddress hostaddr = InetAddress.getByAddress(addr);
try {
return Libcore.os.getnameinfo(hostaddr, NI_NAMEREQD);
} catch (GaiException e) {
UnknownHostException uhe = new UnknownHostException(hostaddr.toString());
uhe.initCause(e);
throw uhe;
}
}
// END Android-changed: b/28609551 Rewrite getHostByAddr0 using POSIX library Libcore.os.
}