Make -K keepalive behave like OpenSSH's ServerAliveInterval

This commit is contained in:
Matt Johnston 2014-07-09 00:15:20 +08:00
parent 1ccac01cee
commit c884e5000e
8 changed files with 82 additions and 33 deletions

View File

@ -163,6 +163,8 @@ void cli_getopts(int argc, char ** argv) {
opts.ipv6 = 1;
*/
opts.recv_window = DEFAULT_RECV_WINDOW;
opts.keepalive_secs = DEFAULT_KEEPALIVE;
opts.idle_timeout_secs = DEFAULT_IDLE_TIMEOUT;
fill_own_user();

View File

@ -51,6 +51,7 @@ int exitflag = 0; /* GLOBAL */
/* called only at the start of a session, set up initial state */
void common_session_init(int sock_in, int sock_out) {
time_t now;
TRACE(("enter session_init"))
@ -58,9 +59,12 @@ void common_session_init(int sock_in, int sock_out) {
ses.sock_out = sock_out;
ses.maxfd = MAX(sock_in, sock_out);
ses.connect_time = 0;
ses.last_trx_packet_time = 0;
ses.last_packet_time = 0;
now = monotonic_now();
ses.connect_time = now;
ses.last_packet_time_keepalive_recv = now;
ses.last_packet_time_idle = now;
ses.last_packet_time_any_sent = 0;
ses.last_packet_time_keepalive_sent = 0;
if (pipe(ses.signal_pipe) < 0) {
dropbear_exit("Signal pipe failed");
@ -387,11 +391,21 @@ static int ident_readln(int fd, char* buf, int count) {
return pos+1;
}
void send_msg_ignore() {
static void send_msg_keepalive() {
CHECKCLEARTOWRITE();
buf_putbyte(ses.writepayload, SSH_MSG_IGNORE);
buf_putstring(ses.writepayload, "", 0);
time_t old_time_idle = ses.last_packet_time_idle;
/* Try to force a response from the other end. Some peers will
reply with SSH_MSG_REQUEST_FAILURE, some will reply with SSH_MSG_UNIMPLEMENTED */
buf_putbyte(ses.writepayload, SSH_MSG_GLOBAL_REQUEST);
/* A short string */
buf_putstring(ses.writepayload, "k@dropbear.nl", 0);
buf_putbyte(ses.writepayload, 1); /* want_reply */
encrypt_packet();
ses.last_packet_time_keepalive_sent = monotonic_now();
/* keepalives shouldn't update idle timeout, reset it back */
ses.last_packet_time_idle = old_time_idle;
}
/* Check all timeouts which are required. Currently these are the time for
@ -401,7 +415,7 @@ static void checktimeouts() {
time_t now;
now = monotonic_now();
if (ses.connect_time != 0 && now - ses.connect_time >= AUTH_TIMEOUT) {
if (now - ses.connect_time >= AUTH_TIMEOUT) {
dropbear_close("Timeout before auth");
}
@ -417,13 +431,27 @@ static void checktimeouts() {
send_msg_kexinit();
}
if (opts.keepalive_secs > 0
&& now - ses.last_trx_packet_time >= opts.keepalive_secs) {
send_msg_ignore();
if (opts.keepalive_secs > 0) {
/* Send keepalives if we've been idle */
if (now - ses.last_packet_time_any_sent >= opts.keepalive_secs) {
send_msg_keepalive();
}
/* Also send an explicit keepalive message to trigger a response
if the remote end hasn't sent us anything */
if (now - ses.last_packet_time_keepalive_recv >= opts.keepalive_secs
&& now - ses.last_packet_time_keepalive_sent >= opts.keepalive_secs) {
send_msg_keepalive();
}
if (now - ses.last_packet_time_keepalive_recv
>= opts.keepalive_secs * DEFAULT_KEEPALIVE_LIMIT) {
dropbear_exit("Keepalive timeout");
}
}
if (opts.idle_timeout_secs > 0 && ses.last_packet_time > 0
&& now - ses.last_packet_time >= opts.idle_timeout_secs) {
if (opts.idle_timeout_secs > 0
&& now - ses.last_packet_time_idle >= opts.idle_timeout_secs) {
dropbear_close("Idle timeout");
}
}

View File

@ -308,6 +308,11 @@ much traffic. */
be overridden at runtime with -K. 0 disables keepalives */
#define DEFAULT_KEEPALIVE 0
/* If this many KEEPALIVES are sent with no packets received from the
other side, exit. Not run-time configurable - if you have a need
for runtime configuration please mail the Dropbear list */
#define DEFAULT_KEEPALIVE_LIMIT 3
/* Ensure that data is received within IDLE_TIMEOUT seconds. This can
be overridden at runtime with -I. 0 disables idle timeouts */
#define DEFAULT_IDLE_TIMEOUT 0

View File

@ -57,9 +57,7 @@ void write_packet() {
int len, written;
buffer * writebuf = NULL;
time_t now;
unsigned packet_type;
int all_ignore = 1;
#ifdef HAVE_WRITEV
struct iovec *iov = NULL;
int i;
@ -90,7 +88,6 @@ void write_packet() {
packet_type = writebuf->data[writebuf->len-1];
len = writebuf->len - 1 - writebuf->pos;
dropbear_assert(len > 0);
all_ignore &= (packet_type == SSH_MSG_IGNORE);
TRACE2(("write_packet writev #%d type %d len %d/%d", i, packet_type,
len, writebuf->len-1))
iov[i].iov_base = buf_getptr(writebuf, len);
@ -146,7 +143,6 @@ void write_packet() {
dropbear_exit("Error writing: %s", strerror(errno));
}
}
all_ignore = (packet_type == SSH_MSG_IGNORE);
if (written == 0) {
ses.remoteclosed();
@ -163,13 +159,6 @@ void write_packet() {
}
#endif /* writev */
now = monotonic_now();
ses.last_trx_packet_time = now;
if (!all_ignore) {
ses.last_packet_time = now;
}
TRACE2(("leave write_packet"))
}
@ -515,6 +504,8 @@ void encrypt_packet() {
unsigned char packet_type;
unsigned int len, encrypt_buf_size;
unsigned char mac_bytes[MAX_MAC_LEN];
time_t now;
TRACE2(("enter encrypt_packet()"))
@ -622,6 +613,18 @@ void encrypt_packet() {
ses.kexstate.datatrans += writebuf->len;
ses.transseq++;
now = monotonic_now();
ses.last_packet_time_any_sent = now;
/* idle timeout shouldn't be affected by responses to keepalives.
send_msg_keepalive() itself also does tricks with
ses.last_packet_idle_time - read that if modifying this code */
if (packet_type != SSH_MSG_REQUEST_FAILURE
&& packet_type != SSH_MSG_UNIMPLEMENTED
&& packet_type != SSH_MSG_IGNORE) {
ses.last_packet_time_idle = now;
}
TRACE2(("leave encrypt_packet()"))
}

View File

@ -44,6 +44,7 @@ void process_packet() {
unsigned char type;
unsigned int i;
time_t now;
TRACE2(("enter process_packet"))
@ -52,7 +53,8 @@ void process_packet() {
ses.lastpacket = type;
ses.last_packet_time = monotonic_now();
now = monotonic_now();
ses.last_packet_time_keepalive_recv = now;
/* These packets we can receive at any time */
switch(type) {
@ -65,13 +67,21 @@ void process_packet() {
case SSH_MSG_UNIMPLEMENTED:
/* debugging XXX */
TRACE(("SSH_MSG_UNIMPLEMENTED"))
dropbear_exit("Received SSH_MSG_UNIMPLEMENTED");
goto out;
case SSH_MSG_DISCONNECT:
/* TODO cleanup? */
dropbear_close("Disconnect received");
}
/* Ignore these packet types so that keepalives don't interfere with
idle detection. This is slightly incorrect since a tcp forwarded
global request with failure won't trigger the idle timeout,
but that's probably acceptable */
if (!(type == SSH_MSG_GLOBAL_REQUEST || type == SSH_MSG_REQUEST_FAILURE)) {
ses.last_packet_time_idle = now;
}
/* This applies for KEX, where the spec says the next packet MUST be
* NEWKEYS */
if (ses.requirenext != 0) {

View File

@ -37,8 +37,8 @@ typedef struct runopts {
int listen_fwd_all;
#endif
unsigned int recv_window;
time_t keepalive_secs;
time_t idle_timeout_secs;
time_t keepalive_secs; /* Time between sending keepalives. 0 is off */
time_t idle_timeout_secs; /* Exit if no traffic is sent/received in this time */
#ifndef DISABLE_ZLIB
/* TODO: add a commandline flag. Currently this is on by default if compression

View File

@ -147,11 +147,14 @@ struct sshsession {
int signal_pipe[2]; /* stores endpoints of a self-pipe used for
race-free signal handling */
time_t last_trx_packet_time; /* time of the last packet transmission, for
keepalive purposes. Not real-world clock */
/* time of the last packet send/receive, for keepalive. Not real-world clock */
time_t last_packet_time_keepalive_sent;
time_t last_packet_time_keepalive_recv;
time_t last_packet_time_any_sent;
time_t last_packet_time; /* time of the last packet transmission or receive, for
idle timeout purposes. Not real-world clock */
time_t last_packet_time_idle; /* time of the last packet transmission or receive, for
idle timeout purposes so ignores SSH_MSG_IGNORE
or responses to keepalives. Not real-world clock */
/* KEX/encryption related */

View File

@ -95,8 +95,6 @@ void svr_session(int sock, int childpipe) {
chaninitialise(svr_chantypes);
svr_chansessinitialise();
ses.connect_time = monotonic_now();
/* for logging the remote address */
get_socket_address(ses.sock_in, NULL, NULL, &host, &port, 0);
len = strlen(host) + strlen(port) + 2;