0

I have an application that opens a read-only TCP stream socket. I have set up KeepAlive to ensure that every second a message is sent to check if the connection is still ok. Wireshark shows that the KeepAlive messages are sent and also ACKd by the other computer. My application is running on windows.

The remote machine to where I open the socket is on a separate LAN adapter compared to the rest of the network/internet.

enter image description here

There are two scenarios that seem to react in a different way, and I would like to know why.

  1. I am connected to the internet. TCP Keep-Alive is going strong every second. I pull the LAN cable from the machine. The last TCP Keep-Alive is not ACKd and therefore Socket.Connected is false, as expected.
  2. I am not connected to the internet. TCP Keep-Alive is going strong every second. I pull the LAN cable from the machine. No TCP Keep-Alive package was sent to the remote machine at all, therefore Socket.Connected is still true.

Am I missing an important concept here on when a TCP Keep-Alive package is sent by the socket? For example, don't send if you don't have a network connection at all? And if so, how can I ensure that the socket is no longer Connected?

_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.SetupKeepAlive();
_socket.Blocking = false;
public static void SetupKeepAlive(this Socket socket, uint interval = 1000U)
{
    if (socket is null)
    {
        throw new ArgumentNullException(nameof(socket));
    }

    // Get the size of the uint to use to back the byte array
    var size = Marshal.SizeOf(0U);

    // Create the byte array
    var keepAlive = new byte[size * 3];

    // Pack the byte array:
    // Turn keep-alive on
    Buffer.BlockCopy(BitConverter.GetBytes(1U), 0, keepAlive, 0, size);

    // Set amount of time without activity before sending a keep-alive
    Buffer.BlockCopy(BitConverter.GetBytes(interval), 0, keepAlive, size, size);

    // Set keep-alive interval
    Buffer.BlockCopy(BitConverter.GetBytes(interval), 0, keepAlive, size * 2, size);

    // Set the keep-alive settings on the underlying Socket
    _ = socket.IOControl(IOControlCode.KeepAliveValues, keepAlive, null);
}
Marnix
  • 6,384
  • 4
  • 43
  • 78

1 Answers1

0

Socket is not aware of Internet Connetion availability but it's aware of Network Device state. It's just a very low-level TCP unit.

It can only send and receive something not diagnosing the network behind the network device. I didn't read the RFC but the behavior you're described is expected.

Keep-alive sill be sent periodically while TCP connetion is up.

therefore Socket.Connected is still true

Looks like a known TCP head-of-line problem. Socket is waiting for response till timeout occured to be closed.

Imagine the setup: you're in poor connection environment, some host is loosing packets or hang for seconds. Then if Socket will close the connection before timout only when not received the keep-alive ping, you'll get a closed connection. Keep-alive is intended to make the connection lifetime longer not shorter.

Also a tip:

var keepAlive = new byte[12];
var values = MemoryMarshal.Cast<byte, int>(ref keepAlive);
values[0] = 1;
values[1] = interval; // make it int
values[2] = interval;
// the values were written directly to byte[] array

Looks like you used the wrong number sizes. Array size expected 12 bytes (3x4) not 24 bytes (3x8). Probably you used C++ long as C# long. But C++ long is int in C#. C# long is C++ long long.

aepot
  • 4,558
  • 2
  • 12
  • 24
  • 1
    Thanks for the tip. I have changed my code. I also noticed what goes wrong. I read in this answer: https://stackoverflow.com/a/32244584/363224 that there is some hardcoded retry mechanism of 10 retries. Meaning that an interval of 1 second can potentially become 10 seconds waiting when unplugging a cable. I made the interval smaller and now it works. – Marnix Dec 02 '21 at 15:44
  • 1
    @Marnix keep-alive interval must be < than idle timeout but as close to it as possible. There's no sense to ping 10 times/sec which cause a bunch of useless traffic. For example idle timeout is 3s, then interval can be 2s which means `2s + ping < timeout`. Just a recommendation. Keep-alive only prevents idle-timeouted connection close, nothing else. – aepot Dec 02 '21 at 15:59