JDK-6595834 : InetAddress.isReachable is not thread safe when using ICMP ECHO.
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.net
  • Affected Version: 6,6u2
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: linux_redhat_9.0,solaris_2.5.1
  • CPU: x86
  • Submitted: 2007-08-22
  • Updated: 2011-05-18
  • Resolved: 2011-05-18
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7
7 b20Fixed
Related Reports
Duplicate :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.6.0_01"
Java(TM) SE Runtime Environment (build 1.6.0_01-b06)
Java HotSpot(TM) Client VM (build 1.6.0_01-b06, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Linux name 2.6.9-42.EL #1 Sat Aug 12 09:17:58 CDT 2006 i686 i686 i386 GNU/Linux

EXTRA RELEVANT SYSTEM CONFIGURATION :
dual-homed machine (but that shouldn't matter).
must run as root.

A DESCRIPTION OF THE PROBLEM :
The return value of InetAddress.isReachable() may represent the results of another thread's call to isReachable.  This only happens if isReachable uses the ICMP ECHO method to gather its results; this means the application must be run as root on linux.  The problem does not exist on Windows.

Thread-safety of InetAddress and isReachable is not documented.  However, another defect in the Sun database states that InetAddress is thread safe because it's immutable.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
See the attached test code.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
is 192.0.2.250 reachable?
is 127.0.0.1 reachable?
is 192.0.2.251 reachable?
is 192.0.2.252 reachable?
192.0.2.250 not reachable on loop 0
192.0.2.250 not reachable on loop 1
192.0.2.250 not reachable on loop 2
192.0.2.252 not reachable on loop 0
192.0.2.252 not reachable on loop 1
192.0.2.252 not reachable on loop 2
192.0.2.251 not reachable on loop 0
192.0.2.251 not reachable on loop 1
192.0.2.251 not reachable on loop 2
127.0.0.1 exists!
Finished!

ACTUAL -
sudo java -cp . Ping 192.0.2.250 127.0.0.1 192.0.2.251 192.0.2.252

is 192.0.2.250 reachable?
is 127.0.0.1 reachable?
127.0.0.1 exists!
is 192.0.2.251 reachable?
192.0.2.250 exists!
is 192.0.2.252 reachable?
192.0.2.251 not reachable on loop 0
192.0.2.252 not reachable on loop 0
192.0.2.251 not reachable on loop 1
192.0.2.252 not reachable on loop 1
192.0.2.251 not reachable on loop 2
192.0.2.252 not reachable on loop 2
Finished!


(Notice that 192.0.2.250 is reported as existing but it does not).

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.net.*;
import java.io.IOException;
import java.util.concurrent.*;
import java.util.*;

/**
 * Test program to prove that InetAddress.isReachableWrapper() is not thread safe when ICMP ECHO is used.
 * Run this test with 1 IP address that DOES exist on your network and N IP addresses that do NOT exist.
 * This program will then falsely indicate that they were "reachable".
 * To run:
 *     java -cp . Ping <ip1> <ip2> <ip3>
 *
 * NOTE: must run as root to use ICMP ECHO (otherwise this test is not valid).
 * This defect does not affect Windows because ICMP ECHO is not used.
 * Remember, at least one IP address must be reachable to trigger this defect and is should be listed 2nd, or 3rd.
 *
 * Work-arounds:
 * - synchronize the isReachableWrapper method (or at least the call to 'isReachable'.
 * - force isReachable to not use ICMP (I'm not sure how to force this).
 */
public class Ping {
    private static ExecutorService executor = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {
        Collection<Callable<PingDevice>> tasks = new ArrayList<Callable<PingDevice>>();
        try {
            for (String ip : args) {
                tasks.add(new PingDevice(ip));
            }
            executor.invokeAll(tasks); // blocks until complete
            System.out.println("Finished!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executor.shutdownNow();
        }

    }

    public static boolean isReachableWrapper(String ip, int retries, int timeout) {
        System.out.println("is " + ip + " reachable?");
        InetAddress address = null;
        try {
            address = InetAddress.getByName(ip);
        } catch (UnknownHostException e) {
            System.out.println("Unable to lookup " + ip);
            return false;
        }
        for (int i = 0; i < retries + 1; i++) {
            try {
                //synchronized(InetAddress.class) { // required to work-around bug.
                if (address.isReachable(timeout)) {
                    System.out.println(ip + " exists!");
                    return true;
                }
                //}
                System.out.println(ip + " not reachable on loop " + i);
            } catch (IOException e) {
                //e.printStackTrace();
                System.out.println("IOE: Unable to reach " + ip + " on try " + i);
            }
        }
        return false;
    }

    private static class PingDevice implements Callable<PingDevice> {
        String ip;
        boolean pingable = false;

        public PingDevice(String ip) {
            this.ip = ip;
        }

        public PingDevice call() throws Exception {
            try {
                //System.out.println("lookup " + ip);
                InetAddress addr = InetAddress.getByName(ip);
                // try pinging 3 times, waiting up to .5 second for each ping.
                pingable = isReachableWrapper(addr.getHostAddress(), 2, 500);
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
            return this;
        }
    }
}


---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
I have created my own synchronized wrapper method for isReachable.  Of course, everything within an application must be sure to call the wrapper method.

Comments
EVALUATION It was later found that this fix did not make InetAddress.isReachable 100% thread safe. For example, even with this fix it is possible that the sequence number (16bit) used in the implementation of the isReachable call with a large timeout, be repeated by another call. This will lead to incorrectly determining that a host is reachable, when it may in fact be unreachable. A complete fix for this problem was done under CR 6599750.
06-09-2007

EVALUATION The raw socket sees every ICMP reply on the interface, therefore 2 or more threads blocked in a recvfrom on a raw socket will see the ICMP ECHO REPLY. The pid of course is the same, so we need to differentiate between requests/reply pairs. I have used the sequence number.
23-08-2007

EVALUATION The description is indeed true. We need to differentiate between request/reply pairs.
23-08-2007