diff --git a/HPN-README b/HPN-README new file mode 100644 index 0000000..2ef5a44 --- /dev/null +++ b/HPN-README @@ -0,0 +1,130 @@ +Notes: + +MULTI-THREADED CIPHER: +The AES cipher in CTR mode has been multithreaded (MTR-AES-CTR). This will allow ssh installations +on hosts with multiple cores to use more than one processing core during encryption. +Tests have show significant throughput performance increases when using MTR-AES-CTR up +to and including a full gigabit per second on quad core systems. It should be possible to +achieve full line rate on dual core systems but OS and data management overhead makes this +more difficult to achieve. The cipher stream from MTR-AES-CTR is entirely compatible with single +thread AES-CTR (ST-AES-CTR) implementations and should be 100% backward compatible. Optimal +performance requires the MTR-AES-CTR mode be enabled on both ends of the connection. +The MTR-AES-CTR replaces ST-AES-CTR and is used in exactly the same way with the same +nomenclature. +Use examples: + ssh -caes128-ctr you@host.com + scp -oCipher=aes256-ctr file you@host.com:~/file + +NONE CIPHER: +To use the NONE option you must have the NoneEnabled switch set on the server and +you *must* have *both* NoneEnabled and NoneSwitch set to yes on the client. The NONE +feature works with ALL ssh subsystems (as far as we can tell) *AS LONG AS* a tty is not +spawned. If a user uses the -T switch to prevent a tty being created the NONE cipher will +be disabled. + +The performance increase will only be as good as the network and TCP stack tuning +on the reciever side of the connection allows. As a rule of thumb a user will need +at least 10Mb/s connection with a 100ms RTT to see a doubling of performance. The +HPN-SSH home page describes this in greater detail. + +http://www.psc.edu/networking/projects/hpn-ssh + +BUFFER SIZES: + +If HPN is disabled the receive buffer size will be set to the +OpenSSH default of 64K. + +If an HPN system connects to a nonHPN system the receive buffer will +be set to the HPNBufferSize value. The default is 2MB but user adjustable. + +If an HPN to HPN connection is established a number of different things might +happen based on the user options and conditions. + +Conditions: HPNBufferSize NOT Set, TCPRcvBufPoll enabled, TCPRcvBuf NOT Set +HPN Buffer Size = up to 64MB +This is the default state. The HPN buffer size will grow to a maximum of 64MB +as the TCP receive buffer grows. The maximum HPN Buffer size of 64MB is +geared towards 10GigE transcontinental connections. + +Conditions: HPNBufferSize NOT Set, TCPRcvBufPoll disabled, TCPRcvBuf NOT Set +HPN Buffer Size = TCP receive buffer value. +Users on non-autotuning systesm should disable TCPRcvBufPoll in the +ssh_cofig and sshd_config + +Conditions: HPNBufferSize SET, TCPRcvBufPoll disabled, TCPRcvBuf NOT Set +HPN Buffer Size = minmum of TCP receive buffer and HPNBufferSize. +This would be the system defined TCP receive buffer (RWIN). + +Conditions: HPNBufferSize SET, TCPRcvBufPoll disabled, TCPRcvBuf SET +HPN Buffer Size = minmum of TCPRcvBuf and HPNBufferSize. +Generally there is no need to set both. + +Conditions: HPNBufferSize SET, TCPRcvBufPoll enabled, TCPRcvBuf NOT Set +HPN Buffer Size = grows to HPNBufferSize +The buffer will grow up to the maximum size specified here. + +Conditions: HPNBufferSize SET, TCPRcvBufPoll enabled, TCPRcvBuf SET +HPN Buffer Size = minmum of TCPRcvBuf and HPNBufferSize. +Generally there is no need to set both of these, especially on autotuning +systems. However, if the users wishes to override the autotuning this would be +one way to do it. + +Conditions: HPNBufferSize NOT Set, TCPRcvBufPoll enabled, TCPRcvBuf SET +HPN Buffer Size = TCPRcvBuf. +This will override autotuning and set the TCP recieve buffer to the user defined +value. + + +HPN Specific Configuration options + +TcpRcvBuf=[int]KB client + set the TCP socket receive buffer to n Kilobytes. It can be set up to the +maximum socket size allowed by the system. This is useful in situations where +the tcp receive window is set low but the maximum buffer size is set +higher (as is typical). This works on a per TCP connection basis. You can also +use this to artifically limit the transfer rate of the connection. In these +cases the throughput will be no more than n/RTT. The minimum buffer size is 1KB. +Default is the current system wide tcp receive buffer size. + +TcpRcvBufPoll=[yes/no] client/server + enable of disable the polling of the tcp receive buffer through the life +of the connection. You would want to make sure that this option is enabled +for systems making use of autotuning kernels (linux 2.4.24+, 2.6, MS Vista) +default is yes. + +NoneEnabled=[yes/no] client/server + enable or disable the use of the None cipher. Care must always be used +when enabling this as it will allow users to send data in the clear. However, +it is important to note that authentication information remains encrypted +even if this option is enabled. Set to no by default. + +NoneSwitch=[yes/no] client + Switch the encryption cipher being used to the None cipher after +authentication takes place. NoneEnabled must be enabled on both the client +and server side of the connection. When the connection switches to the NONE +cipher a warning is sent to STDERR. The connection attempt will fail with an +error if a client requests a NoneSwitch from the server that does not explicitly +have NoneEnabled set to yes. Note: The NONE cipher cannot be used in +interactive (shell) sessions and it will fail silently. Set to no by default. + +HPNDisabled=[yes/no] client/server + In some situations, such as transfers on a local area network, the impact +of the HPN code produces a net decrease in performance. In these cases it is +helpful to disable the HPN functionality. By default HPNDisabled is set to no. + +HPNBufferSize=[int]KB client/server + This is the default buffer size the HPN functionality uses when interacting +with nonHPN SSH installations. Conceptually this is similar to the TcpRcvBuf +option as applied to the internal SSH flow control. This value can range from +1KB to 64MB (1-65536). Use of oversized or undersized buffers can cause performance +problems depending on the length of the network path. The default size of this buffer +is 2MB. + + +Credits: This patch was conceived, designed, and led by Chris Rapier (rapier@psc.edu) + The majority of the actual coding for versions up to HPN12v1 was performed + by Michael Stevens (mstevens@andrew.cmu.edu). The MT-AES-CTR cipher was + implemented by Ben Bennet (ben@psc.edu) and improved by Mike Tasota + (tasota@gmail.com) an NSF REU grant recipient for 2013. + This work was financed, in part, by Cisco System, Inc., the National + Library of Medicine, and the National Science Foundation. diff --git a/Makefile.in b/Makefile.in index 04e1c8e..abe2109 100644 --- a/Makefile.in +++ b/Makefile.in @@ -42,7 +42,7 @@ CC=@CC@ LD=@LD@ CFLAGS=@CFLAGS@ CPPFLAGS=-I. -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@ -LIBS=@LIBS@ +LIBS=@LIBS@ -lpthread K5LIBS=@K5LIBS@ GSSLIBS=@GSSLIBS@ SSHLIBS=@SSHLIBS@ @@ -86,7 +86,7 @@ LIBOPENSSH_OBJS=\ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ authfd.o authfile.o bufaux.o bufbn.o bufec.o buffer.o \ canohost.o channels.o cipher.o cipher-aes.o cipher-aesctr.o \ - cipher-ctr.o cleanup.o \ + cipher-ctr.o cleanup.o cipher-ctr-mt.o \ compat.o crc32.o fatal.o hostfile.o \ log.o match.o moduli.o nchan.o packet.o opacket.o \ readpass.o ttymodes.o xmalloc.o addrmatch.o \ diff --git a/auth2.c b/auth2.c index e003422..81719ba 100644 --- a/auth2.c +++ b/auth2.c @@ -51,6 +51,7 @@ #include "dispatch.h" #include "pathnames.h" #include "buffer.h" +#include "canohost.h" #ifdef GSSAPI #include "ssh-gss.h" @@ -75,6 +76,8 @@ extern Authmethod method_hostbased; extern Authmethod method_gssapi; #endif +static int log_flag = 0; + Authmethod *authmethods[] = { &method_none, &method_pubkey, @@ -226,6 +229,11 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh) service = packet_get_cstring(NULL); method = packet_get_cstring(NULL); debug("userauth-request for user %s service %s method %s", user, service, method); + if (!log_flag) { + logit("SSH: Server;Ltype: Authname;Remote: %s-%d;Name: %s", + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), user); + log_flag = 1; + } debug("attempt %d failures %d", authctxt->attempt, authctxt->failures); if ((style = strchr(user, ':')) != NULL) diff --git a/canohost.h b/canohost.h index 26d6285..25b2599 100644 --- a/canohost.h +++ b/canohost.h @@ -19,7 +19,7 @@ char *get_peer_ipaddr(int); int get_peer_port(int); char *get_local_ipaddr(int); char *get_local_name(int); -int get_local_port(int); +int get_local_port(int); #endif /* _CANOHOST_H */ diff --git a/channels.c b/channels.c index bdee1f3..88df31c 100644 --- a/channels.c +++ b/channels.c @@ -215,6 +215,10 @@ static int rdynamic_connect_finish(struct ssh *, Channel *); /* Setup helper */ static void channel_handler_init(struct ssh_channels *sc); + +static int hpn_disabled = 0; +static int hpn_buffer_size = 2 * 1024 * 1024; + /* -- channel core */ void @@ -391,6 +395,7 @@ channel_new(struct ssh *ssh, char *ctype, int type, int rfd, int wfd, int efd, c->local_window = window; c->local_window_max = window; c->local_maxpacket = maxpack; + c->dynamic_window = 0; c->remote_name = xstrdup(remote_name); c->ctl_chan = -1; c->delayed = 1; /* prevent call to channel_post handler */ @@ -977,10 +982,40 @@ channel_pre_connecting(struct ssh *ssh, Channel *c, FD_SET(c->sock, writeset); } +static int +channel_tcpwinsz(void) +{ + u_int32_t tcpwinsz = 0; + socklen_t optsz = sizeof(tcpwinsz); + int ret = -1; + + /* if we aren't on a socket return 128KB */ + if (!packet_connection_is_on_socket()) + return 128 * 1024; + + ret = getsockopt(packet_get_connection_in(), + SOL_SOCKET, SO_RCVBUF, &tcpwinsz, &optsz); + /* return no more than SSHBUF_SIZE_MAX (currently 256MB) */ + if ((ret == 0) && tcpwinsz > SSHBUF_SIZE_MAX) + tcpwinsz = SSHBUF_SIZE_MAX; + + debug2("tcpwinsz: %d for connection: %d", tcpwinsz, + packet_get_connection_in()); + return tcpwinsz; +} + static void channel_pre_open(struct ssh *ssh, Channel *c, fd_set *readset, fd_set *writeset) { + /* check buffer limits */ + if (!c->tcpwinsz || c->dynamic_window > 0) + c->tcpwinsz = channel_tcpwinsz(); + + /* this may not work and I may have to use a temp variable to hold the + remote window value in this function -CJR */ + c->remote_window = MIN(c->remote_window, 2 * c->tcpwinsz); + if (c->istate == CHAN_INPUT_OPEN && c->remote_window > 0 && sshbuf_len(c->input) < c->remote_window && @@ -2074,13 +2109,20 @@ channel_check_window(struct ssh *ssh, Channel *c) c->local_maxpacket*3) || c->local_window < c->local_window_max/2) && c->local_consumed > 0) { + u_int addition = 0; + /* adjust max window size if we are in a dynamic environment */ + if (c->dynamic_window && (c->tcpwinsz > c->local_window_max)) { + /* grow the window somewhat aggressively to maintain pressure */ + addition = 1.5 * (c->tcpwinsz - c->local_window_max); + c->local_window_max += addition; + } if (!c->have_remote_id) fatal(":%s: channel %d: no remote id", __func__, c->self); if ((r = sshpkt_start(ssh, SSH2_MSG_CHANNEL_WINDOW_ADJUST)) != 0 || (r = sshpkt_put_u32(ssh, c->remote_id)) != 0 || - (r = sshpkt_put_u32(ssh, c->local_consumed)) != 0 || + (r = sshpkt_put_u32(ssh, c->local_consumed + addition)) != 0 || (r = sshpkt_send(ssh)) != 0) { fatal("%s: channel %i: %s", __func__, c->self, ssh_err(r)); @@ -2088,7 +2130,7 @@ channel_check_window(struct ssh *ssh, Channel *c) debug2("channel %d: window %d sent adjust %d", c->self, c->local_window, c->local_consumed); - c->local_window += c->local_consumed; + c->local_window += c->local_consumed + addition; c->local_consumed = 0; } return 1; @@ -2438,7 +2480,7 @@ channel_output_poll_input_open(struct ssh *ssh, Channel *c) size_t len, plen; const u_char *pkt; int r; - + if ((len = sshbuf_len(c->input)) == 0) { if (c->istate == CHAN_INPUT_WAIT_DRAIN) { /* @@ -2484,7 +2526,6 @@ channel_output_poll_input_open(struct ssh *ssh, Channel *c) c->self, ssh_err(r)); } c->remote_window -= plen; - return; } /* Enqueue packet for buffered data. */ @@ -2556,7 +2597,7 @@ channel_output_poll(struct ssh *ssh) c = sc->channels[i]; if (c == NULL) continue; - + /* * We are only interested in channels that can have buffered * incoming data. @@ -2566,10 +2607,10 @@ channel_output_poll(struct ssh *ssh) if ((c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD))) { /* XXX is this true? */ debug3("channel %d: will not send data after close", - c->self); + c->self); continue; } - + /* Get the amount of buffered data for this channel. */ if (c->istate == CHAN_INPUT_OPEN || c->istate == CHAN_INPUT_WAIT_DRAIN) @@ -3258,6 +3299,15 @@ channel_fwd_bind_addr(const char *listen_addr, int *wildcardp, return addr; } + +void +channel_set_hpn(int external_hpn_disabled, int external_hpn_buffer_size) +{ + hpn_disabled = external_hpn_disabled; + hpn_buffer_size = external_hpn_buffer_size; + debug("HPN Disabled: %d, HPN Buffer Size: %d", hpn_disabled, hpn_buffer_size); +} + static int channel_setup_fwd_listener_tcpip(struct ssh *ssh, int type, struct Forward *fwd, int *allocated_listen_port, @@ -3398,9 +3448,11 @@ channel_setup_fwd_listener_tcpip(struct ssh *ssh, int type, } /* Allocate a channel number for the socket. */ + /* explicitly test for hpn disabled option. if true use smaller window size */ c = channel_new(ssh, "port listener", type, sock, sock, -1, - CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, - 0, "port listener", 1); + hpn_disabled ? CHAN_TCP_WINDOW_DEFAULT : hpn_buffer_size, + CHAN_TCP_PACKET_DEFAULT, + 0, "port listener", 1); c->path = xstrdup(host); c->host_port = fwd->connect_port; c->listening_addr = addr == NULL ? NULL : xstrdup(addr); @@ -4459,8 +4511,9 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset, sock = socks[n]; nc = channel_new(ssh, "x11 listener", SSH_CHANNEL_X11_LISTENER, sock, sock, -1, - CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, - 0, "X11 inet listener", 1); + hpn_disabled ? CHAN_X11_WINDOW_DEFAULT : hpn_buffer_size, + CHAN_X11_PACKET_DEFAULT, + 0, "X11 inet listener", 1); nc->single_connection = single_connection; (*chanids)[n] = nc->self; } diff --git a/channels.h b/channels.h index 126b043..fd549c5 100644 --- a/channels.h +++ b/channels.h @@ -141,8 +141,10 @@ struct Channel { u_int local_window_max; u_int local_consumed; u_int local_maxpacket; + int dynamic_window; int extended_usage; int single_connection; + u_int tcpwinsz; char *ctype; /* type */ @@ -336,4 +338,7 @@ void chan_rcvd_ieof(struct ssh *, Channel *); void chan_write_failed(struct ssh *, Channel *); void chan_obuf_empty(struct ssh *, Channel *); +/* hpn handler */ +void channel_set_hpn(int, int); + #endif diff --git a/cipher-ctr-mt.c b/cipher-ctr-mt.c new file mode 100644 index 0000000..9d0390e --- /dev/null +++ b/cipher-ctr-mt.c @@ -0,0 +1,661 @@ +/* + * OpenSSH Multi-threaded AES-CTR Cipher + * + * Author: Benjamin Bennett + * Author: Mike Tasota + * Author: Chris Rapier + * Copyright (c) 2008-2013 Pittsburgh Supercomputing Center. All rights reserved. + * + * Based on original OpenSSH AES-CTR cipher. Small portions remain unchanged, + * Copyright (c) 2003 Markus Friedl + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include "includes.h" + +#if defined(WITH_OPENSSL) +#include + +#include +#include + +#include + +#include "xmalloc.h" +#include "log.h" +#include + +/* compatibility with old or broken OpenSSL versions */ +#include "openbsd-compat/openssl-compat.h" + +#ifndef USE_BUILTIN_RIJNDAEL +#include +#endif + +#include + +/*-------------------- TUNABLES --------------------*/ +/* maximum number of threads and queues */ +#define MAX_THREADS 16 +#define MAX_NUMKQ (MAX_THREADS + 2) + +/* Number of pregen threads to use */ +int cipher_threads = 2; + +/* Number of keystream queues */ +int numkq = 4; + +/* Length of a keystream queue */ +#define KQLEN 4096 + +/* Processor cacheline length */ +#define CACHELINE_LEN 64 + +/* Collect thread stats and print at cancellation when in debug mode */ +#define CIPHER_THREAD_STATS + +/* Can the system do unaligned loads natively? */ +#if defined(__aarch64__) || \ + defined(__i386__) || \ + defined(__powerpc__) || \ + defined(__x86_64__) +# define CIPHER_UNALIGNED_OK +#endif +#if defined(__SIZEOF_INT128__) +# define CIPHER_INT128_OK +#endif +/*-------------------- END TUNABLES --------------------*/ + + +const EVP_CIPHER *evp_aes_ctr_mt(void); + +#ifdef CIPHER_THREAD_STATS +/* + * Struct to collect thread stats + */ +struct thread_stats { + u_int fills; + u_int skips; + u_int waits; + u_int drains; +}; + +/* + * Debug print the thread stats + * Use with pthread_cleanup_push for displaying at thread cancellation + */ +static void +thread_loop_stats(void *x) +{ + struct thread_stats *s = x; + + debug("AES-CTR MT tid %lu - %u fills, %u skips, %u waits", pthread_self(), + s->fills, s->skips, s->waits); +} + +# define STATS_STRUCT(s) struct thread_stats s +# define STATS_INIT(s) { memset(&s, 0, sizeof(s)); } +# define STATS_FILL(s) { s.fills++; } +# define STATS_SKIP(s) { s.skips++; } +# define STATS_WAIT(s) { s.waits++; } +# define STATS_DRAIN(s) { s.drains++; } +#else +# define STATS_STRUCT(s) +# define STATS_INIT(s) +# define STATS_FILL(s) +# define STATS_SKIP(s) +# define STATS_WAIT(s) +# define STATS_DRAIN(s) +#endif + +/* Keystream Queue state */ +enum { + KQINIT, + KQEMPTY, + KQFILLING, + KQFULL, + KQDRAINING +}; + +/* Keystream Queue struct */ +struct kq { + u_char keys[KQLEN][AES_BLOCK_SIZE]; + u_char ctr[AES_BLOCK_SIZE]; + u_char pad0[CACHELINE_LEN]; + int qstate; + pthread_mutex_t lock; + pthread_cond_t cond; + u_char pad1[CACHELINE_LEN]; +}; + +/* Context struct */ +struct ssh_aes_ctr_ctx_mt +{ + int struct_id; + struct kq q[MAX_NUMKQ]; + AES_KEY aes_ctx; + STATS_STRUCT(stats); + u_char aes_counter[AES_BLOCK_SIZE]; + pthread_t tid[MAX_THREADS]; + int id[MAX_THREADS]; + pthread_rwlock_t tid_lock; +#ifdef __APPLE__ + pthread_rwlock_t stop_lock; + int exit_flag; +#endif /* __APPLE__ */ + int state; + int qidx; + int ridx; +}; + +/* + * increment counter 'ctr', + * the counter is of size 'len' bytes and stored in network-byte-order. + * (LSB at ctr[len-1], MSB at ctr[0]) + */ +static void +ssh_ctr_inc(u_char *ctr, size_t len) +{ + int i; + + for (i = len - 1; i >= 0; i--) + if (++ctr[i]) /* continue on overflow */ + return; +} + +/* + * Add num to counter 'ctr' + */ +static void +ssh_ctr_add(u_char *ctr, uint32_t num, u_int len) +{ + int i; + uint16_t n; + + for (n = 0, i = len - 1; i >= 0 && (num || n); i--) { + n = ctr[i] + (num & 0xff) + n; + num >>= 8; + ctr[i] = n & 0xff; + n >>= 8; + } +} + +/* + * Threads may be cancelled in a pthread_cond_wait, we must free the mutex + */ +static void +thread_loop_cleanup(void *x) +{ + pthread_mutex_unlock((pthread_mutex_t *)x); +} + +#ifdef __APPLE__ +/* Check if we should exit, we are doing both cancel and exit condition + * since on OSX threads seem to occasionally fail to notice when they have + * been cancelled. We want to have a backup to make sure that we won't hang + * when the main process join()-s the cancelled thread. + */ +static void +thread_loop_check_exit(struct ssh_aes_ctr_ctx_mt *c) +{ + int exit_flag; + + pthread_rwlock_rdlock(&c->stop_lock); + exit_flag = c->exit_flag; + pthread_rwlock_unlock(&c->stop_lock); + + if (exit_flag) + pthread_exit(NULL); +} +#else +# define thread_loop_check_exit(s) +#endif /* __APPLE__ */ + +/* + * Helper function to terminate the helper threads + */ +static void +stop_and_join_pregen_threads(struct ssh_aes_ctr_ctx_mt *c) +{ + int i; + +#ifdef __APPLE__ + /* notify threads that they should exit */ + pthread_rwlock_wrlock(&c->stop_lock); + c->exit_flag = TRUE; + pthread_rwlock_unlock(&c->stop_lock); +#endif /* __APPLE__ */ + + /* Cancel pregen threads */ + for (i = 0; i < cipher_threads; i++) { + debug ("Canceled %lu (%d,%d)", c->tid[i], c->struct_id, c->id[i]); + pthread_cancel(c->tid[i]); + } + for (i = 0; i < numkq; i++) { + pthread_mutex_lock(&c->q[i].lock); + pthread_cond_broadcast(&c->q[i].cond); + pthread_mutex_unlock(&c->q[i].lock); + } + for (i = 0; i < cipher_threads; i++) { + if (pthread_kill(c->tid[i], 0) != 0) + debug3("AES-CTR MT pthread_join failure: Invalid thread id %lu in %s", c->tid[i], __FUNCTION__); + else { + debug ("Joining %lu (%d, %d)", c->tid[i], c->struct_id, c->id[i]); + pthread_join(c->tid[i], NULL); + } + } +} + +/* + * The life of a pregen thread: + * Find empty keystream queues and fill them using their counter. + * When done, update counter for the next fill. + */ +static void * +thread_loop(void *x) +{ + AES_KEY key; + STATS_STRUCT(stats); + struct ssh_aes_ctr_ctx_mt *c = x; + struct kq *q; + int i; + int qidx; + pthread_t first_tid; + + /* Threads stats on cancellation */ + STATS_INIT(stats); +#ifdef CIPHER_THREAD_STATS + pthread_cleanup_push(thread_loop_stats, &stats); +#endif + + /* Thread local copy of AES key */ + memcpy(&key, &c->aes_ctx, sizeof(key)); + + pthread_rwlock_rdlock(&c->tid_lock); + first_tid = c->tid[0]; + pthread_rwlock_unlock(&c->tid_lock); + + /* + * Handle the special case of startup, one thread must fill + * the first KQ then mark it as draining. Lock held throughout. + */ + if (pthread_equal(pthread_self(), first_tid)) { + q = &c->q[0]; + pthread_mutex_lock(&q->lock); + if (q->qstate == KQINIT) { + for (i = 0; i < KQLEN; i++) { + AES_encrypt(q->ctr, q->keys[i], &key); + ssh_ctr_inc(q->ctr, AES_BLOCK_SIZE); + } + ssh_ctr_add(q->ctr, KQLEN * (numkq - 1), AES_BLOCK_SIZE); + q->qstate = KQDRAINING; + STATS_FILL(stats); + pthread_cond_broadcast(&q->cond); + } + pthread_mutex_unlock(&q->lock); + } else + STATS_SKIP(stats); + + /* + * Normal case is to find empty queues and fill them, skipping over + * queues already filled by other threads and stopping to wait for + * a draining queue to become empty. + * + * Multiple threads may be waiting on a draining queue and awoken + * when empty. The first thread to wake will mark it as filling, + * others will move on to fill, skip, or wait on the next queue. + */ + for (qidx = 1;; qidx = (qidx + 1) % numkq) { + /* Check if I was cancelled, also checked in cond_wait */ + pthread_testcancel(); + + /* Check if we should exit as well */ + thread_loop_check_exit(c); + + /* Lock queue and block if its draining */ + q = &c->q[qidx]; + pthread_mutex_lock(&q->lock); + pthread_cleanup_push(thread_loop_cleanup, &q->lock); + while (q->qstate == KQDRAINING || q->qstate == KQINIT) { + STATS_WAIT(stats); + thread_loop_check_exit(c); + pthread_cond_wait(&q->cond, &q->lock); + } + pthread_cleanup_pop(0); + + /* If filling or full, somebody else got it, skip */ + if (q->qstate != KQEMPTY) { + pthread_mutex_unlock(&q->lock); + STATS_SKIP(stats); + continue; + } + + /* + * Empty, let's fill it. + * Queue lock is relinquished while we do this so others + * can see that it's being filled. + */ + q->qstate = KQFILLING; + pthread_cond_broadcast(&q->cond); + pthread_mutex_unlock(&q->lock); + for (i = 0; i < KQLEN; i++) { + AES_encrypt(q->ctr, q->keys[i], &key); + ssh_ctr_inc(q->ctr, AES_BLOCK_SIZE); + } + + /* Re-lock, mark full and signal consumer */ + pthread_mutex_lock(&q->lock); + ssh_ctr_add(q->ctr, KQLEN * (numkq - 1), AES_BLOCK_SIZE); + q->qstate = KQFULL; + STATS_FILL(stats); + pthread_cond_broadcast(&q->cond); + pthread_mutex_unlock(&q->lock); + } + +#ifdef CIPHER_THREAD_STATS + /* Stats */ + pthread_cleanup_pop(1); +#endif + + return NULL; +} + +static int +ssh_aes_ctr(EVP_CIPHER_CTX *ctx, u_char *dest, const u_char *src, + LIBCRYPTO_EVP_INL_TYPE len) +{ + typedef union { +#ifdef CIPHER_INT128_OK + __uint128_t *u128; +#endif + uint64_t *u64; + uint32_t *u32; + uint8_t *u8; + const uint8_t *cu8; + uintptr_t u; + } ptrs_t; + ptrs_t destp, srcp, bufp; + uintptr_t align; + struct ssh_aes_ctr_ctx_mt *c; + struct kq *q, *oldq; + int ridx; + u_char *buf; + + if (len == 0) + return 1; + if ((c = EVP_CIPHER_CTX_get_app_data(ctx)) == NULL) + return 0; + + q = &c->q[c->qidx]; + ridx = c->ridx; + + /* src already padded to block multiple */ + srcp.cu8 = src; + destp.u8 = dest; + while (len > 0) { + buf = q->keys[ridx]; + bufp.u8 = buf; + + /* figure out the alignment on the fly */ +#ifdef CIPHER_UNALIGNED_OK + align = 0; +#else + align = destp.u | srcp.u | bufp.u; +#endif + +#ifdef CIPHER_INT128_OK + if ((align & 0xf) == 0) { + destp.u128[0] = srcp.u128[0] ^ bufp.u128[0]; + } else +#endif + if ((align & 0x7) == 0) { + destp.u64[0] = srcp.u64[0] ^ bufp.u64[0]; + destp.u64[1] = srcp.u64[1] ^ bufp.u64[1]; + } else if ((align & 0x3) == 0) { + destp.u32[0] = srcp.u32[0] ^ bufp.u32[0]; + destp.u32[1] = srcp.u32[1] ^ bufp.u32[1]; + destp.u32[2] = srcp.u32[2] ^ bufp.u32[2]; + destp.u32[3] = srcp.u32[3] ^ bufp.u32[3]; + } else { + size_t i; + for (i = 0; i < AES_BLOCK_SIZE; ++i) + dest[i] = src[i] ^ buf[i]; + } + + destp.u += AES_BLOCK_SIZE; + srcp.u += AES_BLOCK_SIZE; + len -= AES_BLOCK_SIZE; + ssh_ctr_inc(ctx->iv, AES_BLOCK_SIZE); + + /* Increment read index, switch queues on rollover */ + if ((ridx = (ridx + 1) % KQLEN) == 0) { + oldq = q; + + /* Mark next queue draining, may need to wait */ + c->qidx = (c->qidx + 1) % numkq; + q = &c->q[c->qidx]; + pthread_mutex_lock(&q->lock); + while (q->qstate != KQFULL) { + STATS_WAIT(c->stats); + pthread_cond_wait(&q->cond, &q->lock); + } + q->qstate = KQDRAINING; + pthread_cond_broadcast(&q->cond); + pthread_mutex_unlock(&q->lock); + + /* Mark consumed queue empty and signal producers */ + pthread_mutex_lock(&oldq->lock); + oldq->qstate = KQEMPTY; + STATS_DRAIN(c->stats); + pthread_cond_broadcast(&oldq->cond); + pthread_mutex_unlock(&oldq->lock); + } + } + c->ridx = ridx; + return 1; +} + +#define HAVE_NONE 0 +#define HAVE_KEY 1 +#define HAVE_IV 2 + +int X = 0; + +static int +ssh_aes_ctr_init(EVP_CIPHER_CTX *ctx, const u_char *key, const u_char *iv, + int enc) +{ + struct ssh_aes_ctr_ctx_mt *c; + int i; + + /* get the number of cores in the system */ + /* if it's not linux it currently defaults to 2 */ + /* divide by 2 to get threads for each direction (MODE_IN||MODE_OUT) */ +#ifdef __linux__ + cipher_threads = sysconf(_SC_NPROCESSORS_ONLN) / 2; +#endif /*__linux__*/ +#ifdef __APPLE__ + cipher_threads = sysconf(_SC_NPROCESSORS_ONLN) / 2; +#endif /*__APPLE__*/ +#ifdef __FREEBSD__ + int req[2]; + size_t len; + + req[0] = CTL_HW; + req[1] = HW_NCPU; + + len = sizeof(ncpu); + sysctl(req, 2, &cipher_threads, &len, NULL, 0); + cipher_threads = cipher_threads/2; +#endif /*__FREEBSD__*/ + + /* if they have less than 4 cores spin up 4 threads anyway */ + if (cipher_threads < 2) + cipher_threads = 2; + + /* assure that we aren't trying to create more threads than we have in the struct */ + if (cipher_threads > MAX_THREADS) + cipher_threads = MAX_THREADS; + + /* set the number of keystream queues */ + numkq = cipher_threads + 2; + + if ((c = EVP_CIPHER_CTX_get_app_data(ctx)) == NULL) { + c = xmalloc(sizeof(*c)); + pthread_rwlock_init(&c->tid_lock, NULL); +#ifdef __APPLE__ + pthread_rwlock_init(&c->stop_lock, NULL); + c->exit_flag = FALSE; +#endif /* __APPLE__ */ + + c->state = HAVE_NONE; + for (i = 0; i < numkq; i++) { + pthread_mutex_init(&c->q[i].lock, NULL); + pthread_cond_init(&c->q[i].cond, NULL); + } + + STATS_INIT(c->stats); + EVP_CIPHER_CTX_set_app_data(ctx, c); + } + + if (c->state == (HAVE_KEY | HAVE_IV)) { + /* tell the pregen threads to exit */ + stop_and_join_pregen_threads(c); + +#ifdef __APPLE__ + /* reset the exit flag */ + c->exit_flag = FALSE; +#endif /* __APPLE__ */ + + /* Start over getting key & iv */ + c->state = HAVE_NONE; + } + + if (key != NULL) { + AES_set_encrypt_key(key, EVP_CIPHER_CTX_key_length(ctx) * 8, + &c->aes_ctx); + c->state |= HAVE_KEY; + } + + if (iv != NULL) { + memcpy(ctx->iv, iv, AES_BLOCK_SIZE); + c->state |= HAVE_IV; + } + + if (c->state == (HAVE_KEY | HAVE_IV)) { + /* Clear queues */ + memcpy(c->q[0].ctr, ctx->iv, AES_BLOCK_SIZE); + c->q[0].qstate = KQINIT; + for (i = 1; i < numkq; i++) { + memcpy(c->q[i].ctr, ctx->iv, AES_BLOCK_SIZE); + ssh_ctr_add(c->q[i].ctr, i * KQLEN, AES_BLOCK_SIZE); + c->q[i].qstate = KQEMPTY; + } + c->qidx = 0; + c->ridx = 0; + + /* Start threads */ + for (i = 0; i < cipher_threads; i++) { + pthread_rwlock_wrlock(&c->tid_lock); + if (pthread_create(&c->tid[i], NULL, thread_loop, c) != 0) + debug ("AES-CTR MT Could not create thread in %s", __FUNCTION__); /*should die here */ + else { + if (!c->struct_id) + c->struct_id = X++; + c->id[i] = i; + debug ("AES-CTR MT spawned a thread with id %lu in %s (%d, %d)", c->tid[i], __FUNCTION__, c->struct_id, c->id[i]); + } + pthread_rwlock_unlock(&c->tid_lock); + } + pthread_mutex_lock(&c->q[0].lock); + while (c->q[0].qstate == KQINIT) + pthread_cond_wait(&c->q[0].cond, &c->q[0].lock); + pthread_mutex_unlock(&c->q[0].lock); + } + return 1; +} + +/* this function is no longer used but might prove handy in the future + * this comment also applies to ssh_aes_ctr_thread_reconstruction + */ +void +ssh_aes_ctr_thread_destroy(EVP_CIPHER_CTX *ctx) +{ + struct ssh_aes_ctr_ctx_mt *c; + + c = EVP_CIPHER_CTX_get_app_data(ctx); + stop_and_join_pregen_threads(c); +} + +void +ssh_aes_ctr_thread_reconstruction(EVP_CIPHER_CTX *ctx) +{ + struct ssh_aes_ctr_ctx_mt *c; + int i; + c = EVP_CIPHER_CTX_get_app_data(ctx); + /* reconstruct threads */ + for (i = 0; i < cipher_threads; i++) { + pthread_rwlock_wrlock(&c->tid_lock); + if (pthread_create(&c->tid[i], NULL, thread_loop, c) !=0 ) + debug("AES-CTR MT could not create thread in %s", __FUNCTION__); + else { + c->struct_id = X++; + c->id[i] = i; + debug ("AES-CTR MT spawned a thread with id %lu in %s (%d, %d)", c->tid[i], __FUNCTION__, c->struct_id, c->id[i]); + debug("AES-CTR MT spawned a thread with id %lu in %s", c->tid[i], __FUNCTION__); + } + pthread_rwlock_unlock(&c->tid_lock); + } +} + +static int +ssh_aes_ctr_cleanup(EVP_CIPHER_CTX *ctx) +{ + struct ssh_aes_ctr_ctx_mt *c; + + if ((c = EVP_CIPHER_CTX_get_app_data(ctx)) != NULL) { +#ifdef CIPHER_THREAD_STATS + debug("AES-CTR MT main thread: %u drains, %u waits", c->stats.drains, + c->stats.waits); +#endif + stop_and_join_pregen_threads(c); + + memset(c, 0, sizeof(*c)); + free(c); + EVP_CIPHER_CTX_set_app_data(ctx, NULL); + } + return 1; +} + +/* */ +const EVP_CIPHER * +evp_aes_ctr_mt(void) +{ + static EVP_CIPHER aes_ctr; + + memset(&aes_ctr, 0, sizeof(EVP_CIPHER)); + aes_ctr.nid = NID_undef; + aes_ctr.block_size = AES_BLOCK_SIZE; + aes_ctr.iv_len = AES_BLOCK_SIZE; + aes_ctr.key_len = 16; + aes_ctr.init = ssh_aes_ctr_init; + aes_ctr.cleanup = ssh_aes_ctr_cleanup; + aes_ctr.do_cipher = ssh_aes_ctr; +#ifndef SSH_OLD_EVP + aes_ctr.flags = EVP_CIPH_CBC_MODE | EVP_CIPH_VARIABLE_LENGTH | + EVP_CIPH_ALWAYS_CALL_INIT | EVP_CIPH_CUSTOM_IV; +#endif + return &aes_ctr; +} + +#endif /* defined(WITH_OPENSSL) */ diff --git a/cipher.c b/cipher.c index 5787636..0655547 100644 --- a/cipher.c +++ b/cipher.c @@ -52,6 +52,9 @@ #include "openbsd-compat/openssl-compat.h" +/* for multi-threaded aes-ctr cipher */ +extern const EVP_CIPHER *evp_aes_ctr_mt(void); + struct sshcipher_ctx { int plaintext; int encrypt; @@ -80,7 +83,7 @@ struct sshcipher { #endif }; -static const struct sshcipher ciphers[] = { +static struct sshcipher ciphers[] = { #ifdef WITH_OPENSSL { "3des-cbc", 8, 24, 0, 0, CFLAG_CBC, EVP_des_ede3_cbc }, { "aes128-cbc", 16, 16, 0, 0, CFLAG_CBC, EVP_aes_128_cbc }, @@ -138,6 +141,29 @@ cipher_alg_list(char sep, int auth_only) return ret; } +/* used to get the cipher name so when force rekeying to handle the + * single to multithreaded ctr cipher swap we only rekey when appropriate + */ +const char * +cipher_ctx_name(const struct sshcipher_ctx *cc) +{ + return cc->cipher->name; +} + +/* in order to get around sandbox and forking issues with a threaded cipher + * we set the initial pre-auth aes-ctr cipher to the default OpenSSH cipher + * post auth we set them to the new evp as defined by cipher-ctr-mt + */ +#ifdef WITH_OPENSSL +void +cipher_reset_multithreaded(void) +{ + cipher_by_name("aes128-ctr")->evptype = evp_aes_ctr_mt; + cipher_by_name("aes192-ctr")->evptype = evp_aes_ctr_mt; + cipher_by_name("aes256-ctr")->evptype = evp_aes_ctr_mt; +} +#endif + u_int cipher_blocksize(const struct sshcipher *c) { @@ -187,10 +213,10 @@ cipher_ctx_is_plaintext(struct sshcipher_ctx *cc) return cc->plaintext; } -const struct sshcipher * +struct sshcipher * cipher_by_name(const char *name) { - const struct sshcipher *c; + struct sshcipher *c; for (c = ciphers; c->name != NULL; c++) if (strcmp(c->name, name) == 0) return c; @@ -212,7 +238,8 @@ ciphers_valid(const char *names) for ((p = strsep(&cp, CIPHER_SEP)); p && *p != '\0'; (p = strsep(&cp, CIPHER_SEP))) { c = cipher_by_name(p); - if (c == NULL || (c->flags & CFLAG_INTERNAL) != 0) { + if (c == NULL || ((c->flags & CFLAG_INTERNAL) != 0 && + (c->flags & CFLAG_NONE) != 0)) { free(cipher_list); return 0; } diff --git a/cipher.h b/cipher.h index dc7ecf1..7cba847 100644 --- a/cipher.h +++ b/cipher.h @@ -48,7 +48,9 @@ struct sshcipher; struct sshcipher_ctx; -const struct sshcipher *cipher_by_name(const char *); +void ssh_aes_ctr_thread_destroy(EVP_CIPHER_CTX *ctx); // defined in cipher-ctr-mt.c +void ssh_aes_ctr_thread_reconstruction(EVP_CIPHER_CTX *ctx); +struct sshcipher *cipher_by_name(const char *); const char *cipher_warning_message(const struct sshcipher_ctx *); int ciphers_valid(const char *); char *cipher_alg_list(char, int); @@ -65,6 +67,8 @@ u_int cipher_seclen(const struct sshcipher *); u_int cipher_authlen(const struct sshcipher *); u_int cipher_ivlen(const struct sshcipher *); u_int cipher_is_cbc(const struct sshcipher *); +void cipher_reset_multithreaded(void); +const char *cipher_ctx_name(const struct sshcipher_ctx *); u_int cipher_ctx_is_plaintext(struct sshcipher_ctx *); diff --git a/clientloop.c b/clientloop.c index 7bcf22e..fcc5e0c 100644 --- a/clientloop.c +++ b/clientloop.c @@ -1549,9 +1549,11 @@ client_request_x11(struct ssh *ssh, const char *request_type, int rchan) sock = x11_connect_display(ssh); if (sock < 0) return NULL; - c = channel_new(ssh, "x11", - SSH_CHANNEL_X11_OPEN, sock, sock, -1, - CHAN_TCP_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, 0, "x11", 1); + c = channel_new(ssh, "x11", + SSH_CHANNEL_X11_OPEN, sock, sock, -1, + /* again is this really necessary for X11? */ + options.hpn_disabled ? CHAN_TCP_WINDOW_DEFAULT : options.hpn_buffer_size, + CHAN_X11_PACKET_DEFAULT, 0, "x11", 1); c->force_drain = 1; return c; } @@ -1575,9 +1577,10 @@ client_request_agent(struct ssh *ssh, const char *request_type, int rchan) return NULL; } c = channel_new(ssh, "authentication agent connection", - SSH_CHANNEL_OPEN, sock, sock, -1, - CHAN_X11_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, - "authentication agent connection", 1); + SSH_CHANNEL_OPEN, sock, sock, -1, + options.hpn_disabled ? CHAN_X11_WINDOW_DEFAULT : options.hpn_buffer_size, + CHAN_TCP_PACKET_DEFAULT, 0, + "authentication agent connection", 1); c->force_drain = 1; return c; } @@ -1602,10 +1605,13 @@ client_request_tun_fwd(struct ssh *ssh, int tun_mode, } debug("Tunnel forwarding using interface %s", ifname); - c = channel_new(ssh, "tun", SSH_CHANNEL_OPENING, fd, fd, -1, - CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); + c = channel_new(ssh, "tun", SSH_CHANNEL_OPENING, fd, fd, -1, + options.hpn_disabled ? CHAN_TCP_WINDOW_DEFAULT : options.hpn_buffer_size, + CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); c->datagram = 1; + + #if defined(SSH_TUN_FILTER) if (options.tun_open == SSH_TUNMODE_POINTOPOINT) channel_register_filter(ssh, c->self, sys_tun_infilter, diff --git a/compat.c b/compat.c index 861e9e2..6c2dc00 100644 --- a/compat.c +++ b/compat.c @@ -137,6 +137,13 @@ compat_datafellows(const char *version) debug("match: %s pat %s compat 0x%08x", version, check[i].pat, check[i].bugs); datafellows = check[i].bugs; /* XXX for now */ + /* Check to see if the remote side is OpenSSH and not HPN */ + if (strstr(version, "OpenSSH") != NULL) { + if (strstr(version, "hpn") == NULL) { + datafellows |= SSH_BUG_LARGEWINDOW; + debug("Remote is NON-HPN aware"); + } + } return check[i].bugs; } } diff --git a/compat.h b/compat.h index 4fee349..d99e46a 100644 --- a/compat.h +++ b/compat.h @@ -62,6 +62,7 @@ #define SSH_BUG_CURVE25519PAD 0x10000000 #define SSH_BUG_HOSTKEYS 0x20000000 #define SSH_BUG_DHGEX_LARGE 0x40000000 +#define SSH_BUG_LARGEWINDOW 0x80000000 u_int compat_datafellows(const char *); int proto_spec(const char *); diff --git a/kex.c b/kex.c index 15ea28b..a0ce871 100644 --- a/kex.c +++ b/kex.c @@ -52,6 +52,7 @@ #include "ssherr.h" #include "sshbuf.h" +#include "canohost.h" #include "digest.h" /* prototype */ @@ -768,6 +769,11 @@ kex_choose_conf(struct ssh *ssh) int nenc, nmac, ncomp; u_int mode, ctos, need, dh_need, authlen; int r, first_kex_follows; + int auth_flag; + int log_flag = 0; + + auth_flag = packet_authentication_state(ssh); + debug("AUTH STATE IS %d", auth_flag); debug2("local %s KEXINIT proposal", kex->server ? "server" : "client"); if ((r = kex_buf2prop(kex->my, NULL, &my)) != 0) @@ -838,11 +844,35 @@ kex_choose_conf(struct ssh *ssh) peer[ncomp] = NULL; goto out; } + debug("REQUESTED ENC.NAME is '%s'", newkeys->enc.name); + if (strcmp(newkeys->enc.name, "none") == 0) { + debug("Requesting NONE. Authflag is %d", auth_flag); + if (auth_flag == 1) + debug("None requested post authentication."); + else + fatal("Pre-authentication none cipher requests are not allowed."); + } debug("kex: %s cipher: %s MAC: %s compression: %s", ctos ? "client->server" : "server->client", newkeys->enc.name, authlen == 0 ? newkeys->mac.name : "", newkeys->comp.name); + /* + * client starts with ctos = 0 && log flag = 0 and no log. + * 2nd client pass ctos = 1 and flag = 1 so no log. + * server starts with ctos = 1 && log_flag = 0 so log. + * 2nd sever pass ctos = 1 && log flag = 1 so no log. + * -cjr + */ + if (ctos && !log_flag) { + logit("SSH: Server;Ltype: Kex;Remote: %s-%d;Enc: %s;MAC: %s;Comp: %s", + ssh_remote_ipaddr(ssh), + ssh_remote_port(ssh), + newkeys->enc.name, + authlen == 0 ? newkeys->mac.name : "", + newkeys->comp.name); + } + log_flag = 1; } need = dh_need = 0; for (mode = 0; mode < MODE_MAX; mode++) { diff --git a/opacket.c b/opacket.c index fca48e5..c06cd28 100644 --- a/opacket.c +++ b/opacket.c @@ -99,13 +99,15 @@ ssh_packet_put_ecpoint(struct ssh *ssh, const EC_GROUP *curve, # endif #endif /* WITH_OPENSSL */ -void +int ssh_packet_send(struct ssh *ssh) { int r; if ((r = sshpkt_send(ssh)) != 0) fatal("%s: %s", __func__, ssh_err(r)); + + return r; } u_int @@ -275,13 +277,15 @@ packet_write_wait(void) sshpkt_fatal(active_state, __func__, r); } -void +int packet_write_poll(void) { int r; if ((r = ssh_packet_write_poll(active_state)) != 0) sshpkt_fatal(active_state, __func__, r); + + return r; } void diff --git a/opacket.h b/opacket.h index b2c2e7f..188dc55 100644 --- a/opacket.h +++ b/opacket.h @@ -12,7 +12,7 @@ void ssh_packet_put_ecpoint(struct ssh *, const EC_GROUP *, const EC_POINT * void ssh_packet_put_string(struct ssh *, const void *buf, u_int len); void ssh_packet_put_cstring(struct ssh *, const char *str); void ssh_packet_put_raw(struct ssh *, const void *buf, u_int len); -void ssh_packet_send(struct ssh *); +int ssh_packet_send(struct ssh *); u_int ssh_packet_get_char(struct ssh *); u_int ssh_packet_get_int(struct ssh *); @@ -43,7 +43,7 @@ int packet_read_seqnr(u_int32_t *); int packet_read_poll_seqnr(u_int32_t *); void packet_process_incoming(const char *buf, u_int len); void packet_write_wait(void); -void packet_write_poll(void); +int packet_write_poll(void); void packet_read_expect(int expected_type); #define packet_set_timeout(timeout, count) \ ssh_packet_set_timeout(active_state, (timeout), (count)) diff --git a/packet.c b/packet.c index 4bfb507..3e6858d 100644 --- a/packet.c +++ b/packet.c @@ -280,7 +280,7 @@ struct ssh * ssh_packet_set_connection(struct ssh *ssh, int fd_in, int fd_out) { struct session_state *state; - const struct sshcipher *none = cipher_by_name("none"); + struct sshcipher *none = cipher_by_name("none"); int r; if (none == NULL) { @@ -926,6 +926,24 @@ ssh_set_newkeys(struct ssh *ssh, int mode) return 0; } +/* this supports the forced rekeying required for the NONE cipher */ +int rekey_requested = 0; +void +packet_request_rekeying(void) +{ + rekey_requested = 1; +} + +/* used to determine if pre or post auth when rekeying for aes-ctr + * and none cipher switch */ +int +packet_authentication_state(const struct ssh *ssh) +{ + struct session_state *state = ssh->state; + + return state->after_authentication; +} + #define MAX_PACKETS (1U<<31) static int ssh_packet_need_rekeying(struct ssh *ssh, u_int outbound_packet_len) @@ -952,6 +970,13 @@ ssh_packet_need_rekeying(struct ssh *ssh, u_int outbound_packet_len) if (state->p_send.packets == 0 && state->p_read.packets == 0) return 0; + /* used to force rekeying when called for by the none + * cipher switch and aes-mt-ctr methods -cjr */ + if (rekey_requested == 1) { + rekey_requested = 0; + return 1; + } + /* Time-based rekeying */ if (state->rekey_interval != 0 && (int64_t)state->rekey_time + state->rekey_interval <= monotime()) @@ -2684,3 +2709,10 @@ sshpkt_add_padding(struct ssh *ssh, u_char pad) ssh->state->extra_pad = pad; return 0; } + +/* need this for the moment for the aes-ctr cipher */ +void * +ssh_packet_get_send_context(struct ssh *ssh) +{ + return ssh->state->send_context; +} diff --git a/packet.h b/packet.h index a2ece67..41152d1 100644 --- a/packet.h +++ b/packet.h @@ -156,6 +156,10 @@ int ssh_packet_inc_alive_timeouts(struct ssh *); int ssh_packet_set_maxsize(struct ssh *, u_int); u_int ssh_packet_get_maxsize(struct ssh *); +/* for forced packet rekeying post auth */ +void packet_request_rekeying(void); +int packet_authentication_state(const struct ssh *); + int ssh_packet_get_state(struct ssh *, struct sshbuf *); int ssh_packet_set_state(struct ssh *, struct sshbuf *); @@ -170,6 +174,9 @@ time_t ssh_packet_get_rekey_timeout(struct ssh *); void *ssh_packet_get_input(struct ssh *); void *ssh_packet_get_output(struct ssh *); +void *ssh_packet_get_receive_context(struct ssh *); +void *ssh_packet_get_send_context(struct ssh *); +void packet_request_rekeying(void); /* new API */ int sshpkt_start(struct ssh *ssh, u_char type); diff --git a/progressmeter.c b/progressmeter.c index fe9bf52..85667b2 100644 --- a/progressmeter.c +++ b/progressmeter.c @@ -69,6 +69,8 @@ static const char *file; /* name of the file being transferred */ static off_t start_pos; /* initial position of transfer */ static off_t end_pos; /* ending position of transfer */ static off_t cur_pos; /* transfer position as of last refresh */ +static off_t last_pos; +static off_t max_delta_pos = 0; static volatile off_t *counter; /* progress counter */ static long stalled; /* how long we have been stalled */ static int bytes_per_second; /* current speed in bytes per second */ @@ -128,12 +130,17 @@ refresh_progress_meter(void) int hours, minutes, seconds; int i, len; int file_len; + off_t delta_pos; transferred = *counter - (cur_pos ? cur_pos : start_pos); cur_pos = *counter; now = monotime_double(); bytes_left = end_pos - cur_pos; + delta_pos = cur_pos - last_pos; + if (delta_pos > max_delta_pos) + max_delta_pos = delta_pos; + if (bytes_left > 0) elapsed = now - last_update; else { @@ -158,7 +165,7 @@ refresh_progress_meter(void) /* filename */ buf[0] = '\0'; - file_len = win_size - 35; + file_len = win_size - 45; if (file_len > 0) { len = snprintf(buf, file_len + 1, "\r%s", file); if (len < 0) @@ -188,6 +195,15 @@ refresh_progress_meter(void) (off_t)bytes_per_second); strlcat(buf, "/s ", win_size); + /* instantaneous rate */ + if (bytes_left > 0) + format_rate(buf + strlen(buf), win_size - strlen(buf), + delta_pos); + else + format_rate(buf + strlen(buf), win_size - strlen(buf), + max_delta_pos); + strlcat(buf, "/s ", win_size); + /* ETA */ if (!transferred) stalled += elapsed; @@ -224,6 +240,7 @@ refresh_progress_meter(void) atomicio(vwrite, STDOUT_FILENO, buf, win_size - 1); last_update = now; + last_pos = cur_pos; } /*ARGSUSED*/ diff --git a/readconf.c b/readconf.c index 88051db..544ff74 100644 --- a/readconf.c +++ b/readconf.c @@ -66,6 +66,7 @@ #include "uidswap.h" #include "myproposal.h" #include "digest.h" +#include "sshbuf.h" /* Format of the configuration file: @@ -165,6 +166,9 @@ typedef enum { oHashKnownHosts, oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, oRemoteCommand, + oTcpRcvBufPoll, oTcpRcvBuf, oHPNDisabled, oHPNBufferSize, + oNoneEnabled, oNoneSwitch, + oDisableMTAES, oVisualHostKey, oKexAlgorithms, oIPQoS, oRequestTTY, oIgnoreUnknown, oProxyUseFdpass, oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots, @@ -291,6 +295,9 @@ static struct { { "kexalgorithms", oKexAlgorithms }, { "ipqos", oIPQoS }, { "requesttty", oRequestTTY }, + { "noneenabled", oNoneEnabled }, + { "noneswitch", oNoneSwitch }, + { "disablemtaes", oDisableMTAES }, { "proxyusefdpass", oProxyUseFdpass }, { "canonicaldomains", oCanonicalDomains }, { "canonicalizefallbacklocal", oCanonicalizeFallbackLocal }, @@ -307,6 +314,11 @@ static struct { { "ignoreunknown", oIgnoreUnknown }, { "proxyjump", oProxyJump }, + { "tcprcvbufpoll", oTcpRcvBufPoll }, + { "tcprcvbuf", oTcpRcvBuf }, + { "hpndisabled", oHPNDisabled }, + { "hpnbuffersize", oHPNBufferSize }, + { NULL, oBadOption } }; @@ -962,6 +974,42 @@ parse_time: intptr = &options->check_host_ip; goto parse_flag; + case oHPNDisabled: + intptr = &options->hpn_disabled; + goto parse_flag; + + case oHPNBufferSize: + intptr = &options->hpn_buffer_size; + goto parse_int; + + case oTcpRcvBufPoll: + intptr = &options->tcp_rcv_buf_poll; + goto parse_flag; + + case oNoneEnabled: + intptr = &options->none_enabled; + goto parse_flag; + + case oDisableMTAES: + intptr = &options->disable_multithreaded; + goto parse_flag; + + /* + * We check to see if the command comes from the command + * line or not. If it does then enable it otherwise fail. + * NONE should never be a default configuration. + */ + case oNoneSwitch: + if (strcmp(filename, "command-line") == 0) { + intptr = &options->none_switch; + goto parse_flag; + } else { + error("NoneSwitch is found in %.200s.\nYou may only use this configuration option from the command line", filename); + error("Continuing..."); + debug("NoneSwitch directive found in %.200s.", filename); + return 0; + } + case oVerifyHostKeyDNS: intptr = &options->verify_host_key_dns; multistate_ptr = multistate_yesnoask; @@ -1148,6 +1196,10 @@ parse_int: intptr = &options->connection_attempts; goto parse_int; + case oTcpRcvBuf: + intptr = &options->tcp_rcv_buf; + goto parse_int; + case oCiphers: arg = strdelim(&s); if (!arg || *arg == '\0') @@ -1833,6 +1885,13 @@ initialize_options(Options * options) options->ip_qos_interactive = -1; options->ip_qos_bulk = -1; options->request_tty = -1; + options->none_switch = -1; + options->none_enabled = -1; + options->disable_multithreaded = -1; + options->hpn_disabled = -1; + options->hpn_buffer_size = -1; + options->tcp_rcv_buf_poll = -1; + options->tcp_rcv_buf = -1; options->proxy_use_fdpass = -1; options->ignored_unknown = NULL; options->num_canonical_domains = 0; @@ -1979,6 +2038,32 @@ fill_default_options(Options * options) options->server_alive_interval = 0; if (options->server_alive_count_max == -1) options->server_alive_count_max = 3; + if (options->hpn_disabled == -1) + options->hpn_disabled = 0; + if (options->hpn_buffer_size > -1) { + /* if a user tries to set the size to 0 set it to 1KB */ + if (options->hpn_buffer_size == 0) + options->hpn_buffer_size = 1; + /* limit the buffer to SSHBUF_SIZE_MAX (currently 256MB) */ + if (options->hpn_buffer_size > (SSHBUF_SIZE_MAX / 1024)) { + options->hpn_buffer_size = SSHBUF_SIZE_MAX; + debug("User requested buffer larger than 256MB. Request reverted to 256MB"); + } else + options->hpn_buffer_size *= 1024; + debug("hpn_buffer_size set to %d", options->hpn_buffer_size); + } + if (options->tcp_rcv_buf == 0) + options->tcp_rcv_buf = 1; + if (options->tcp_rcv_buf > -1) + options->tcp_rcv_buf *=1024; + if (options->tcp_rcv_buf_poll == -1) + options->tcp_rcv_buf_poll = 1; + if (options->none_switch == -1) + options->none_switch = 0; + if (options->none_enabled == -1) + options->none_enabled = 0; + if (options->disable_multithreaded == -1) + options->disable_multithreaded = 0; if (options->control_master == -1) options->control_master = 0; if (options->control_persist == -1) { diff --git a/readconf.h b/readconf.h index f4d9e2b..056a44e 100644 --- a/readconf.h +++ b/readconf.h @@ -52,6 +52,10 @@ typedef struct { int strict_host_key_checking; /* Strict host key checking. */ int compression; /* Compress packets in both directions. */ int tcp_keep_alive; /* Set SO_KEEPALIVE. */ + int tcp_rcv_buf; /* user switch to set tcp recv buffer */ + int tcp_rcv_buf_poll; /* Option to poll recv buf every window transfer */ + int hpn_disabled; /* Switch to disable HPN buffer management */ + int hpn_buffer_size; /* User definable size for HPN buffer window */ int ip_qos_interactive; /* IP ToS/DSCP/class for interactive */ int ip_qos_bulk; /* IP ToS/DSCP/class for bulk traffic */ SyslogFacility log_facility; /* Facility for system logging. */ @@ -113,7 +117,11 @@ typedef struct { int enable_ssh_keysign; int64_t rekey_limit; + int none_switch; /* Use none cipher */ + int none_enabled; /* Allow none to be used */ + int disable_multithreaded; /*disable multithreaded aes-ctr*/ int rekey_interval; + int no_host_authentication_for_localhost; int identities_only; int server_alive_interval; diff --git a/sandbox-seccomp-filter.c b/sandbox-seccomp-filter.c index ca75cc7..8f9d194 100644 --- a/sandbox-seccomp-filter.c +++ b/sandbox-seccomp-filter.c @@ -166,6 +166,9 @@ static const struct sock_filter preauth_insns[] = { #ifdef __NR_exit_group SC_ALLOW(__NR_exit_group), #endif +#ifdef __NR_getpeername /* not defined on archs that go via socketcall(2) */ + SC_ALLOW(__NR_getpeername), +#endif #ifdef __NR_getpgid SC_ALLOW(__NR_getpgid), #endif @@ -217,6 +220,9 @@ static const struct sock_filter preauth_insns[] = { #ifdef __NR_sigprocmask SC_ALLOW(__NR_sigprocmask), #endif +#ifdef __NR_socketcall + SC_ALLOW(socketcall), +#endif #ifdef __NR_time SC_ALLOW(__NR_time), #endif diff --git a/scp.c b/scp.c index 31e6709..17b678c 100644 --- a/scp.c +++ b/scp.c @@ -809,7 +809,7 @@ source(int argc, char **argv) off_t i, statbytes; size_t amt, nr; int fd = -1, haderr, indx; - char *last, *name, buf[2048], encname[PATH_MAX]; + char *last, *name, buf[16384], encname[PATH_MAX]; int len; for (indx = 0; indx < argc; ++indx) { diff --git a/servconf.c b/servconf.c index 0f0d090..5bd1678 100644 --- a/servconf.c +++ b/servconf.c @@ -63,6 +63,7 @@ #include "auth.h" #include "myproposal.h" #include "digest.h" +#include "sshbuf.h" static void add_listen_addr(ServerOptions *, const char *, const char *, int); @@ -169,6 +170,11 @@ initialize_server_options(ServerOptions *options) options->authorized_principals_file = NULL; options->authorized_principals_command = NULL; options->authorized_principals_command_user = NULL; + options->tcp_rcv_buf_poll = -1; + options->hpn_disabled = -1; + options->hpn_buffer_size = -1; + options->none_enabled = -1; + options->disable_multithreaded = -1; options->ip_qos_interactive = -1; options->ip_qos_bulk = -1; options->version_addendum = NULL; @@ -237,6 +243,10 @@ void fill_default_server_options(ServerOptions *options) { u_int i; + /* needed for hpn socket tests */ + int sock; + int socksize; + int socksizelen = sizeof(int); /* Portable-specific options */ if (options->use_pam == -1) @@ -371,6 +381,45 @@ fill_default_server_options(ServerOptions *options) } if (options->permit_tun == -1) options->permit_tun = SSH_TUNMODE_NO; + if (options->none_enabled == -1) + options->none_enabled = 0; + if (options->disable_multithreaded == -1) + options->disable_multithreaded = 0; + if (options->hpn_disabled == -1) + options->hpn_disabled = 0; + + if (options->hpn_buffer_size == -1) { + /* option not explicitly set. Now we have to figure out */ + /* what value to use */ + if (options->hpn_disabled == 1) { + options->hpn_buffer_size = CHAN_SES_WINDOW_DEFAULT; + } else { + /* get the current RCV size and set it to that */ + /*create a socket but don't connect it */ + /* we use that the get the rcv socket size */ + sock = socket(AF_INET, SOCK_STREAM, 0); + getsockopt(sock, SOL_SOCKET, SO_RCVBUF, + &socksize, &socksizelen); + close(sock); + options->hpn_buffer_size = socksize; + debug("HPN Buffer Size: %d", options->hpn_buffer_size); + } + } else { + /* we have to do this in case the user sets both values in a contradictory */ + /* manner. hpn_disabled overrrides hpn_buffer_size*/ + if (options->hpn_disabled <= 0) { + if (options->hpn_buffer_size == 0) + options->hpn_buffer_size = 1; + /* limit the maximum buffer to SSHBUF_SIZE_MAX (currently 256MB) */ + if (options->hpn_buffer_size > (SSHBUF_SIZE_MAX / 1024)) { + options->hpn_buffer_size = SSHBUF_SIZE_MAX; + } else { + options->hpn_buffer_size *= 1024; + } + } else + options->hpn_buffer_size = CHAN_TCP_WINDOW_DEFAULT; + } + if (options->ip_qos_interactive == -1) options->ip_qos_interactive = IPTOS_LOWDELAY; if (options->ip_qos_bulk == -1) @@ -449,6 +498,9 @@ typedef enum { sPasswordAuthentication, sKbdInteractiveAuthentication, sListenAddress, sAddressFamily, sPrintMotd, sPrintLastLog, sIgnoreRhosts, + sNoneEnabled, + sDisableMTAES, + sTcpRcvBufPoll, sHPNDisabled, sHPNBufferSize, sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost, sPermitTTY, sStrictModes, sEmptyPasswd, sTCPKeepAlive, sPermitUserEnvironment, sAllowTcpForwarding, sCompression, @@ -603,6 +655,11 @@ static struct { { "revokedkeys", sRevokedKeys, SSHCFG_ALL }, { "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL }, { "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_ALL }, + { "hpndisabled", sHPNDisabled, SSHCFG_ALL }, + { "hpnbuffersize", sHPNBufferSize, SSHCFG_ALL }, + { "tcprcvbufpoll", sTcpRcvBufPoll, SSHCFG_ALL }, + { "noneenabled", sNoneEnabled, SSHCFG_ALL }, + { "disableMTAES", sDisableMTAES, SSHCFG_ALL }, { "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL }, { "ipqos", sIPQoS, SSHCFG_ALL }, { "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL }, @@ -644,6 +701,7 @@ parse_token(const char *cp, const char *filename, for (i = 0; keywords[i].name; i++) if (strcasecmp(cp, keywords[i].name) == 0) { + debug("Config token is %s", keywords[i].name); *flags = keywords[i].flags; return keywords[i].opcode; } @@ -1348,10 +1406,31 @@ process_server_config_line(ServerOptions *options, char *line, multistate_ptr = multistate_flag; goto parse_multistate; + + case sTcpRcvBufPoll: + intptr = &options->tcp_rcv_buf_poll; + goto parse_flag; + + case sHPNDisabled: + intptr = &options->hpn_disabled; + goto parse_flag; + + case sHPNBufferSize: + intptr = &options->hpn_buffer_size; + goto parse_int; + case sIgnoreUserKnownHosts: intptr = &options->ignore_user_known_hosts; goto parse_flag; + case sNoneEnabled: + intptr = &options->none_enabled; + goto parse_flag; + + case sDisableMTAES: + intptr = &options->disable_multithreaded; + goto parse_flag; + case sHostbasedAuthentication: intptr = &options->hostbased_authentication; goto parse_flag; diff --git a/servconf.h b/servconf.h index 37a0fb1..1af3864 100644 --- a/servconf.h +++ b/servconf.h @@ -184,6 +184,13 @@ typedef struct { char *adm_forced_command; int use_pam; /* Enable auth via PAM */ + int tcp_rcv_buf_poll; /* poll tcp rcv window in autotuning kernels*/ + int hpn_disabled; /* disable hpn functionality. false by default */ + int hpn_buffer_size; /* set the hpn buffer size - default 3MB */ + + int none_enabled; /* Enable NONE cipher switch */ + + int disable_multithreaded; /*disable multithreaded aes-ctr cipher */ int permit_tun; diff --git a/serverloop.c b/serverloop.c index d6fe24c..1819a74 100644 --- a/serverloop.c +++ b/serverloop.c @@ -85,6 +85,9 @@ extern Authctxt *the_authctxt; extern struct sshauthopt *auth_opts; extern int use_privsep; +static u_long stdin_bytes = 0; /* Number of bytes written to stdin. */ +static u_long fdout_bytes = 0; /* Number of stdout bytes read from program. */ + static int no_more_sessions = 0; /* Disallow further sessions. */ /* @@ -104,6 +107,20 @@ static void server_init_dispatch(void); char *tun_fwd_ifnames = NULL; /* + * Returns current time in seconds from Jan 1, 1970 with the maximum + * available resolution. + */ + +static double +get_current_time(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (double) tv.tv_sec + (double) tv.tv_usec / 1000000.0; +} + + +/* * we write to this pipe if a SIGCHLD is caught in order to avoid * the race between select() and child_terminated */ @@ -320,6 +337,7 @@ process_input(struct ssh *ssh, fd_set *readset, int connection_in) } else { /* Buffer any received data. */ packet_process_incoming(buf, len); + fdout_bytes += len; } } return 0; @@ -333,7 +351,7 @@ process_output(fd_set *writeset, int connection_out) { /* Send any buffered packet data to the client. */ if (FD_ISSET(connection_out, writeset)) - packet_write_poll(); + stdin_bytes += packet_write_poll(); } static void @@ -368,11 +386,13 @@ void server_loop2(struct ssh *ssh, Authctxt *authctxt) { fd_set *readset = NULL, *writeset = NULL; + double start_time, total_time; int max_fd; u_int nalloc = 0, connection_in, connection_out; u_int64_t rekey_timeout_ms = 0; debug("Entering interactive session for SSH2."); + start_time = get_current_time(); signal(SIGCHLD, sigchld_handler); child_terminated = 0; @@ -427,6 +447,13 @@ server_loop2(struct ssh *ssh, Authctxt *authctxt) /* free all channels, no more reads and writes */ channel_free_all(ssh); + /* write final data to log */ + total_time = get_current_time() - start_time; + logit("SSH: Server;LType: Throughput;Remote: %s-%d;IN: %lu;OUT: %lu;Duration: %.1f;tPut_in: %.1f;tPut_out: %.1f", + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), + stdin_bytes, fdout_bytes, total_time, stdin_bytes / total_time, + fdout_bytes / total_time); + /* free remaining sessions, e.g. remove wtmp entries */ session_destroy_all(ssh, NULL); } @@ -551,7 +578,8 @@ server_request_tun(struct ssh *ssh) debug("Tunnel forwarding using interface %s", ifname); c = channel_new(ssh, "tun", SSH_CHANNEL_OPEN, sock, sock, -1, - CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); + options.hpn_disabled ? CHAN_TCP_WINDOW_DEFAULT : options.hpn_buffer_size, + CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); c->datagram = 1; #if defined(SSH_TUN_FILTER) if (mode == SSH_TUNMODE_POINTOPOINT) @@ -600,6 +628,8 @@ server_request_session(struct ssh *ssh) c = channel_new(ssh, "session", SSH_CHANNEL_LARVAL, -1, -1, -1, /*window size*/0, CHAN_SES_PACKET_DEFAULT, 0, "server-session", 1); + if ((options.tcp_rcv_buf_poll) && (!options.hpn_disabled)) + c->dynamic_window = 1; if (session_open(the_authctxt, c->self) != 1) { debug("session open failed, free channel %d", c->self); channel_free(ssh, c); diff --git a/session.c b/session.c index 58826db..3bd2cb2 100644 --- a/session.c +++ b/session.c @@ -225,6 +225,7 @@ auth_input_request_forwarding(struct ssh *ssh, struct passwd * pw) goto authsock_err; /* Allocate a channel for the authentication agent socket. */ + /* this shouldn't matter if its hpn or not - cjr */ nc = channel_new(ssh, "auth socket", SSH_CHANNEL_AUTH_SOCKET, sock, sock, -1, CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT, @@ -2116,10 +2117,11 @@ session_set_fds(struct ssh *ssh, Session *s, */ if (s->chanid == -1) fatal("no channel for session %d", s->self); - channel_set_fds(ssh, s->chanid, - fdout, fdin, fderr, - ignore_fderr ? CHAN_EXTENDED_IGNORE : CHAN_EXTENDED_READ, - 1, is_tty, CHAN_SES_WINDOW_DEFAULT); + channel_set_fds(ssh, s->chanid, + fdout, fdin, fderr, + ignore_fderr ? CHAN_EXTENDED_IGNORE : CHAN_EXTENDED_READ, + 1, is_tty, + options.hpn_disabled ? CHAN_SES_WINDOW_DEFAULT : options.hpn_buffer_size); } /* diff --git a/sftp.1 b/sftp.1 index 43e0442..0d4dd06 100644 --- a/sftp.1 +++ b/sftp.1 @@ -264,7 +264,8 @@ diagnostic messages from Specify how many requests may be outstanding at any one time. Increasing this may slightly improve file transfer speed but will increase memory usage. -The default is 64 outstanding requests. +The default is 256 outstanding requests providing for 8MB +of outstanding data with a 32KB buffer. .It Fl r Recursively copy entire directories when uploading and downloading. Note that diff --git a/sftp.c b/sftp.c index 5ce864e..8c9b600 100644 --- a/sftp.c +++ b/sftp.c @@ -72,7 +72,7 @@ typedef void EditLine; #include "sftp-client.h" #define DEFAULT_COPY_BUFLEN 32768 /* Size of buffer for up/download */ -#define DEFAULT_NUM_REQUESTS 64 /* # concurrent outstanding requests */ +#define DEFAULT_NUM_REQUESTS 256 /* # concurrent outstanding requests */ /* File to read commands from */ FILE* infile; diff --git a/ssh.c b/ssh.c index d3619fe..d43d91c 100644 --- a/ssh.c +++ b/ssh.c @@ -954,6 +954,10 @@ main(int ac, char **av) break; case 'T': options.request_tty = REQUEST_TTY_NO; + /* ensure that the user doesn't try to backdoor a */ + /* null cipher switch on an interactive session */ + /* so explicitly disable it no matter what */ + options.none_switch=0; break; case 'o': line = xstrdup(optarg); @@ -1606,6 +1610,8 @@ control_persist_detach(void) setproctitle("%s [mux]", options.control_path); } +extern const EVP_CIPHER *evp_aes_ctr_mt(void); + /* Do fork() after authentication. Used by "ssh -f" */ static void fork_postauth(void) @@ -1833,6 +1839,78 @@ ssh_session2_setup(struct ssh *ssh, int id, int success, void *arg) NULL, fileno(stdin), &command, environ); } +static void +hpn_options_init(void) +{ + /* + * We need to check to see if what they want to do about buffer + * sizes here. In a hpn to nonhpn connection we want to limit + * the window size to something reasonable in case the far side + * has the large window bug. In hpn to hpn connection we want to + * use the max window size but allow the user to override it + * lastly if they disabled hpn then use the ssh std window size. + * + * So why don't we just do a getsockopt() here and set the + * ssh window to that? In the case of a autotuning receive + * window the window would get stuck at the initial buffer + * size generally less than 96k. Therefore we need to set the + * maximum ssh window size to the maximum hpn buffer size + * unless the user has specifically set the tcprcvbufpoll + * to no. In which case we *can* just set the window to the + * minimum of the hpn buffer size and tcp receive buffer size. + */ + + if (tty_flag) + options.hpn_buffer_size = CHAN_SES_WINDOW_DEFAULT; + else + options.hpn_buffer_size = 2 * 1024 * 1024; + + if (datafellows & SSH_BUG_LARGEWINDOW) { + debug("HPN to Non-HPN Connection"); + } else { + int sock, socksize; + socklen_t socksizelen; + if (options.tcp_rcv_buf_poll <= 0) { + sock = socket(AF_INET, SOCK_STREAM, 0); + socksizelen = sizeof(socksize); + getsockopt(sock, SOL_SOCKET, SO_RCVBUF, + &socksize, &socksizelen); + close(sock); + debug("socksize %d", socksize); + options.hpn_buffer_size = socksize; + debug("HPNBufferSize set to TCP RWIN: %d", options.hpn_buffer_size); + } else { + if (options.tcp_rcv_buf > 0) { + /* + * Create a socket but don't connect it: + * we use that the get the rcv socket size + */ + sock = socket(AF_INET, SOCK_STREAM, 0); + /* + * If they are using the tcp_rcv_buf option, + * attempt to set the buffer size to that. + */ + if (options.tcp_rcv_buf) { + socksizelen = sizeof(options.tcp_rcv_buf); + setsockopt(sock, SOL_SOCKET, SO_RCVBUF, + &options.tcp_rcv_buf, socksizelen); + } + socksizelen = sizeof(socksize); + getsockopt(sock, SOL_SOCKET, SO_RCVBUF, + &socksize, &socksizelen); + close(sock); + debug("socksize %d", socksize); + options.hpn_buffer_size = socksize; + debug("HPNBufferSize set to user TCPRcvBuf: %d", options.hpn_buffer_size); + } + } + } + + debug("Final hpn_buffer_size = %d", options.hpn_buffer_size); + + channel_set_hpn(options.hpn_disabled, options.hpn_buffer_size); +} + /* open new channel for a session */ static int ssh_session2_open(struct ssh *ssh) @@ -1859,9 +1937,11 @@ ssh_session2_open(struct ssh *ssh) if (!isatty(err)) set_nonblock(err); - window = CHAN_SES_WINDOW_DEFAULT; + window = options.hpn_buffer_size; + packetmax = CHAN_SES_PACKET_DEFAULT; if (tty_flag) { + window = CHAN_SES_WINDOW_DEFAULT; window >>= 1; packetmax >>= 1; } @@ -1870,6 +1950,10 @@ ssh_session2_open(struct ssh *ssh) window, packetmax, CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0); + if ((options.tcp_rcv_buf_poll > 0) && (!options.hpn_disabled)) { + c->dynamic_window = 1; + debug("Enabled Dynamic Window Scaling"); + } debug3("%s: channel_new: %d", __func__, c->self); channel_send_open(ssh, c->self); @@ -1886,6 +1970,13 @@ ssh_session2(struct ssh *ssh, struct passwd *pw) int devnull, id = -1; char *cp, *tun_fwd_ifname = NULL; + /* + * We need to initialize this early because the forwarding logic below + * might open channels that use the hpn buffer sizes. We can't send a + * window of -1 (the default) to the server as it breaks things. + */ + hpn_options_init(); + /* XXX should be pre-session */ if (!options.control_persist) ssh_init_stdio_forwarding(ssh); diff --git a/sshbuf.h b/sshbuf.h index 77f1e9e..362edaf 100644 --- a/sshbuf.h +++ b/sshbuf.h @@ -28,7 +28,7 @@ # endif /* OPENSSL_HAS_ECC */ #endif /* WITH_OPENSSL */ -#define SSHBUF_SIZE_MAX 0x8000000 /* Hard maximum size */ +#define SSHBUF_SIZE_MAX 0xF000000 /* Hard maximum size 256MB */ #define SSHBUF_REFS_MAX 0x100000 /* Max child buffers */ #define SSHBUF_MAX_BIGNUM (16384 / 8) /* Max bignum *bytes* */ #define SSHBUF_MAX_ECPOINT ((528 * 2 / 8) + 1) /* Max EC point *bytes* */ diff --git a/sshconnect.c b/sshconnect.c index 3805d35..b2e56b5 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -338,6 +338,31 @@ check_ifaddrs(const char *ifname, int af, const struct ifaddrs *ifaddrs, #endif /* + * Set TCP receive buffer if requested. + * Note: tuning needs to happen after the socket is + * created but before the connection happens + * so winscale is negotiated properly -cjr + */ +static void +ssh_set_socket_recvbuf(int sock) +{ + void *buf = (void *)&options.tcp_rcv_buf; + int sz = sizeof(options.tcp_rcv_buf); + int socksize; + int socksizelen = sizeof(int); + + debug("setsockopt Attempting to set SO_RCVBUF to %d", options.tcp_rcv_buf); + if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, buf, sz) >= 0) { + getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &socksize, &socksizelen); + debug("setsockopt SO_RCVBUF: %.100s %d", strerror(errno), socksize); + } + else + error("Couldn't set socket receive buffer to %d: %.100s", + options.tcp_rcv_buf, strerror(errno)); +} + + +/* * Creates a (possibly privileged) socket for use as the ssh connection. */ static int @@ -359,6 +384,9 @@ ssh_create_socket(int privileged, struct addrinfo *ai) } fcntl(sock, F_SETFD, FD_CLOEXEC); + if (options.tcp_rcv_buf > 0) + ssh_set_socket_recvbuf(sock); + /* Bind the socket to an alternative local IP address */ if (options.bind_address == NULL && options.bind_interface == NULL && !privileged) diff --git a/sshconnect2.c b/sshconnect2.c index 1f4a74c..d59aac1 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -83,6 +83,13 @@ extern char *server_version_string; extern Options options; /* + * tty_flag is set in ssh.c. Use this in ssh_userauth2: + * if it is set, then prevent the switch to the null cipher. + */ + +extern int tty_flag; + +/* * SSH2 key exchange */ @@ -154,6 +161,8 @@ order_hostkeyalgs(char *host, struct sockaddr *hostaddr, u_short port) return ret; } +static char *myproposal[PROPOSAL_MAX]; +static const char *myproposal_default[PROPOSAL_MAX] = { KEX_CLIENT }; void ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) { @@ -162,6 +171,8 @@ ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port) struct kex *kex; int r; + memcpy(&myproposal, &myproposal_default, sizeof(myproposal)); + xxx_host = host; xxx_hostaddr = hostaddr; @@ -409,6 +420,47 @@ ssh_userauth2(const char *local_user, const char *server_user, char *host, if (!authctxt.success) fatal("Authentication failed."); + + /* + * If the user wants to use the none cipher, do it post authentication + * and only if the right conditions are met -- both of the NONE commands + * must be true and there must be no tty allocated. + */ + if ((options.none_switch == 1) && (options.none_enabled == 1)) { + if (!tty_flag) { /* no null on tty sessions */ + debug("Requesting none rekeying..."); + memcpy(&myproposal, &myproposal_default, sizeof(myproposal)); + myproposal[PROPOSAL_ENC_ALGS_STOC] = "none"; + myproposal[PROPOSAL_ENC_ALGS_CTOS] = "none"; + kex_prop2buf(active_state->kex->my, myproposal); + packet_request_rekeying(); + fprintf(stderr, "WARNING: ENABLED NONE CIPHER\n"); + } else { + /* requested NONE cipher when in a tty */ + debug("Cannot switch to NONE cipher with tty allocated"); + fprintf(stderr, "NONE cipher switch disabled when a TTY is allocated\n"); + } + } + +#ifdef WITH_OPENSSL + if (options.disable_multithreaded == 0) { + /* if we are using aes-ctr there can be issues in either a fork or sandbox + * so the initial aes-ctr is defined to point to the original single process + * evp. After authentication we'll be past the fork and the sandboxed privsep + * so we repoint the define to the multithreaded evp. To start the threads we + * then force a rekey + */ + const void *cc = ssh_packet_get_send_context(active_state); + + /* only do this for the ctr cipher. otherwise gcm mode breaks. Don't know why though */ + if (strstr(cipher_ctx_name(cc), "ctr")) { + debug("Single to Multithread CTR cipher swap - client request"); + cipher_reset_multithreaded(); + packet_request_rekeying(); + } + } +#endif + debug("Authentication succeeded (%s).", authctxt.method->name); } diff --git a/sshd.c b/sshd.c index fd95b68..f25c524 100644 --- a/sshd.c +++ b/sshd.c @@ -373,7 +373,7 @@ sshd_exchange_identification(struct ssh *ssh, int sock_in, int sock_out) char remote_version[256]; /* Must be at least as big as buf. */ xasprintf(&server_version_string, "SSH-%d.%d-%.100s%s%s\r\n", - PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION, + PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_RELEASE, *options.version_addendum == '\0' ? "" : " ", options.version_addendum); @@ -428,6 +428,9 @@ sshd_exchange_identification(struct ssh *ssh, int sock_in, int sock_out) } debug("Client protocol version %d.%d; client software version %.100s", remote_major, remote_minor, remote_version); + logit("SSH: Server;Ltype: Version;Remote: %s-%d;Protocol: %d.%d;Client: %.100s", + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), + remote_major, remote_minor, remote_version); ssh->compat = compat_datafellows(remote_version); @@ -1025,6 +1028,8 @@ listen_on_addrs(struct listenaddr *la) int ret, listen_sock; struct addrinfo *ai; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; + int socksize; + int socksizelen = sizeof(int); for (ai = la->addrs; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) @@ -1070,6 +1075,11 @@ listen_on_addrs(struct listenaddr *la) debug("Bind to port %s on %s.", strport, ntop); + getsockopt(listen_sock, SOL_SOCKET, SO_RCVBUF, + &socksize, &socksizelen); + debug("Server TCP RWIN socket size: %d", socksize); + debug("HPN Buffer Size: %d", options.hpn_buffer_size); + /* Bind the socket to the desired port. */ if (bind(listen_sock, ai->ai_addr, ai->ai_addrlen) < 0) { error("Bind to port %s on %s failed: %.200s.", @@ -1634,6 +1644,13 @@ main(int ac, char **av) /* Fill in default values for those options not explicitly set. */ fill_default_server_options(&options); + if (options.none_enabled == 1) { + char *old_ciphers = options.ciphers; + + xasprintf(&options.ciphers, "%s,none", old_ciphers); + free(old_ciphers); + } + /* challenge-response is implemented via keyboard interactive */ if (options.challenge_response_authentication) options.kbd_interactive_authentication = 1; @@ -2047,6 +2064,9 @@ main(int ac, char **av) rdomain == NULL ? "" : "\""); free(laddr); + /* set the HPN options for the child */ + channel_set_hpn(options.hpn_disabled, options.hpn_buffer_size); + /* * We don't want to listen forever unless the other side * successfully authenticates itself. So we set up an alarm which is @@ -2149,6 +2169,25 @@ main(int ac, char **av) /* Try to send all our hostkeys to the client */ notify_hostkeys(ssh); +#ifdef WITH_OPENSSL + if (options.disable_multithreaded == 0) { + /* if we are using aes-ctr there can be issues in either a fork or sandbox + * so the initial aes-ctr is defined to point ot the original single process + * evp. After authentication we'll be past the fork and the sandboxed privsep + * so we repoint the define to the multithreaded evp. To start the threads we + * then force a rekey + */ + const void *cc = ssh_packet_get_send_context(active_state); + + /* only rekey if necessary. If we don't do this gcm mode cipher breaks */ + if (strstr(cipher_ctx_name(cc), "ctr")) { + debug("Single to Multithreaded CTR cipher swap - server request"); + cipher_reset_multithreaded(); + packet_request_rekeying(); + } + } +#endif + /* Start session. */ do_authenticated(ssh, authctxt); @@ -2213,6 +2252,9 @@ do_ssh2_kex(void) struct kex *kex; int r; + if (options.none_enabled == 1) + debug("WARNING: None cipher enabled"); + myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal( options.kex_algorithms); myproposal[PROPOSAL_ENC_ALGS_CTOS] = compat_cipher_proposal( diff --git a/sshd_config b/sshd_config index 3109d5d..aa1b057 100644 --- a/sshd_config +++ b/sshd_config @@ -109,6 +109,19 @@ AuthorizedKeysFile .ssh/authorized_keys # override default of no subsystems Subsystem sftp /usr/libexec/sftp-server +# the following are HPN related configuration options +# tcp receive buffer polling. disable in non autotuning kernels +#TcpRcvBufPoll yes + +# disable hpn performance boosts +#HPNDisabled no + +# buffer size for hpn to non-hpn connections +#HPNBufferSize 2048 + +# allow the use of the none cipher +#NoneEnabled no + # Example of overriding settings on a per-user basis #Match User anoncvs # X11Forwarding no diff --git a/version.h b/version.h index ea52b26..0c1e9a2 100644 --- a/version.h +++ b/version.h @@ -3,4 +3,5 @@ #define SSH_VERSION "OpenSSH_7.7" #define SSH_PORTABLE "p1" -#define SSH_RELEASE SSH_VERSION SSH_PORTABLE +#define SSH_HPN "-hpn14v15" +#define SSH_RELEASE SSH_VERSION SSH_PORTABLE SSH_HPN