Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Socket Connected even though it cannot

Firstly I know what your thinking, it's a strange question and it seems like it cannot be true but here me out on this one...

The project is a project which sends an array of bytes over a socket and also receives data from a socket. I am currently working on protection. I want to make sure that the user knows when the socket can't connect for whatever reason... You can view the connection code below.

So I have an android app which connects to a executable on a Windows Server computer in a office elsewhere in the world. The socket connects via the usual IP address and port number.

I am testing the app using a Android 5.0 (lollipop) phone...

Now enough of the boring stuff: If I close the server and turn it off completely and test this on wi-fi then the socket fails - which is correct. It cannot connect, and it will throw a SocketException. Which is fine and is working as expected. Now if I were to turn wi-fi off and use mobile data (I use o2 here in the sunny United Kingdom!) then an issue arises, using the same code, with the server still turned off, I do not throw a SocketException, instead the code simple thinks it has connected on a socket. The connection thread is below.

Thread StartConnection = new Thread()
        {
            @Override
            public void run()
            {

                int dstPort = 10600;

                socket = new Socket();
                try
                {
                    ipaddress = InetAddress.getByName(dstName);
                }
                catch (UnknownHostException uhe)
                {
                    uhe.printStackTrace();
                }

                j = new InetSocketAddress(IPString , dstPort);

                try
                {
                        socket.setKeepAlive(true);
                        socket.setReuseAddress(true);
                        socket.setTcpNoDelay(true);
                        socket.setSoTimeout(5000);
                        socket.connect(j, 5000);
                        connected = true;
                        outputstream = socket.getOutputStream();
                        DataThread();
                        SocketError = false;
                }
                catch (SocketException se)
                {
                    se.printStackTrace();
                }
                catch (IllegalArgumentException iae)
                {
                    iae.printStackTrace();
                }
                catch (IOException ioe)
                {
                    ioe.printStackTrace();
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        };
        StartConnection.start();

socket.connect(j, 5000); 

works fine no socket errors. No timeouts either.

outputstream = socket.getOutputStream(); 

also works. I get a result of "java.net.PlainSocketImpl$PlainSocketOutputStream@48e6f6e" which is also fine.

socket.getInputStream() returns "java.net.PlainSocketImpl$PlainSocketInputStream@1ea305a5"

like image 361
apmartin1991 Avatar asked Feb 23 '15 14:02

apmartin1991


1 Answers

This is interesting. I have been testing and can replicate the behaviour you describe.

Firstly, the highest voted answer here provides a good discussion on the Java Socket API:

Java socket API: How to tell if a connection has been closed?

From this, I wondered whether attempting to read a byte from the socket would allow us to check if it is really open

// Endpoint that does not exist
Socket socket = new Socket("1.2.3.4", 1234);
InputStream tmpIn = socket.getInputStream();
int result = tmpIn.read();
Log.i("SocketTest", "read() result: " + result);

In the case that the endpoint is invalid, I have found that the call to read() returns -1 after approximately 10 seconds.

In the case that there is a listener on the IP and port, the call to read() seems to block indefinitely until something happens, such as actually receiving data from the server, or losing internet connectivity etc.

Based on this difference, I have wrapped this test up into a new class:

public class VerifiedSocket extends Socket {

    public VerifiedSocket(String ip, int port, int millisecondTimeout) throws UnknownHostException, IOException{
        super(ip,port);

        ReadThread tryRead = new ReadThread();      
        tryRead.start();
        try {
            tryRead.join(millisecondTimeout);
        } catch (InterruptedException e) {
        }
        if(tryRead.getReadValue()==-1){
            // We read -1 so assume endpoint invalid
            throw new IOException("Endpoint is invalid");
        }       
    }

    private class ReadThread extends Thread{
        private int readValue = 512; // Impossible byte value

        @Override
        public void run(){              
            try {
                InputStream input = getInputStream();
                readValue = input.read();
            } catch (IOException e) {                   
            }               
        }

        public int getReadValue(){
            return readValue;
        }
    }       
}

And then tested it as follows:

try {
    VerifiedSocket socket = new VerifiedSocket("1.2.3.4", 1234, 20000);
    Log.i("SocketTest", "Endpoint is valid");
    socket.close();
} catch (UnknownHostException e) {      
    Log.e("SocketTest", e.getLocalizedMessage());
} catch (IOException e) {       
    Log.e("SocketTest", e.getLocalizedMessage());
}

If I use an invalid endpoint on Wifi, the constructor of the VerifiedSocket throws an exception with a timeout. If I use an invalid endpoint on mobile data, it throws the IOException with the 'Endpoint is invalid' message. Not perfect, but hopefully it might help you.

like image 135
Gary Wright Avatar answered Oct 08 '22 23:10

Gary Wright