Add an option to use TCP keepalives

Posted by Kobold on Thu 12 Nov 2009 11:31 AM — 10 posts, 41,404 views.

#0
I'm behind a NAT router that is really aggressive about kicking off idle connections, and I end up getting kicked off for inactivity every 10-20 minutes or so.

I'm aware that this can be solved via scripting (by sending an idle command), but I think this is a common enough problem that either a plugin should be provided as a download to users or the option included in the application. I don't think the user should be expected to code for this simple functionality. Also, such an application-level solution is inferior because it: a. makes idle timers inaccurate on MUCKs and b. makes it possible for the 'idle command' to mess up an edit in progress (that the user idled on).

Therefore, I think the best way is to use TCP keepalives. There was a discussion earlier on this board about them, but I think the objections some people raised were all very dated (being from the 80s). The proposed method would be to add an option in the world preferences, along with a text field for amount of time (in seconds) to wait between sending keepalive packets. The TCP keepalives can then be implemented with WSAIoctl(SIO_KEEPALIVE_VALS) when the sockets are opened.

I would contribute a patch, but I am basically inexperienced in Windows GUI programming. I barely managed to get MUCKClient compiled and even then it seemed to be off a bit (the toolbars were the wrong color). I also don't know how to go about adding the required UI elements.

However, I did "patch" this manually myself as an experiment. This involved the use of a disassembler and Microsoft Detours to hook into one of the methods in MUSHClient and making the call myself before returning control back to the main executable. It works very well with my connection, though as it stands, I have to find the offset for InitiateConnection with every MUSHClient revision, so it would be cool if this feature could make it in.
USA #1
Have you tried updating the firmware on your router? I know old linksys routers loved putting the smackdown on sockets.
Australia Forum Administrator #2
OK, well I added an option to the next version to set SO_KEEPALIVE for the main world socket. We'll see if that helps.

Like this:


  if (m_bSendKeepAlives)
    {
    BOOL bKeepAlive = TRUE;
    m_pSocket->SetSockOpt (SO_KEEPALIVE, &bKeepAlive, sizeof bKeepAlive);
    }


That isn't exactly what you did, but hopefully that will work. If you don't think it will, please advise.
#3
Nick: Thanks! The only problem with that is that the default Windows keep alive interval is something like two hours. I think if anyone actually needs the option, two hours is way too long. One can adjust this with a global registry setting, but it's easiest to use a per connection way to do it. The per-connection way is almost as easy as SO_KEEPALIVE. Just requires passing a struct via WSAIoctl.

Something like:


#include <mstcpip.h>

...

        if (m_bSendKeepAlives)
        {
                DWORD dwBytesReturned;
                tcp_keepalive keepaliveData = {1, 600000, 6000};
                WSAIoctl(m_pSocket->m_hSocket, SIO_KEEPALIVE_VALS, &keepaliveData, sizeof(keepaliveData), NULL, 0, &dwBytesReturned, NULL, NULL);
        }



This causes keep-alive probes to be sent after ten minutes of inactivity (every ten minutes if the server responds on time). (10 * 60 * 1000 = 600000). Also, if the server doesn't respond after a minute to probes, the connection is considered terminated. (60*1000/10, the 10 is the hardcoded number of probes Windows sends out before giving up on the connection, each probe timing out after 6000 milliseconds).

The numbers I've given are probably sensible ones, but the best thing might be to allow the user to specify the interval (in seconds) between keep-alives.

WSAIoctl returns non-zero on error and WSAGetLastError can be used to retrieve the exact error code if one is interested in that sort of thing (probably unnecessary).

Thanks again!

WillFa: Heh, I'm using the latest firmware for my router unfortunately (and I don't really want to mess with third-party firmware in this situation).
Australia Forum Administrator #4
I initially thought the documentation said the same thing, but rather confusingly (standard however for Microsoft, heh) it says this:


The default settings when a TCP socket is initialized sets the keep-alive timeout to 2 hours and the keep-alive interval to 1 second. The default system-wide value of the keep-alive timeout is controllable through the KeepAliveTime registry setting which takes a value in milliseconds. The default system-wide value of the keep-alive interval is controllable through the KeepAliveInterval registry setting which takes a value in milliseconds.

On Windows Vista and later, the number of keep-alive probes (data retransmissions) is set to 10 and cannot be changed.


What is this "1 second" interval they talk about?

From:

http://msdn.microsoft.com/en-us/library/ee470551(VS.85).aspx

However I have no objection to using your alternate code.


...

Or I would if I had mstcpip.h, which I don't. Can you post enough of that to get my code to work, specifically the structure for tcp_keepalive and the value of SIO_KEEPALIVE_VALS?
USA #5
It sounds like it sends a keepalive packet every second, and if the keepalives aren't responded to for two hours, it decides the connection has broken. Assuming there weren't any more obvious network failures/closures, that is.
#6
See this page: http://msdn.microsoft.com/en-us/library/dd877220(VS.85).aspx


/* Argument structure for SIO_KEEPALIVE_VALS */
struct tcp_keepalive {
    u_long  onoff;
    u_long  keepalivetime;
    u_long  keepaliveinterval;
};


Quote:
The value specified in the onoff member determines if TCP keep-alive is enabled or disabled. If the onoff member is set to a non-zero value, TCP keep-alive is enabled and the other members in the structure are used. The keepalivetime member specifies the timeout, in milliseconds, with no activity until the first keep-alive packet is sent. The keepaliveinterval member specifies the interval, in milliseconds, between when successive keep-alive packets are sent if no acknowledgement is received.

...

On Windows Vista and later, the number of keep-alive probes (data retransmissions) is set to 10 and cannot be changed.


Anyway, it means the default is two hours between transmissions and one second in between retransmissions of the probe that was transmitted. If ten probes have been transmitted but have gotten no response from the peer, the connection is considered terminated.

And I have plenty of empirical evidence on this score too. I tried SO_KEEPALIVE only myself only when I first did it and it didn't help at all. =P Later, I added the proper registry settings and that worked.

EDIT: I zipped up mstcpip.h and nldef.h and put it here: http://3.ly/w3w

The important parts to know seems to be that SIO_KEEPALIVE_VALS is 0x98000004 and the struct tcp_keepalive above. The include files came with my install of Visual Studio 2008, which installed Windows Platform SDK 6.0a which is what had those includes.
Amended on Fri 13 Nov 2009 09:17 AM by Kobold
Australia Forum Administrator #7
OK got that thanks.
Australia Forum Administrator #8
I saw an interesting suggestion recently.

The sequence IAC NOP (255 241) should be ignored by the server (I'm not saying it *will* be ignored but the spec says it should).

Thus doing this periodically (eg. in a timer) should keep the connection alive, whilst have no effect on the server:


SendPkt (string.char (255, 241))


I wouldn't flood the server with them, once a minute or 5 minutes should be plenty. And depending on the server, it might either get you disconnected, or result in funny characters in your input stream. However technically it should work.
USA #9
Unfortunately, the full extent of Telnet (or even just the subnegotiations) is often not supported by MUD codebases, from what I've heard recently. I think someone said Diku specifically had problems with it. TCP is pretty much universal, though, and protocol-level keepalives are just about guaranteed to work.