diff --git a/config.c b/config.c index 50548eee..d3d76f57 100644 --- a/config.c +++ b/config.c @@ -1292,6 +1292,10 @@ void setup_config_box(struct controlbox *b, struct sesslist *sesslist, 'n', HELPCTX(connection_nodelay), dlg_stdcheckbox_handler, I(offsetof(Config,tcp_nodelay))); + ctrl_checkbox(s, "Enable TCP keepalives (SO_KEEPALIVE option)", + 'p', HELPCTX(connection_tcpkeepalive), + dlg_stdcheckbox_handler, + I(offsetof(Config,tcp_keepalives))); } } diff --git a/doc/config.but b/doc/config.but index 7572a779..2279db58 100644 --- a/doc/config.but +++ b/doc/config.but @@ -1,4 +1,4 @@ -\versionid $Id: config.but,v 1.83 2004/06/15 11:31:30 jacob Exp $ +\versionid $Id: config.but,v 1.84 2004/06/20 17:07:36 jacob Exp $ \C{config} Configuring PuTTY @@ -1506,7 +1506,8 @@ what \e{kind} of network problems you have between you and the server. Keepalives are only supported in Telnet and SSH; the Rlogin and Raw -protocols offer no way of implementing them. +protocols offer no way of implementing them. (For an alternative, see +\k{config-tcp-keepalives}.) Note that if you are using SSH1 and the server has a bug that makes it unable to deal with SSH1 ignore messages (see @@ -1525,6 +1526,34 @@ types of server. The Nagle algorithm is disabled by default. +\S{config-tcp-keepalives} \q{Enable TCP keepalives} + +\cfg{winhelp-topic}{connection.tcpkeepalive} + +\e{NOTE:} TCP keepalives should not be confused with the +application-level keepalives described in \k{config-keepalive}. If in +doubt, you probably want application-level keepalives; TCP keepalives +are provided for completeness. + +The idea of TCP keepalives is similar to application-level keepalives, +and the same caveats apply. The main differences are: + +\b TCP keepalives are available on \e{all} connection types, including +Raw and Rlogin. + +\b The interval between TCP keepalives is usually much longer, +typically two hours; this is set by the operating system, and cannot +be configured within PuTTY. + +\b If the operating system does not receive a response to a keepalive, +it may send out more in quick succession and if terminate the connection +if no response is received. + +TCP keepalives may be useful for ensuring that half-open connections +are terminated than for keeping a connection alive. + +TCP keepalives are disabled by default. + \H{config-proxy} The Proxy panel \cfg{winhelp-topic}{proxy.main} diff --git a/mac/macnet.c b/mac/macnet.c index 106a6315..9fab15bc 100644 --- a/mac/macnet.c +++ b/mac/macnet.c @@ -12,7 +12,7 @@ struct macnet_stack { void (*addrcopy)(SockAddr, char *); void (*addr_free)(SockAddr); Socket (*skregister)(void *, Plug); /* "register" is a reserved word */ - Socket (*new)(SockAddr, int, int, int, int, Plug); + Socket (*new)(SockAddr, int, int, int, int, int, Plug); Socket (*newlistener)(char *, int, Plug, int); char *(*addr_error)(SockAddr); void (*poll)(void); @@ -128,11 +128,12 @@ Socket sk_register(void *sock, Plug plug) } Socket sk_new(SockAddr addr, int port, int privport, int oobinline, - int nodelay, Plug plug) + int nodelay, int keepalive, Plug plug) { if (stack != NULL) - return stack->new(addr, port, privport, oobinline, nodelay, plug); + return stack->new(addr, port, privport, oobinline, nodelay, keepalive, + plug); return NULL; } diff --git a/mac/macterm.c b/mac/macterm.c index 88eb2668..7766c7b9 100644 --- a/mac/macterm.c +++ b/mac/macterm.c @@ -1,4 +1,4 @@ -/* $Id: macterm.c,v 1.75 2003/05/04 14:18:18 simon Exp $ */ +/* $Id: macterm.c,v 1.76 2004/06/20 17:07:37 jacob Exp $ */ /* * Copyright (c) 1999 Simon Tatham * Copyright (c) 1999, 2002 Ben Harris @@ -158,7 +158,8 @@ void mac_startsession(Session *s) term_provide_logctx(s->term, s->logctx); errmsg = s->back->init(s, &s->backhandle, &s->cfg, s->cfg.host, - s->cfg.port, &s->realhost, s->cfg.tcp_nodelay); + s->cfg.port, &s->realhost, s->cfg.tcp_nodelay, + s->cfg.tcp_keepalives); if (errmsg != NULL) fatalbox("%s", errmsg); s->back->provide_logctx(s->backhandle, s->logctx); diff --git a/mac/mtcpnet.c b/mac/mtcpnet.c index a8404b7a..791557d2 100644 --- a/mac/mtcpnet.c +++ b/mac/mtcpnet.c @@ -410,7 +410,7 @@ Socket mactcp_register(void *sock, Plug plug) static TCPNotifyUPP mactcp_asr_upp; Socket mactcp_new(SockAddr addr, int port, int privport, int oobinline, - int nodelay, Plug plug) + int nodelay, int keepalive, Plug plug) { static struct socket_function_table fn_table = { mactcp_plug, diff --git a/mac/otnet.c b/mac/otnet.c index 7881e02a..35d3b16c 100644 --- a/mac/otnet.c +++ b/mac/otnet.c @@ -234,7 +234,7 @@ Socket ot_register(void *sock, Plug plug) } Socket ot_new(SockAddr addr, int port, int privport, int oobinline, - int nodelay, Plug plug) + int nodelay, int keepalive, Plug plug) { static struct socket_function_table fn_table = { ot_tcp_plug, @@ -281,7 +281,7 @@ Socket ot_new(SockAddr addr, int port, int privport, int oobinline, return (Socket) ret; } - /* TODO: oobinline, nodelay */ + /* TODO: oobinline, nodelay, keepalive */ /* * Bind to local address. diff --git a/network.h b/network.h index cb077fdb..45742073 100644 --- a/network.h +++ b/network.h @@ -79,8 +79,8 @@ struct plug_function_table { * responsibility for freeing it */ Socket new_connection(SockAddr addr, char *hostname, int port, int privport, - int oobinline, int nodelay, Plug plug, - const Config *cfg); + int oobinline, int nodelay, int keepalive, + Plug plug, const Config *cfg); Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only, const Config *cfg); SockAddr name_lookup(char *host, int port, char **canonicalname, @@ -90,8 +90,8 @@ SockAddr name_lookup(char *host, int port, char **canonicalname, /* (same caveat about addr as new_connection()) */ Socket platform_new_connection(SockAddr addr, char *hostname, int port, int privport, - int oobinline, int nodelay, Plug plug, - const Config *cfg); + int oobinline, int nodelay, int keepalive, + Plug plug, const Config *cfg); /* socket functions */ @@ -111,7 +111,7 @@ void sk_addr_free(SockAddr addr); /* NB, control of 'addr' is passed via sk_new, which takes responsibility * for freeing it, as for new_connection() */ Socket sk_new(SockAddr addr, int port, int privport, int oobinline, - int nodelay, Plug p); + int nodelay, int keepalive, Plug p); Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only); diff --git a/plink.c b/plink.c index a0a9541a..92769e86 100644 --- a/plink.c +++ b/plink.c @@ -564,7 +564,7 @@ int main(int argc, char **argv) (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR); error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, - &realhost, nodelay); + &realhost, nodelay, cfg.tcp_keepalives); if (error) { fprintf(stderr, "Unable to open connection:\n%s", error); return 1; diff --git a/portfwd.c b/portfwd.c index d9941718..b95e5acd 100644 --- a/portfwd.c +++ b/portfwd.c @@ -390,7 +390,7 @@ const char *pfd_newconnect(Socket *s, char *hostname, int port, pr->dynamic = 0; pr->s = *s = new_connection(addr, dummy_realhost, port, - 0, 1, 0, (Plug) pr, cfg); + 0, 1, 0, 0, (Plug) pr, cfg); if ((err = sk_socket_error(*s)) != NULL) { sfree(pr); return err; diff --git a/pproxy.c b/pproxy.c index 5cb90162..9c4c2d89 100644 --- a/pproxy.c +++ b/pproxy.c @@ -10,8 +10,8 @@ Socket platform_new_connection(SockAddr addr, char *hostname, int port, int privport, - int oobinline, int nodelay, Plug plug, - const Config *cfg) + int oobinline, int nodelay, int keepalive, + Plug plug, const Config *cfg) { return NULL; } diff --git a/proxy.c b/proxy.c index dd5c428c..d3bcca69 100644 --- a/proxy.c +++ b/proxy.c @@ -356,8 +356,8 @@ SockAddr name_lookup(char *host, int port, char **canonicalname, Socket new_connection(SockAddr addr, char *hostname, int port, int privport, - int oobinline, int nodelay, Plug plug, - const Config *cfg) + int oobinline, int nodelay, int keepalive, + Plug plug, const Config *cfg) { static const struct socket_function_table socket_fn_table = { sk_proxy_plug, @@ -388,7 +388,8 @@ Socket new_connection(SockAddr addr, char *hostname, Socket sret; if ((sret = platform_new_connection(addr, hostname, port, privport, - oobinline, nodelay, plug, cfg)) != + oobinline, nodelay, keepalive, + plug, cfg)) != NULL) return sret; @@ -444,7 +445,7 @@ Socket new_connection(SockAddr addr, char *hostname, */ ret->sub_socket = sk_new(proxy_addr, cfg->proxy_port, privport, oobinline, - nodelay, (Plug) pplug); + nodelay, keepalive, (Plug) pplug); if (sk_socket_error(ret->sub_socket) != NULL) return (Socket) ret; @@ -456,7 +457,7 @@ Socket new_connection(SockAddr addr, char *hostname, } /* no proxy, so just return the direct socket */ - return sk_new(addr, port, privport, oobinline, nodelay, plug); + return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); } Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only, diff --git a/psftp.c b/psftp.c index b134c503..10056bd5 100644 --- a/psftp.c +++ b/psftp.c @@ -1939,7 +1939,8 @@ static int psftp_connect(char *userhost, char *user, int portnumber) back = &ssh_backend; - err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,0); + err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost, + 0, cfg.tcp_keepalives); if (err != NULL) { fprintf(stderr, "ssh_init: %s\n", err); return 1; diff --git a/putty.h b/putty.h index 723f1e6b..fed589bb 100644 --- a/putty.h +++ b/putty.h @@ -269,7 +269,8 @@ enum { struct backend_tag { const char *(*init) (void *frontend_handle, void **backend_handle, Config *cfg, - char *host, int port, char **realhost, int nodelay); + char *host, int port, char **realhost, int nodelay, + int keepalive); void (*free) (void *handle); /* back->reconfig() passes in a replacement configuration. */ void (*reconfig) (void *handle, Config *cfg); @@ -329,6 +330,7 @@ struct config_tag { int warn_on_close; int ping_interval; /* in seconds */ int tcp_nodelay; + int tcp_keepalives; /* Proxy options */ char proxy_exclude_list[512]; int proxy_dns; diff --git a/raw.c b/raw.c index 4c9e5bec..8c1f97fd 100644 --- a/raw.c +++ b/raw.c @@ -69,7 +69,8 @@ static void raw_sent(Plug plug, int bufsize) */ static const char *raw_init(void *frontend_handle, void **backend_handle, Config *cfg, - char *host, int port, char **realhost, int nodelay) + char *host, int port, char **realhost, int nodelay, + int keepalive) { static const struct plug_function_table fn_table = { raw_closing, @@ -115,7 +116,7 @@ static const char *raw_init(void *frontend_handle, void **backend_handle, logevent(raw->frontend, buf); sfree(buf); } - raw->s = new_connection(addr, *realhost, port, 0, 1, nodelay, + raw->s = new_connection(addr, *realhost, port, 0, 1, nodelay, keepalive, (Plug) raw, cfg); if ((err = sk_socket_error(raw->s)) != NULL) return err; diff --git a/rlogin.c b/rlogin.c index af3bd876..f2b61d27 100644 --- a/rlogin.c +++ b/rlogin.c @@ -100,7 +100,7 @@ static void rlogin_sent(Plug plug, int bufsize) static const char *rlogin_init(void *frontend_handle, void **backend_handle, Config *cfg, char *host, int port, char **realhost, - int nodelay) + int nodelay, int keepalive) { static const struct plug_function_table fn_table = { rlogin_closing, @@ -149,7 +149,7 @@ static const char *rlogin_init(void *frontend_handle, void **backend_handle, sfree(buf); } rlogin->s = new_connection(addr, *realhost, port, 1, 0, - nodelay, (Plug) rlogin, cfg); + nodelay, keepalive, (Plug) rlogin, cfg); if ((err = sk_socket_error(rlogin->s)) != NULL) return err; diff --git a/scp.c b/scp.c index 06bf0893..f7fa255f 100644 --- a/scp.c +++ b/scp.c @@ -450,7 +450,8 @@ static void do_cmd(char *host, char *user, char *cmd) back = &ssh_backend; - err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,0); + err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost, + 0, cfg.tcp_keepalives); if (err != NULL) bump("ssh_init: %s", err); logctx = log_init(NULL, &cfg); diff --git a/settings.c b/settings.c index 99ec7eeb..a5f2e448 100644 --- a/settings.c +++ b/settings.c @@ -182,6 +182,7 @@ void save_open_settings(void *sesskey, int do_host, Config *cfg) write_setting_i(sesskey, "PingInterval", cfg->ping_interval / 60); /* minutes */ write_setting_i(sesskey, "PingIntervalSecs", cfg->ping_interval % 60); /* seconds */ write_setting_i(sesskey, "TCPNoDelay", cfg->tcp_nodelay); + write_setting_i(sesskey, "TCPKeepalives", cfg->tcp_keepalives); write_setting_s(sesskey, "TerminalType", cfg->termtype); write_setting_s(sesskey, "TerminalSpeed", cfg->termspeed); @@ -411,6 +412,7 @@ void load_open_settings(void *sesskey, int do_host, Config *cfg) cfg->ping_interval = pingmin * 60 + pingsec; } gppi(sesskey, "TCPNoDelay", 1, &cfg->tcp_nodelay); + gppi(sesskey, "TCPKeepalives", 0, &cfg->tcp_keepalives); gpps(sesskey, "TerminalType", "xterm", cfg->termtype, sizeof(cfg->termtype)); gpps(sesskey, "TerminalSpeed", "38400,38400", cfg->termspeed, diff --git a/ssh.c b/ssh.c index dec48ce8..6c94ba49 100644 --- a/ssh.c +++ b/ssh.c @@ -2128,7 +2128,7 @@ static void ssh_sent(Plug plug, int bufsize) * freed by the caller. */ static const char *connect_to_host(Ssh ssh, char *host, int port, - char **realhost, int nodelay) + char **realhost, int nodelay, int keepalive) { static const struct plug_function_table fn_table = { ssh_closing, @@ -2169,7 +2169,7 @@ static const char *connect_to_host(Ssh ssh, char *host, int port, } ssh->fn = &fn_table; ssh->s = new_connection(addr, *realhost, port, - 0, 1, nodelay, (Plug) ssh, &ssh->cfg); + 0, 1, nodelay, keepalive, (Plug) ssh, &ssh->cfg); if ((err = sk_socket_error(ssh->s)) != NULL) { ssh->s = NULL; return err; @@ -6183,7 +6183,8 @@ static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt) */ static const char *ssh_init(void *frontend_handle, void **backend_handle, Config *cfg, - char *host, int port, char **realhost, int nodelay) + char *host, int port, char **realhost, int nodelay, + int keepalive) { const char *p; Ssh ssh; @@ -6267,7 +6268,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->protocol = NULL; - p = connect_to_host(ssh, host, port, realhost, nodelay); + p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive); if (p != NULL) return p; diff --git a/telnet.c b/telnet.c index 2edae219..6f4f54ca 100644 --- a/telnet.c +++ b/telnet.c @@ -674,7 +674,7 @@ static void telnet_sent(Plug plug, int bufsize) static const char *telnet_init(void *frontend_handle, void **backend_handle, Config *cfg, char *host, int port, char **realhost, - int nodelay) + int nodelay, int keepalive) { static const struct plug_function_table fn_table = { telnet_closing, @@ -729,7 +729,7 @@ static const char *telnet_init(void *frontend_handle, void **backend_handle, sfree(buf); } telnet->s = new_connection(addr, *realhost, port, 0, 1, - nodelay, (Plug) telnet, &telnet->cfg); + nodelay, keepalive, (Plug) telnet, &telnet->cfg); if ((err = sk_socket_error(telnet->s)) != NULL) return err; diff --git a/testback.c b/testback.c index 22860f33..bc2eb31f 100644 --- a/testback.c +++ b/testback.c @@ -1,4 +1,4 @@ -/* $Id: testback.c,v 1.9 2003/05/10 11:57:55 ben Exp $ */ +/* $Id: testback.c,v 1.10 2004/06/20 17:07:32 jacob Exp $ */ /* * Copyright (c) 1999 Simon Tatham * Copyright (c) 1999 Ben Harris @@ -34,9 +34,9 @@ #include "putty.h" static const char *null_init(void *, void **, Config *, char *, int, char **, - int); + int, int); static const char *loop_init(void *, void **, Config *, char *, int, char **, - int); + int, int); static void null_free(void *); static void loop_free(void *); static void null_reconfig(void *, Config *); @@ -72,14 +72,14 @@ struct loop_state { static const char *null_init(void *frontend_handle, void **backend_handle, Config *cfg, char *host, int port, - char **realhost, int nodelay) { + char **realhost, int nodelay, int keepalive) { return NULL; } static const char *loop_init(void *frontend_handle, void **backend_handle, Config *cfg, char *host, int port, - char **realhost, int nodelay) { + char **realhost, int nodelay, int keepalive) { struct loop_state *st = snew(struct loop_state); st->term = frontend_handle; diff --git a/unix/pterm.c b/unix/pterm.c index 87339fb4..8e2554d8 100644 --- a/unix/pterm.c +++ b/unix/pterm.c @@ -3367,7 +3367,8 @@ int pt_main(int argc, char **argv) error = inst->back->init((void *)inst, &inst->backhandle, &inst->cfg, inst->cfg.host, inst->cfg.port, - &realhost, inst->cfg.tcp_nodelay); + &realhost, inst->cfg.tcp_nodelay, + inst->cfg.tcp_keepalives); if (error) { char *msg = dupprintf("Unable to open connection to %s:\n%s", diff --git a/unix/pty.c b/unix/pty.c index 7f46f1aa..20ddc801 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -486,7 +486,8 @@ static void pty_uxsel_setup(void) * freed by the caller. */ static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, - char *host, int port, char **realhost, int nodelay) + char *host, int port, char **realhost, int nodelay, + int keepalive) { int slavefd; pid_t pid, pgrp; diff --git a/unix/uxnet.c b/unix/uxnet.c index 1e7d51b7..08fb4e17 100644 --- a/unix/uxnet.c +++ b/unix/uxnet.c @@ -378,7 +378,7 @@ Socket sk_register(OSSocket sockfd, Plug plug) } Socket sk_new(SockAddr addr, int port, int privport, int oobinline, - int nodelay, Plug plug) + int nodelay, int keepalive, Plug plug) { int s; #ifdef IPV6 @@ -433,6 +433,11 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline, setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b)); } + if (keepalive) { + int b = TRUE; + setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b)); + } + /* * Bind to local address. */ diff --git a/unix/uxplink.c b/unix/uxplink.c index e59a701b..6bf2c858 100644 --- a/unix/uxplink.c +++ b/unix/uxplink.c @@ -557,7 +557,7 @@ int main(int argc, char **argv) int nodelay = cfg.tcp_nodelay && isatty(0); error = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, - &realhost, nodelay); + &realhost, nodelay, cfg.tcp_keepalives); if (error) { fprintf(stderr, "Unable to open connection:\n%s\n", error); return 1; diff --git a/unix/uxproxy.c b/unix/uxproxy.c index b3f75e81..1191a61a 100644 --- a/unix/uxproxy.c +++ b/unix/uxproxy.c @@ -221,8 +221,8 @@ static int localproxy_select_result(int fd, int event) Socket platform_new_connection(SockAddr addr, char *hostname, int port, int privport, - int oobinline, int nodelay, Plug plug, - const Config *cfg) + int oobinline, int nodelay, int keepalive, + Plug plug, const Config *cfg) { char *cmd; diff --git a/window.c b/window.c index 71f30b4d..e720ed3c 100644 --- a/window.c +++ b/window.c @@ -611,7 +611,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) char *realhost; error = back->init(NULL, &backhandle, &cfg, - cfg.host, cfg.port, &realhost, cfg.tcp_nodelay); + cfg.host, cfg.port, &realhost, cfg.tcp_nodelay, + cfg.tcp_keepalives); back->provide_logctx(backhandle, logctx); if (error) { char *str = dupprintf("%s Error", appname); diff --git a/winhelp.h b/winhelp.h index bbde922b..5271f924 100644 --- a/winhelp.h +++ b/winhelp.h @@ -63,6 +63,7 @@ #define WINHELP_CTX_connection_username "connection.username" #define WINHELP_CTX_connection_keepalive "connection.keepalive" #define WINHELP_CTX_connection_nodelay "connection.nodelay" +#define WINHELP_CTX_connection_tcpkeepalive "connection.tcpkeepalive" #define WINHELP_CTX_proxy_type "proxy.type" #define WINHELP_CTX_proxy_main "proxy.main" #define WINHELP_CTX_proxy_exclude "proxy.exclude" diff --git a/winnet.c b/winnet.c index 21a2f293..bcb22bf4 100644 --- a/winnet.c +++ b/winnet.c @@ -658,7 +658,7 @@ Socket sk_register(void *sock, Plug plug) } Socket sk_new(SockAddr addr, int port, int privport, int oobinline, - int nodelay, Plug plug) + int nodelay, int keepalive, Plug plug) { static const struct socket_function_table fn_table = { sk_tcp_plug, @@ -722,6 +722,11 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline, p_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b)); } + if (keepalive) { + BOOL b = TRUE; + p_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b)); + } + /* * Bind to local address. */ diff --git a/x11fwd.c b/x11fwd.c index 049381b4..b0b82698 100644 --- a/x11fwd.c +++ b/x11fwd.c @@ -310,7 +310,7 @@ const char *x11_init(Socket * s, char *display, void *c, void *auth, pr->c = c; pr->s = *s = new_connection(addr, dummy_realhost, port, - 0, 1, 0, (Plug) pr, cfg); + 0, 1, 0, 0, (Plug) pr, cfg); if ((err = sk_socket_error(*s)) != NULL) { sfree(pr); return err;