-1

I am writing a Java application that opens a Socket, as follows:

socketConnection = new Socket();
socketConnection.connect(new InetSocketAddress(server, port));

and occasionally performs operations such as reads and writes. One function that is important for sending meta data along with packets is the local and remote addresses of the connection. For example, I am getting the local address bytes as:

public byte[] getLocalIP() {
        InetAddress localAddr = socketConnection.getLocalAddress();
        byte[] addressBytes = localAddr.getAddress();
        return addressBytes;
    }

My protocol would like to have the IPv4 of the sender sent along in the header. However, sometimes this function returns 16 bytes instead of 4, which causes problems. Even more confusing, the behavior sometimes changes within the same run of the program, despite the same Socket object returning IPv4 for previous calls. It is difficult to replicate, I'm still not sure under what circumstances it happens.

Under what circumstances will the above return an IPv6 instead of an IPv4? Is this dependent on the network I'm running on? And what would cause it to shift during the middle of a program execution?

  • 1
    It should be pretty obvious what condition is causing this - you are creating an IPv6 socket that is bound to an IPv6 address (using `Inet6Address`), instead of creating an IPv4 socket bound to an IPv4 address (using `Inet4Address`). How are you creating and configuring `socketConnection` in the first place? Is this client-side or server-side? Please provide a [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve). If your protocol does not support IPv6-based metadata, then don't create an IPv6 socket to begin with. – Remy Lebeau Aug 04 '16 at 18:10
  • I added an example of how I initialize the connection. I also clarified that the same `Socket` object will sometimes return 4 bytes vs 16 bytes during a single run of the program after the object has been initialized and connected. – Matthew G Dippel Aug 04 '16 at 18:21
  • 2
    It is *PHYSICALLY IMPOSSIBLE* for a connected `Socket` to return both IPv4 and IPv6 addresses, or to return different IPv4/IPv6 addresses at different times. It can only be bound to one address at a time, and that address is persistent for the lifetime of the connection. If you are seeing different results, you *HAVE* to be doing something wrong in your code, but you have not provided a [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) to diagnose what that something could be. – Remy Lebeau Aug 04 '16 at 20:20
  • I am voting to close this question as off-topic unless you can provide a [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) that demonstrates the problem in action. – Remy Lebeau Aug 04 '16 at 20:22
  • Your protocol doesn't need the address of the sender. It is already available to the receiver via `Socket.getRemoteAddress()` and friends. It is also constant for the life of a TCP connection. Unclear what the point of all this is. – user207421 Aug 04 '16 at 23:54

2 Answers2

1

You are creating a Socket object without explicitly binding it locally to an IPv4 or IPv6 address, so it will create and bind an internal IPv4 or IPv6 socket based on whether you connect() to the remote server using an IPv4 or IPv6 address.

If server is an InetAddress, the IP version will be based on whether you use an Inet4Address or Inet6Address.

But if server is a string, it has to be resolved to an InetAddress, and that resolution will depend on whether the string contains a literal IPv4 address, a literal IPv6 address, or a hostname. In the case of literal addresses, the answer is obvious - a literal IPv4 address will resolve into an Inet4Address and a literal IPv6 address will resolve into an Inet6Address. But a hostname can go either way, depending on the result of DNS lookups.

If server is a hostname, and you want to restrict the result to IPv4 only, call InetAddress.getAllByName() directly and loop through the resulting array (a hostname may have multiple IP addresses assigned to it), connect()'ing only to Inet4Address addresses until one of them succeeds:

socketConnection = null;

for(InetAddress addr : InetAddress.getAllByName(server))
{
    if (addr instanceof Inet4Address)
    {
        socketConnection = new Socket();
        try {
            socketConnection.connect(new InetSocketAddress(addr, port));
            break;
        }
        catch (IOException e) {
            socketConnection = null;
        }
    }
}

if (socketConnection == null) {
    throw new ConnectException("Cannot connect to '" + host + "' using IPv4");
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Okay, but then why would `getLocalAddress()` change between an IPv4 and an IPv6 for the same `Socket` object after I have already connected and been sending / receiving messages? This is the behavior I am trying to find an explanation for. – Matthew G Dippel Aug 04 '16 at 18:56
  • 1
    What you are describing is physically impossible. The IP version is established when the socket descriptor is created, and it cannot change IP version afterwards. It can only bind to local/remote IP addresses that match its established IP version, and a bound socket cannot jump from one IP address to another during its lifetime. Once connected, `getLocalAddress()` and `getInetAddress()` are persistent for the lifetime of the connection. So something else is going on in your code. My guess is that you are likely calling `getLocalAddress()` on different `Socket` objects and not realizing it. – Remy Lebeau Aug 04 '16 at 19:02
  • I am definitely not calling it on different sockets, and the behavior is definitely occurring. I only create one `Socket`, and only call `connect` once. If it is rebound to a different IP, then it is happening internally to Java library code, independent of my calls. This is the behavior I am trying to find an explanation for. – Matthew G Dippel Aug 04 '16 at 19:16
  • 1
    I already explained in my answer how the code you have shown could be binding the `Socket` to either IPv4 or IPv6 if you are `connect()`'ing to a hostname instead of an IP address. And I showed you how to restrict a hostname `connect()` to IPv4 only. However, once `connect()` is successful, there is *no possible way* that calling `getLocalAddress()` multiple times on the *same* `Socket` object can return different IP addresses at different times. That is just not how sockets work, either in Java or even at the OS layer. Once a socket is bound, it cannot be rebound, it cannot change its address – Remy Lebeau Aug 04 '16 at 20:15
1

I was able to determine what causes the problem, although I'm still not sure exactly what is happening under the covers. If the connection is broken server side, and reads from the socket are resulting in Broken pipe errors, then this behavior occurs. See below for how I replicated it:

    Socket s = new Socket();
    String server = "1.2.3.4" // not my real server ip
    s.connect(new InetSocketAddress(server, 9050));
    try{
        System.out.println(s.getLocalAddress().getAddress().length);
        byte[] badData = new byte[100];
        // server application will kill the connection when it cant parse this
        // according to application logic
        Arrays.fill(badData, (byte) 0xFF);
        for(int i = 0; i < 1000; i++){
            OutputStream out = s.getOutputStream();
            out.write(badData);
            out.flush();
            Thread.sleep(1);
        }

    }
    catch(SocketException e){
        System.out.println(e.getMessage());
        System.out.println(s.getLocalAddress().getAddress().length);
    }

Which outputs the following:

4
Broken pipe
16

So, I have figured out what is causing it, but still no insights into why this behavior occurs from Java.

  • The local and remote addresses of a socket are meaningless after the connection is broken. What you're doing makes no sense. – user207421 Aug 04 '16 at 23:40
  • Agreed that they are meaningless, but it was still causing errors to be thrown in my application logic when I tried to format my data to be sent, before I realized the connection was broken from the server side. Hence why I was asking what could cause the behavior. – Matthew G Dippel Aug 05 '16 at 01:02