From afb651821b9e603fc9347b3fb5d0ba9ddfdad355 Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Thu, 27 Jul 2006 01:24:39 +0000 Subject: Just use the normal "remote closed" handler when reading ident stings --HG-- extra : convert_revision : 9a4e042fd565f46141e81e0c1ab90260303348fe --- common-session.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'common-session.c') diff --git a/common-session.c b/common-session.c index 4c15391..b8ea6f7 100644 --- a/common-session.c +++ b/common-session.c @@ -229,7 +229,7 @@ void session_identification() { /* write our version string, this blocks */ if (atomicio(write, ses.sock, LOCAL_IDENT "\r\n", strlen(LOCAL_IDENT "\r\n")) == DROPBEAR_FAILURE) { - dropbear_exit("Error writing ident string"); + ses.remoteclosed(); } /* If they send more than 50 lines, something is wrong */ @@ -250,7 +250,7 @@ void session_identification() { if (!done) { TRACE(("err: %s for '%s'\n", strerror(errno), linebuf)) - dropbear_exit("Failed to get remote version"); + ses.remoteclosed(); } else { /* linebuf is already null terminated */ ses.remoteident = m_malloc(len); -- cgit v1.2.3 From dd06653e53c9a6a3734eaf5ce5e9374f559a2bf4 Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Tue, 5 Dec 2006 13:27:59 +0000 Subject: Tidy up behaviour when select() is interrupted. We follow normal codepaths, just with no FDs set. --HG-- branch : channel-fix extra : convert_revision : d348546b80847bc0d42a7b5208bb31a54f1fdfaf --- common-session.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) (limited to 'common-session.c') diff --git a/common-session.c b/common-session.c index b8ea6f7..a16d1f9 100644 --- a/common-session.c +++ b/common-session.c @@ -143,27 +143,21 @@ void session_loop(void(*loophandler)()) { dropbear_exit("Terminated by signal"); } - if (val < 0) { - if (errno == EINTR) { - /* This must happen even if we've been interrupted, so that - * changed signal-handler vars can take effect etc */ - if (loophandler) { - loophandler(); - } - continue; - } else { - dropbear_exit("Error in select"); - } + if (val < 0 && errno != EINTR) { + dropbear_exit("Error in select"); + } + + if (val <= 0) { + /* If we were interrupted or the select timed out, we still + * want to iterate over channels etc for reading, to handle + * server processes exiting etc. + * We don't want to read/write FDs. */ + FD_ZERO(&writefd); + FD_ZERO(&readfd); } /* check for auth timeout, rekeying required etc */ checktimeouts(); - - if (val == 0) { - /* timeout */ - TRACE(("select timeout")) - continue; - } /* process session socket's incoming/outgoing data */ if (ses.sock != -1) { -- cgit v1.2.3 From f5ad5c15531a8288222aeafa5b28f30298b3497f Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Fri, 9 Feb 2007 10:43:16 +0000 Subject: Improve behaviour when flushing out after a process has exited. --HG-- branch : channel-fix extra : convert_revision : e73ee8f7ae404a9355685c30828a0ad4524031bc --- channel.h | 2 ++ common-channel.c | 52 +++++++++++++++++++++++++++++++++++++++++++++------- common-session.c | 19 ++++++++++++++++++- session.h | 3 ++- svr-chansession.c | 21 +++++++++++++++++---- 5 files changed, 84 insertions(+), 13 deletions(-) (limited to 'common-session.c') diff --git a/channel.h b/channel.h index fc3cb90..1bfe392 100644 --- a/channel.h +++ b/channel.h @@ -84,6 +84,8 @@ struct Channel { for this channel (and are awaiting a confirmation or failure). */ + int flushing; + const struct ChanType* type; }; diff --git a/common-channel.c b/common-channel.c index 19b807f..d87ea7d 100644 --- a/common-channel.c +++ b/common-channel.c @@ -148,6 +148,7 @@ struct Channel* newchannel(unsigned int remotechan, newchan->errfd = FD_CLOSED; /* this isn't always set to start with */ newchan->initconn = 0; newchan->await_open = 0; + newchan->flushing = 0; newchan->writebuf = cbuf_new(RECV_MAXWINDOW); newchan->extrabuf = NULL; /* The user code can set it up */ @@ -203,12 +204,14 @@ void channelio(fd_set *readfds, fd_set *writefds) { /* read data and send it over the wire */ if (channel->readfd >= 0 && FD_ISSET(channel->readfd, readfds)) { + TRACE(("send normal readfd")) send_msg_channel_data(channel, 0); } /* read stderr data and send it over the wire */ - if (ERRFD_IS_READ(channel) && - channel->errfd >= 0 && FD_ISSET(channel->errfd, readfds)) { + if (ERRFD_IS_READ(channel) && channel->errfd >= 0 + && FD_ISSET(channel->errfd, readfds)) { + TRACE(("send normal errfd")) send_msg_channel_data(channel, 1); } @@ -265,8 +268,14 @@ static void check_close(struct Channel *channel) { cbuf_getused(channel->writebuf), channel->extrabuf ? cbuf_getused(channel->extrabuf) : 0)) + if (!channel->flushing && channel->type->check_close + && channel->type->check_close(channel)) + { + channel->flushing = 1; + } + if (channel->recv_close && !write_pending(channel)) { - if (! channel->sent_close) { + if (!channel->sent_close) { TRACE(("Sending MSG_CHANNEL_CLOSE in response to same.")) send_msg_channel_close(channel); } @@ -278,6 +287,22 @@ static void check_close(struct Channel *channel) { close_chan_fd(channel, channel->writefd, SHUT_WR); } + /* Special handling for flushing read data after an exit. We + read regardless of whether the select FD was set, + and if there isn't data available, the channel will get closed. */ + if (channel->flushing) { + TRACE(("might send data, flushing")) + if (channel->readfd >= 0 && channel->transwindow > 0) { + TRACE(("send data readfd")) + send_msg_channel_data(channel, 0); + } + if (ERRFD_IS_READ(channel) && channel->readfd >= 0 + && channel->transwindow > 0) { + TRACE(("send data errfd")) + send_msg_channel_data(channel, 1); + } + } + /* If we're not going to send any more data, send EOF */ if (!channel->sent_eof && channel->readfd == FD_CLOSED @@ -287,15 +312,13 @@ static void check_close(struct Channel *channel) { /* And if we can't receive any more data from them either, close up */ if (!channel->sent_close - && channel->writefd == FD_CLOSED && channel->readfd == FD_CLOSED - && channel->errfd == FD_CLOSED) { + && !write_pending(channel)) { + TRACE(("sending close, readfd is closed")) send_msg_channel_close(channel); } - } - /* Check whether a deferred (EINPROGRESS) connect() was successful, and * if so, set up the channel properly. Otherwise, the channel is cleaned up, so * it is important that the channel reference isn't used after a call to this @@ -341,6 +364,9 @@ static void send_msg_channel_close(struct Channel *channel) { channel->sent_eof = 1; channel->sent_close = 1; + close_chan_fd(channel, channel->readfd, SHUT_RD); + close_chan_fd(channel, channel->errfd, SHUT_RDWR); + close_chan_fd(channel, channel->writefd, SHUT_WR); TRACE(("leave send_msg_channel_close")) } @@ -556,6 +582,7 @@ static void send_msg_channel_data(struct Channel *channel, int isextended) { CHECKCLEARTOWRITE(); + TRACE(("enter send_msg_channel_data")) dropbear_assert(!channel->sent_close); if (isextended) { @@ -591,6 +618,9 @@ static void send_msg_channel_data(struct Channel *channel, int isextended) { len = read(fd, buf_getwriteptr(ses.writepayload, maxlen), maxlen); if (len <= 0) { if (len == 0 || errno != EINTR) { + /* This will also get hit in the case of EAGAIN. The only + time we expect to receive EAGAIN is when we're flushing a FD, + in which case it can be treated the same as EOF */ close_chan_fd(channel, fd, SHUT_RD); } ses.writepayload->len = ses.writepayload->pos = 0; @@ -606,6 +636,14 @@ static void send_msg_channel_data(struct Channel *channel, int isextended) { channel->transwindow -= len; encrypt_packet(); + + /* If we receive less data than we requested when flushing, we've + reached the equivalent of EOF */ + if (channel->flushing && len < maxlen) + { + TRACE(("closing from channel, flushing out.")) + close_chan_fd(channel, fd, SHUT_RD); + } TRACE(("leave send_msg_channel_data")) } diff --git a/common-session.c b/common-session.c index a16d1f9..6e1abf3 100644 --- a/common-session.c +++ b/common-session.c @@ -61,6 +61,12 @@ void common_session_init(int sock, char* remotehost) { ses.connecttimeout = 0; + if (pipe(ses.signal_pipe) < 0) { + dropbear_exit("signal pipe failed"); + } + setnonblocking(ses.signal_pipe[0]); + setnonblocking(ses.signal_pipe[1]); + kexfirstinitialise(); /* initialise the kex state */ ses.writepayload = buf_new(MAX_TRANS_PAYLOAD_LEN); @@ -108,7 +114,6 @@ void common_session_init(int sock, char* remotehost) { ses.allowprivport = 0; - TRACE(("leave session_init")) } @@ -132,6 +137,10 @@ void session_loop(void(*loophandler)()) { FD_SET(ses.sock, &writefd); } } + + /* We get woken up when signal handlers write to this pipe. + SIGCHLD in svr-chansession is the only one currently. */ + FD_SET(ses.signal_pipe[0], &readfd); /* set up for channels which require reading/writing */ if (ses.dataallowed) { @@ -155,6 +164,14 @@ void session_loop(void(*loophandler)()) { FD_ZERO(&writefd); FD_ZERO(&readfd); } + + /* We'll just empty out the pipe if required. We don't do + any thing with the data, since the pipe's purpose is purely to + wake up the select() above. */ + if (FD_ISSET(ses.signal_pipe[0], &readfd)) { + char x; + while (read(ses.signal_pipe[0], &x, 1) > 0) {} + } /* check for auth timeout, rekeying required etc */ checktimeouts(); diff --git a/session.h b/session.h index a4ad45f..78e3e57 100644 --- a/session.h +++ b/session.h @@ -123,7 +123,8 @@ struct sshsession { unsigned char lastpacket; /* What the last received packet type was */ - + int signal_pipe[2]; /* stores endpoints of a self-pipe used for + race-free signal handling */ /* KEX/encryption related */ struct KEXState kexstate; diff --git a/svr-chansession.c b/svr-chansession.c index a45e7a6..23cac85 100644 --- a/svr-chansession.c +++ b/svr-chansession.c @@ -120,9 +120,21 @@ static void sesssigchild_handler(int UNUSED(dummy)) { /* we use this to determine how pid exited */ exit->exitsignal = -1; } + + /* Make sure that the main select() loop wakes up */ + while (1) { + /* EAGAIN means the pipe's full, so don't need to write anything */ + /* isserver is just a random byte to write */ + if (write(ses.signal_pipe[1], &ses.isserver, 1) == 1 || errno == EAGAIN) { + break; + } + if (errno == EINTR) { + continue; + } + dropbear_exit("error writing signal pipe"); + } } - sa_chld.sa_handler = sesssigchild_handler; sa_chld.sa_flags = SA_NOCLDSTOP; sigaction(SIGCHLD, &sa_chld, NULL); @@ -244,16 +256,17 @@ static void closechansess(struct Channel *channel) { unsigned int i; struct logininfo *li; - chansess = (struct ChanSess*)channel->typedata; + TRACE(("enter closechansess")) - send_exitsignalstatus(channel); + chansess = (struct ChanSess*)channel->typedata; - TRACE(("enter closechansess")) if (chansess == NULL) { TRACE(("leave closechansess: chansess == NULL")) return; } + send_exitsignalstatus(channel); + m_free(chansess->cmd); m_free(chansess->term); -- cgit v1.2.3 From 2d4d9627a21f11a3e5caa17149e5391e71af256b Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Tue, 24 Jul 2007 15:40:23 +0000 Subject: Rearrange the channel buffer sizes into three neat use-editable values in options.h. Increasing RECV_MAX_WINDOW gives big network performance increases - even with the present buffers (which haven't changed) it performs a lot better. Next step is to make the window size a cmdline option. --HG-- extra : convert_revision : 24c7cb47fb56cf5b82e3bc0859b45ea83038eab0 --- channel.h | 8 -------- cli-chansession.c | 2 +- common-channel.c | 22 +++++++++++----------- common-session.c | 2 +- options.h | 31 ++++++++++++++++++++++++------- packet.c | 4 ++-- 6 files changed, 39 insertions(+), 30 deletions(-) (limited to 'common-session.c') diff --git a/channel.h b/channel.h index 1bfe392..46f023e 100644 --- a/channel.h +++ b/channel.h @@ -45,16 +45,8 @@ /* Not a real type */ #define SSH_OPEN_IN_PROGRESS 99 -#define MAX_CHANNELS 100 /* simple mem restriction, includes each tcp/x11 - connection, so can't be _too_ small */ - #define CHAN_EXTEND_SIZE 3 /* how many extra slots to add when we need more */ -#define RECV_MAXWINDOW 8000 /* tweak */ -#define RECV_WINDOWEXTEND 1000 /* We send a "window extend" every - RECV_WINDOWEXTEND bytes */ -#define RECV_MAXPACKET RECV_MAXWINDOW /* tweak */ - struct ChanType; struct Channel { diff --git a/cli-chansession.c b/cli-chansession.c index 0f0d07a..e4ec45c 100644 --- a/cli-chansession.c +++ b/cli-chansession.c @@ -350,7 +350,7 @@ static int cli_initchansess(struct Channel *channel) { channel->errfd = STDERR_FILENO; setnonblocking(STDERR_FILENO); - channel->extrabuf = cbuf_new(RECV_MAXWINDOW); + channel->extrabuf = cbuf_new(RECV_MAX_WINDOW); if (cli_opts.wantpty) { send_chansess_pty_req(channel); diff --git a/common-channel.c b/common-channel.c index d77a575..ed6e5a2 100644 --- a/common-channel.c +++ b/common-channel.c @@ -150,11 +150,11 @@ struct Channel* newchannel(unsigned int remotechan, newchan->await_open = 0; newchan->flushing = 0; - newchan->writebuf = cbuf_new(RECV_MAXWINDOW); + newchan->writebuf = cbuf_new(RECV_MAX_WINDOW); newchan->extrabuf = NULL; /* The user code can set it up */ - newchan->recvwindow = RECV_MAXWINDOW; + newchan->recvwindow = RECV_MAX_WINDOW; newchan->recvdonelen = 0; - newchan->recvmaxpacket = RECV_MAXPACKET; + newchan->recvmaxpacket = RECV_MAX_PAYLOAD_LEN; ses.channels[i] = newchan; ses.chancount++; @@ -421,7 +421,7 @@ static void writechannel(struct Channel* channel, int fd, circbuffer *cbuf) { channel->recvdonelen = 0; } - dropbear_assert(channel->recvwindow <= RECV_MAXWINDOW); + dropbear_assert(channel->recvwindow <= RECV_MAX_WINDOW); dropbear_assert(channel->recvwindow <= cbuf_getavail(channel->writebuf)); dropbear_assert(channel->extrabuf == NULL || channel->recvwindow <= cbuf_getavail(channel->extrabuf)); @@ -710,7 +710,7 @@ void common_recv_msg_channel_data(struct Channel *channel, int fd, dropbear_assert(channel->recvwindow >= datalen); channel->recvwindow -= datalen; - dropbear_assert(channel->recvwindow <= RECV_MAXWINDOW); + dropbear_assert(channel->recvwindow <= RECV_MAX_WINDOW); TRACE(("leave recv_msg_channel_data")) } @@ -727,10 +727,10 @@ void recv_msg_channel_window_adjust() { incr = buf_getint(ses.payload); TRACE(("received window increment %d", incr)) - incr = MIN(incr, MAX_TRANS_WIN_INCR); + incr = MIN(incr, TRANS_MAX_WIN_INCR); channel->transwindow += incr; - channel->transwindow = MIN(channel->transwindow, MAX_TRANS_WINDOW); + channel->transwindow = MIN(channel->transwindow, TRANS_MAX_WINDOW); } @@ -769,9 +769,9 @@ void recv_msg_channel_open() { remotechan = buf_getint(ses.payload); transwindow = buf_getint(ses.payload); - transwindow = MIN(transwindow, MAX_TRANS_WINDOW); + transwindow = MIN(transwindow, TRANS_MAX_WINDOW); transmaxpacket = buf_getint(ses.payload); - transmaxpacket = MIN(transmaxpacket, MAX_TRANS_PAYLOAD_LEN); + transmaxpacket = MIN(transmaxpacket, TRANS_MAX_PAYLOAD_LEN); /* figure what type of packet it is */ if (typelen > MAX_NAME_LEN) { @@ -970,8 +970,8 @@ int send_msg_channel_open_init(int fd, const struct ChanType *type) { buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_OPEN); buf_putstring(ses.writepayload, type->name, strlen(type->name)); buf_putint(ses.writepayload, chan->index); - buf_putint(ses.writepayload, RECV_MAXWINDOW); - buf_putint(ses.writepayload, RECV_MAXPACKET); + buf_putint(ses.writepayload, RECV_MAX_WINDOW); + buf_putint(ses.writepayload, RECV_MAX_PAYLOAD_LEN); TRACE(("leave send_msg_channel_open_init()")) return DROPBEAR_SUCCESS; diff --git a/common-session.c b/common-session.c index 6e1abf3..b5adad2 100644 --- a/common-session.c +++ b/common-session.c @@ -69,7 +69,7 @@ void common_session_init(int sock, char* remotehost) { kexfirstinitialise(); /* initialise the kex state */ - ses.writepayload = buf_new(MAX_TRANS_PAYLOAD_LEN); + ses.writepayload = buf_new(TRANS_MAX_PAYLOAD_LEN); ses.transseq = 0; ses.readbuf = NULL; diff --git a/options.h b/options.h index 8fd7971..15d4522 100644 --- a/options.h +++ b/options.h @@ -216,6 +216,20 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ * shell/sftp session etc. */ /* #define LOG_COMMANDS */ +/* Window size limits. These tend to be a trade-off between memory + usage and network performance: */ +/* Size of the network receive window. This amount of memory is allocated + as a per-channel receive buffer. Increasing this value can make a + significant difference to network performance. */ +#define RECV_MAX_WINDOW 8192 +/* Maximum size of a received SSH data packet - this _MUST_ be >= 32768 + in order to interoperate with other implementations */ +#define RECV_MAX_PAYLOAD_LEN 32768 +/* Maximum size of a transmitted data packet - this can be any value, + though increasing it may not make a significant difference. */ +#define TRANS_MAX_PAYLOAD_LEN 16384 + + /******************************************************************* * You shouldn't edit below here unless you know you need to. *******************************************************************/ @@ -317,16 +331,19 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ #define MAX_PROPOSED_ALGO 20 /* size/count limits */ - -#define MAX_PACKET_LEN 35000 #define MIN_PACKET_LEN 16 -#define MAX_PAYLOAD_LEN 32768 -#define MAX_TRANS_PAYLOAD_LEN 32768 -#define MAX_TRANS_PACKET_LEN (MAX_TRANS_PAYLOAD_LEN+50) +#define RECV_MAX_PACKET_LEN (MAX(35000, ((RECV_MAX_PAYLOAD_LEN)+100))) + +/* for channel code */ +#define TRANS_MAX_WINDOW 500000000 /* 500MB is sufficient, stopping overflow */ +#define TRANS_MAX_WIN_INCR 500000000 /* overflow prevention */ + +#define RECV_WINDOWEXTEND (RECV_MAX_WINDOW / 3) /* We send a "window extend" every + RECV_WINDOWEXTEND bytes */ -#define MAX_TRANS_WINDOW 500000000 /* 500MB is sufficient, stopping overflow */ -#define MAX_TRANS_WIN_INCR 500000000 /* overflow prevention */ +#define MAX_CHANNELS 100 /* simple mem restriction, includes each tcp/x11 + connection, so can't be _too_ small */ #define MAX_STRING_LEN 1400 /* ~= MAX_PROPOSED_ALGO * MAX_NAME_LEN, also is the max length for a password etc */ diff --git a/packet.c b/packet.c index 9e7c67a..273b9bf 100644 --- a/packet.c +++ b/packet.c @@ -212,7 +212,7 @@ static void read_packet_init() { buf_setpos(ses.readbuf, blocksize); /* check packet length */ - if ((len > MAX_PACKET_LEN) || + if ((len > RECV_MAX_PACKET_LEN) || (len < MIN_PACKET_LEN + macsize) || ((len - macsize) % blocksize != 0)) { dropbear_exit("bad packet size %d", len); @@ -281,7 +281,7 @@ void decrypt_packet() { /* payload length */ /* - 4 - 1 is for LEN and PADLEN values */ len = ses.decryptreadbuf->len - padlen - 4 - 1; - if ((len > MAX_PAYLOAD_LEN) || (len < 1)) { + if ((len > RECV_MAX_PAYLOAD_LEN) || (len < 1)) { dropbear_exit("bad packet size"); } -- cgit v1.2.3 From 57ae0bfedfaa475c4c49621fd4e2b168b5c73d50 Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Fri, 27 Jul 2007 17:13:42 +0000 Subject: Prevent invalid packets being sent during key-exchange, instead queue them until afterwards. This could sometimes terminate connections after 8 hours if (for example) a new TCP forwarded connection was sent at the KEX timeout. --HG-- extra : convert_revision : 48426bd66b8f5ba50045f7ba190d1672745132e2 --- common-session.c | 9 +++++++- packet.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- packet.h | 1 + session.h | 10 +++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) (limited to 'common-session.c') diff --git a/common-session.c b/common-session.c index b5adad2..9b248cf 100644 --- a/common-session.c +++ b/common-session.c @@ -80,9 +80,12 @@ void common_session_init(int sock, char* remotehost) { initqueue(&ses.writequeue); ses.requirenext = SSH_MSG_KEXINIT; - ses.dataallowed = 0; /* don't send data yet, we'll wait until after kex */ + ses.dataallowed = 1; /* we can send data until we actually + send the SSH_MSG_KEXINIT */ ses.ignorenext = 0; ses.lastpacket = 0; + ses.reply_queue_head = NULL; + ses.reply_queue_tail = NULL; /* set all the algos to none */ ses.keys = (struct key_context*)m_malloc(sizeof(struct key_context)); @@ -192,6 +195,10 @@ void session_loop(void(*loophandler)()) { process_packet(); } } + + /* if required, flush out any queued reply packets that + were being held up during a KEX */ + maybe_flush_reply_queue(); /* process pipes etc for the channels, ses.dataallowed == 0 * during rekeying ) */ diff --git a/packet.c b/packet.c index 273b9bf..87921f3 100644 --- a/packet.c +++ b/packet.c @@ -403,7 +403,60 @@ static buffer* buf_decompress(buffer* buf, unsigned int len) { #endif +/* returns 1 if the packet is a valid type during kex (see 7.1 of rfc4253) */ +static int packet_is_okay_kex(unsigned char type) { + if (type >= SSH_MSG_USERAUTH_REQUEST) { + return 0; + } + if (type == SSH_MSG_SERVICE_REQUEST || type == SSH_MSG_SERVICE_ACCEPT) { + return 0; + } + if (type == SSH_MSG_KEXINIT) { + /* XXX should this die horribly if !dataallowed ?? */ + return 0; + } + return 1; +} +static void enqueue_reply_packet() { + struct packetlist * new_item = NULL; + new_item = m_malloc(sizeof(struct packetlist)); + new_item->next = NULL; + + new_item->payload = buf_newcopy(ses.writepayload); + buf_setpos(ses.writepayload, 0); + buf_setlen(ses.writepayload, 0); + + if (ses.reply_queue_tail) { + ses.reply_queue_tail->next = new_item; + } else { + ses.reply_queue_head = new_item; + ses.reply_queue_tail = new_item; + } + TRACE(("leave enqueue_reply_packet")) +} + +void maybe_flush_reply_queue() { + struct packetlist *tmp_item = NULL, *curr_item = NULL; + if (!ses.dataallowed) + { + TRACE(("maybe_empty_reply_queue - no data allowed")) + return; + } + + for (curr_item = ses.reply_queue_head; curr_item; ) { + CHECKCLEARTOWRITE(); + buf_putbytes(ses.writepayload, + curr_item->payload->data, curr_item->payload->len); + + buf_free(curr_item->payload); + tmp_item = curr_item; + curr_item = curr_item->next; + m_free(tmp_item); + encrypt_packet(); + } + ses.reply_queue_head = ses.reply_queue_tail = NULL; +} /* encrypt the writepayload, putting into writebuf, ready for write_packet() * to put on the wire */ @@ -413,9 +466,20 @@ void encrypt_packet() { unsigned char blocksize, macsize; buffer * writebuf; /* the packet which will go on the wire */ buffer * clearwritebuf; /* unencrypted, possibly compressed */ + unsigned char type; + type = ses.writepayload->data[0]; TRACE(("enter encrypt_packet()")) - TRACE(("encrypt_packet type is %d", ses.writepayload->data[0])) + TRACE(("encrypt_packet type is %d", type)) + + if (!ses.dataallowed && !packet_is_okay_kex(type)) { + /* During key exchange only particular packets are allowed. + Since this type isn't OK we just enqueue it to send + after the KEX, see maybe_flush_reply_queue */ + enqueue_reply_packet(); + return; + } + blocksize = ses.keys->trans_algo_crypt->blocksize; macsize = ses.keys->trans_algo_mac->hashsize; diff --git a/packet.h b/packet.h index e9768cd..8fadeb3 100644 --- a/packet.h +++ b/packet.h @@ -35,6 +35,7 @@ void encrypt_packet(); void process_packet(); +void maybe_flush_reply_queue(); typedef struct PacketType { unsigned char type; /* SSH_MSG_FOO */ void (*handler)(); diff --git a/session.h b/session.h index 559fe29..8a95614 100644 --- a/session.h +++ b/session.h @@ -81,6 +81,12 @@ struct key_context { }; +struct packetlist; +struct packetlist { + struct packetlist *next; + buffer * payload; +}; + struct sshsession { /* Is it a client or server? */ @@ -137,6 +143,10 @@ struct sshsession { buffer* kexhashbuf; /* session hash buffer calculated from various packets*/ buffer* transkexinit; /* the kexinit packet we send should be kept so we can add it to the hash when generating keys */ + + /* a list of queued replies that should be sent after a KEX has + concluded (ie, while dataallowed was unset)*/ + struct packetlist *reply_queue_head, *reply_queue_tail; algo_type*(*buf_match_algo)(buffer*buf, algo_type localalgos[], int *goodguess); /* The function to use to choose which algorithm -- cgit v1.2.3 From 75ec4d6510aba8780effddb53eee4a0296015608 Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Wed, 8 Aug 2007 15:12:06 +0000 Subject: - Add -K keepalive flag for dropbear and dbclient - Try to reduce the frequency of select() timeouts - Add a max receive window size of 1MB --HG-- extra : convert_revision : 9aa22036cb511cddb35fbc0e09ad05acb39b64d1 --- cli-runopts.c | 21 ++++++++++++++++++--- common-kex.c | 7 +------ common-session.c | 46 ++++++++++++++++++++++++++++++++++------------ dbclient.1 | 7 +++++++ dropbear.8 | 7 +++++++ includes.h | 1 + kex.h | 2 +- options.h | 7 ++++--- packet.c | 2 ++ process-packet.c | 2 +- runopts.h | 1 + session.h | 9 +++++++-- svr-auth.c | 2 +- svr-main.c | 8 ++------ svr-runopts.c | 27 +++++++++++++++++++++------ svr-session.c | 8 +------- 16 files changed, 109 insertions(+), 48 deletions(-) (limited to 'common-session.c') diff --git a/cli-runopts.c b/cli-runopts.c index f38ccd2..68990fa 100644 --- a/cli-runopts.c +++ b/cli-runopts.c @@ -63,11 +63,14 @@ static void printhelp() { #ifdef ENABLE_CLI_REMOTETCPFWD "-R Remote port forwarding\n" #endif - "-W (default %d, larger may be faster)\n" + "-W (default %d, larger may be faster, max 1MB)\n" + "-K (0 is never, default %d)\n" #ifdef DEBUG_TRACE "-v verbose\n" #endif - ,DROPBEAR_VERSION, cli_opts.progname, DEFAULT_RECV_WINDOW); + ,DROPBEAR_VERSION, cli_opts.progname, + DEFAULT_RECV_WINDOW, DEFAULT_KEEPALIVE); + } void cli_getopts(int argc, char ** argv) { @@ -112,6 +115,7 @@ void cli_getopts(int argc, char ** argv) { */ opts.recv_window = DEFAULT_RECV_WINDOW; char* recv_window_arg = NULL; + char* keepalive_arg = NULL; /* Iterate all the arguments */ for (i = 1; i < (unsigned int)argc; i++) { @@ -207,6 +211,9 @@ void cli_getopts(int argc, char ** argv) { case 'W': next = &recv_window_arg; break; + case 'K': + next = &keepalive_arg; + break; #ifdef DEBUG_TRACE case 'v': debug_trace = 1; @@ -302,11 +309,19 @@ void cli_getopts(int argc, char ** argv) { if (recv_window_arg) { opts.recv_window = atol(recv_window_arg); - if (opts.recv_window == 0) + if (opts.recv_window == 0 || opts.recv_window > MAX_RECV_WINDOW) { dropbear_exit("Bad recv window '%s'", recv_window_arg); } } + if (keepalive_arg) { + opts.keepalive_secs = strtoul(keepalive_arg, NULL, 10); + if (opts.keepalive_secs == 0 && errno == EINVAL) + { + dropbear_exit("Bad keepalive '%s'", keepalive_arg); + } + } + } #ifdef ENABLE_CLI_PUBKEY_AUTH diff --git a/common-kex.c b/common-kex.c index dd36cd1..e9c655d 100644 --- a/common-kex.c +++ b/common-kex.c @@ -188,8 +188,6 @@ void kexfirstinitialise() { /* Reset the kex state, ready for a new negotiation */ static void kexinitialise() { - struct timeval tv; - TRACE(("kexinitialise()")) /* sent/recv'd MSG_KEXINIT */ @@ -206,10 +204,7 @@ static void kexinitialise() { ses.kexstate.datatrans = 0; ses.kexstate.datarecv = 0; - if (gettimeofday(&tv, 0) < 0) { - dropbear_exit("Error getting time"); - } - ses.kexstate.lastkextime = tv.tv_sec; + ses.kexstate.lastkextime = time(NULL); } diff --git a/common-session.c b/common-session.c index 9b248cf..79313f2 100644 --- a/common-session.c +++ b/common-session.c @@ -34,8 +34,10 @@ #include "kex.h" #include "channel.h" #include "atomicio.h" +#include "runopts.h" static void checktimeouts(); +static long select_timeout(); static int ident_readln(int fd, char* buf, int count); struct sshsession ses; /* GLOBAL */ @@ -59,7 +61,8 @@ void common_session_init(int sock, char* remotehost) { ses.sock = sock; ses.maxfd = sock; - ses.connecttimeout = 0; + ses.connect_time = 0; + ses.last_packet_time = 0; if (pipe(ses.signal_pipe) < 0) { dropbear_exit("signal pipe failed"); @@ -129,7 +132,7 @@ void session_loop(void(*loophandler)()) { /* main loop, select()s for all sockets in use */ for(;;) { - timeout.tv_sec = SELECT_TIMEOUT; + timeout.tv_sec = select_timeout(); timeout.tv_usec = 0; FD_ZERO(&writefd); FD_ZERO(&readfd); @@ -359,20 +362,22 @@ static int ident_readln(int fd, char* buf, int count) { return pos+1; } +void send_msg_ignore() { + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_IGNORE); + buf_putstring(ses.writepayload, "", 0); + encrypt_packet(); +} + /* Check all timeouts which are required. Currently these are the time for * user authentication, and the automatic rekeying. */ static void checktimeouts() { - struct timeval tv; - long secs; - - if (gettimeofday(&tv, 0) < 0) { - dropbear_exit("Error getting time"); - } + time_t now; - secs = tv.tv_sec; + now = time(NULL); - if (ses.connecttimeout != 0 && secs > ses.connecttimeout) { + if (ses.connect_time != 0 && now - ses.connect_time >= AUTH_TIMEOUT) { dropbear_close("Timeout before auth"); } @@ -382,10 +387,27 @@ static void checktimeouts() { } if (!ses.kexstate.sentkexinit - && (secs - ses.kexstate.lastkextime >= KEX_REKEY_TIMEOUT - || ses.kexstate.datarecv+ses.kexstate.datatrans >= KEX_REKEY_DATA)){ + && (now - ses.kexstate.lastkextime >= KEX_REKEY_TIMEOUT + || ses.kexstate.datarecv+ses.kexstate.datatrans >= KEX_REKEY_DATA)) { TRACE(("rekeying after timeout or max data reached")) send_msg_kexinit(); } + + if (opts.keepalive_secs > 0 + && now - ses.last_packet_time >= opts.keepalive_secs) { + send_msg_ignore(); + } } +static long select_timeout() { + /* determine the minimum timeout that might be required, so + as to avoid waking when unneccessary */ + long ret = LONG_MAX; + if (KEX_REKEY_TIMEOUT > 0) + ret = MIN(KEX_REKEY_TIMEOUT, ret); + if (AUTH_TIMEOUT > 0) + ret = MIN(AUTH_TIMEOUT, ret); + if (opts.keepalive_secs > 0) + ret = MIN(opts.keepalive_secs, ret); + return ret; +} diff --git a/dbclient.1 b/dbclient.1 index 3e67c08..e72f249 100644 --- a/dbclient.1 +++ b/dbclient.1 @@ -79,6 +79,13 @@ connection will abort as normal. Specify the per-channel receive window buffer size. Increasing this may improve network performance at the expense of memory use. Use -h to see the default buffer size. +.TP +.B \-K \fItimeout_seconds +Ensure that traffic is transmitted at a certain interval in seconds. This is +useful for working around firewalls or routers that drop connections after +a certain period of inactivity. The trade-off is that a session may be +closed if there is a temporary lapse of network connectivity. A setting +if 0 disables keepalives. .SH AUTHOR Matt Johnston (matt@ucc.asn.au). .br diff --git a/dropbear.8 b/dropbear.8 index 70dfb3e..c9c2e79 100644 --- a/dropbear.8 +++ b/dropbear.8 @@ -87,6 +87,13 @@ Allow remote hosts to connect to forwarded ports. Specify the per-channel receive window buffer size. Increasing this may improve network performance at the expense of memory use. Use -h to see the default buffer size. +.TP +.B \-K \fItimeout_seconds +Ensure that traffic is transmitted at a certain interval in seconds. This is +useful for working around firewalls or routers that drop connections after +a certain period of inactivity. The trade-off is that a session may be +closed if there is a temporary lapse of network connectivity. A setting +if 0 disables keepalives. .SH AUTHOR Matt Johnston (matt@ucc.asn.au). .br diff --git a/includes.h b/includes.h index 017de66..03cb1cf 100644 --- a/includes.h +++ b/includes.h @@ -56,6 +56,7 @@ #include #include #include +#include #ifdef HAVE_UTMP_H #include diff --git a/kex.h b/kex.h index 448ad1b..d3dd187 100644 --- a/kex.h +++ b/kex.h @@ -53,7 +53,7 @@ struct KEXState { unsigned donefirstkex : 1; /* Set to 1 after the first kex has completed, ie the transport layer has been set up */ - long lastkextime; /* time of the last kex */ + time_t lastkextime; /* time of the last kex */ unsigned int datatrans; /* data transmitted since last kex */ unsigned int datarecv; /* data received since last kex */ diff --git a/options.h b/options.h index 939206a..722a43a 100644 --- a/options.h +++ b/options.h @@ -231,6 +231,9 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ though increasing it may not make a significant difference. */ #define TRANS_MAX_PAYLOAD_LEN 16384 +/* Ensure that data is transmitted every KEEPALIVE seconds. This can +be overridden at runtime with -K. 0 disables keepalives */ +#define DEFAULT_KEEPALIVE 0 /******************************************************************* * You shouldn't edit below here unless you know you need to. @@ -287,9 +290,6 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ #define _PATH_CP "/bin/cp" -/* Timeouts in seconds */ -#define SELECT_TIMEOUT 20 - /* success/failure defines */ #define DROPBEAR_SUCCESS 0 #define DROPBEAR_FAILURE -1 @@ -343,6 +343,7 @@ etc) slower (perhaps by 50%). Recommended for most small systems. */ #define RECV_WINDOWEXTEND (opts.recv_window / 3) /* We send a "window extend" every RECV_WINDOWEXTEND bytes */ +#define MAX_RECV_WINDOW (1024*1024) /* 1 MB should be enough */ #define MAX_CHANNELS 100 /* simple mem restriction, includes each tcp/x11 connection, so can't be _too_ small */ diff --git a/packet.c b/packet.c index 87921f3..28c7346 100644 --- a/packet.c +++ b/packet.c @@ -71,6 +71,8 @@ void write_packet() { dropbear_exit("error writing"); } } + + ses.last_packet_time = time(NULL); if (written == 0) { ses.remoteclosed(); diff --git a/process-packet.c b/process-packet.c index ba39d9f..d96c1cb 100644 --- a/process-packet.c +++ b/process-packet.c @@ -56,8 +56,8 @@ void process_packet() { switch(type) { case SSH_MSG_IGNORE: + goto out; case SSH_MSG_DEBUG: - TRACE(("received SSH_MSG_IGNORE or SSH_MSG_DEBUG")) goto out; case SSH_MSG_UNIMPLEMENTED: diff --git a/runopts.h b/runopts.h index e430371..d6e8917 100644 --- a/runopts.h +++ b/runopts.h @@ -37,6 +37,7 @@ typedef struct runopts { int listen_fwd_all; #endif unsigned int recv_window; + time_t keepalive_secs; } runopts; diff --git a/session.h b/session.h index 8a95614..e11c959 100644 --- a/session.h +++ b/session.h @@ -45,6 +45,7 @@ void common_session_init(int sock, char* remotehost); void session_loop(void(*loophandler)()); void common_session_cleanup(); void session_identification(); +void send_msg_ignore(); /* Server */ @@ -92,8 +93,9 @@ struct sshsession { /* Is it a client or server? */ unsigned char isserver; - long connecttimeout; /* time to disconnect if we have a timeout (for - userauth etc), or 0 for no timeout */ + time_t connect_time; /* time the connection was established + (cleared after auth once we're not + respecting AUTH_TIMEOUT any more) */ int sock; @@ -131,6 +133,9 @@ struct sshsession { int signal_pipe[2]; /* stores endpoints of a self-pipe used for race-free signal handling */ + + time_t last_packet_time; /* time of the last packet transmission, for + keepalive purposes */ /* KEX/encryption related */ struct KEXState kexstate; diff --git a/svr-auth.c b/svr-auth.c index d0eba9b..ea31e79 100644 --- a/svr-auth.c +++ b/svr-auth.c @@ -357,7 +357,7 @@ void send_msg_userauth_success() { encrypt_packet(); ses.authstate.authdone = 1; - ses.connecttimeout = 0; + ses.connect_time = 0; if (ses.authstate.pw->pw_uid == 0) { diff --git a/svr-main.c b/svr-main.c index 9e0578e..f7ce221 100644 --- a/svr-main.c +++ b/svr-main.c @@ -111,7 +111,6 @@ static void main_inetd() { #ifdef NON_INETD_MODE void main_noinetd() { fd_set fds; - struct timeval seltimeout; unsigned int i, j; int val; int maxsock = -1; @@ -175,9 +174,6 @@ void main_noinetd() { FD_ZERO(&fds); - seltimeout.tv_sec = 60; - seltimeout.tv_usec = 0; - /* listening sockets */ for (i = 0; i < listensockcount; i++) { FD_SET(listensocks[i], &fds); @@ -191,7 +187,7 @@ void main_noinetd() { } } - val = select(maxsock+1, &fds, NULL, NULL, &seltimeout); + val = select(maxsock+1, &fds, NULL, NULL, NULL); if (exitflag) { unlink(svr_opts.pidfile); @@ -199,7 +195,7 @@ void main_noinetd() { } if (val == 0) { - /* timeout reached */ + /* timeout reached - shouldn't happen. eh */ continue; } diff --git a/svr-runopts.c b/svr-runopts.c index f78461b..83c75c3 100644 --- a/svr-runopts.c +++ b/svr-runopts.c @@ -80,7 +80,8 @@ static void printhelp(const char * progname) { #ifdef INETD_MODE "-i Start for inetd\n" #endif - "-W (default %d, larger may be faster)\n" + "-W (default %d, larger may be faster, max 1MB)\n" + "-K (0 is never, default %d)\n" #ifdef DEBUG_TRACE "-v verbose\n" #endif @@ -91,7 +92,8 @@ static void printhelp(const char * progname) { #ifdef DROPBEAR_RSA RSA_PRIV_FILENAME, #endif - DROPBEAR_MAX_PORTS, DROPBEAR_DEFPORT, DROPBEAR_PIDFILE, DEFAULT_RECV_WINDOW); + DROPBEAR_MAX_PORTS, DROPBEAR_DEFPORT, DROPBEAR_PIDFILE, + DEFAULT_RECV_WINDOW, DEFAULT_KEEPALIVE); } void svr_getopts(int argc, char ** argv) { @@ -99,6 +101,8 @@ void svr_getopts(int argc, char ** argv) { unsigned int i; char ** next = 0; int nextisport = 0; + char* recv_window_arg = NULL; + char* keepalive_arg = NULL; /* see printhelp() for options */ svr_opts.rsakeyfile = NULL; @@ -130,7 +134,8 @@ void svr_getopts(int argc, char ** argv) { svr_opts.usingsyslog = 1; #endif opts.recv_window = DEFAULT_RECV_WINDOW; - char* recv_window_arg = NULL; + opts.keepalive_secs = DEFAULT_KEEPALIVE; + #ifdef ENABLE_SVR_REMOTETCPFWD opts.listen_fwd_all = 0; #endif @@ -210,6 +215,9 @@ void svr_getopts(int argc, char ** argv) { case 'W': next = &recv_window_arg; break; + case 'K': + next = &keepalive_arg; + break; #if defined(ENABLE_SVR_PASSWORD_AUTH) || defined(ENABLE_SVR_PAM_AUTH) case 's': svr_opts.noauthpass = 1; @@ -274,14 +282,21 @@ void svr_getopts(int argc, char ** argv) { } - if (recv_window_arg) - { + if (recv_window_arg) { opts.recv_window = atol(recv_window_arg); - if (opts.recv_window == 0) + if (opts.recv_window == 0 || opts.recv_window > MAX_RECV_WINDOW) { dropbear_exit("Bad recv window '%s'", recv_window_arg); } } + + if (keepalive_arg) { + opts.keepalive_secs = strtoul(keepalive_arg, NULL, 10); + if (opts.keepalive_secs == 0 && errno == EINVAL) + { + dropbear_exit("Bad keepalive '%s'", keepalive_arg); + } + } } static void addportandaddress(char* spec) { diff --git a/svr-session.c b/svr-session.c index 3701597..18fab6b 100644 --- a/svr-session.c +++ b/svr-session.c @@ -77,8 +77,6 @@ static const struct ChanType *svr_chantypes[] = { void svr_session(int sock, int childpipe, char* remotehost, char *addrstring) { - struct timeval timeout; - reseedrandom(); crypto_init(); @@ -91,11 +89,7 @@ void svr_session(int sock, int childpipe, chaninitialise(svr_chantypes); svr_chansessinitialise(); - if (gettimeofday(&timeout, 0) < 0) { - dropbear_exit("Error getting time"); - } - - ses.connecttimeout = timeout.tv_sec + AUTH_TIMEOUT; + ses.connect_time = time(NULL); /* set up messages etc */ ses.remoteclosed = svr_remoteclosed; -- cgit v1.2.3