diff --git a/HPN-README b/HPN-README new file mode 100644 index 00000000..2ef5a44a --- /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/channels.c b/channels.c index 0f45aee4..7d20bdf0 100644 --- a/channels.c +++ b/channels.c @@ -220,6 +220,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 @@ -392,6 +396,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 */ @@ -1079,6 +1084,28 @@ channel_pre_connecting(struct ssh *ssh, Channel *c, FD_SET(c->sock, writeset); } +static int +channel_tcpwinsz(struct ssh *ssh) +{ + u_int32_t tcpwinsz = 0; + socklen_t optsz = sizeof(tcpwinsz); + int ret = -1; + + /* if we aren't on a socket return 128KB */ + if (!ssh_packet_connection_is_on_socket(ssh)) + return 128 * 1024; + + ret = getsockopt(ssh_packet_get_connection_in(ssh), + 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: tcp connection %d, Receive window: %d", + ssh_packet_get_connection_in(ssh), tcpwinsz); + return tcpwinsz; +} + static void channel_pre_open(struct ssh *ssh, Channel *c, fd_set *readset, fd_set *writeset) @@ -2178,21 +2205,30 @@ 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; + u_int32_t tcpwinsz = channel_tcpwinsz(ssh); + /* adjust max window size if we are in a dynamic environment */ + if (c->dynamic_window && (tcpwinsz > c->local_window_max)) { + /* grow the window somewhat aggressively to maintain pressure */ + addition = 1.5 * (tcpwinsz - c->local_window_max); + c->local_window_max += addition; + debug("Channel: Window growth to %d by %d bytes", 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)); } debug2("channel %d: window %d sent adjust %d", c->self, c->local_window, - c->local_consumed); - c->local_window += c->local_consumed; + c->local_consumed + addition); + c->local_window += c->local_consumed + addition; c->local_consumed = 0; } return 1; @@ -3373,6 +3409,15 @@ channel_fwd_bind_addr(struct ssh *ssh, 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, @@ -3513,9 +3558,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); @@ -4670,8 +4717,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 aa2a87c1..0cb064e8 100644 --- a/channels.h +++ b/channels.h @@ -150,6 +150,7 @@ struct Channel { u_int local_window_max; u_int local_consumed; u_int local_maxpacket; + int dynamic_window; int extended_usage; int single_connection; @@ -341,4 +342,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.c b/cipher.c index 25f98ba8..453ee661 100644 --- a/cipher.c +++ b/cipher.c @@ -217,7 +217,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/clientloop.c b/clientloop.c index b5a1f703..daccdceb 100644 --- a/clientloop.c +++ b/clientloop.c @@ -1599,9 +1599,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; } @@ -1625,9 +1627,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; } @@ -1652,10 +1655,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 0624dc6d..c4aee493 100644 --- a/compat.c +++ b/compat.c @@ -150,6 +150,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 d611d33e..2c5b2f9e 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 49d70156..0b6ec834 100644 --- a/kex.c +++ b/kex.c @@ -884,6 +884,10 @@ 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; + + 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) @@ -954,6 +958,14 @@ 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, diff --git a/packet.c b/packet.c index 817da43b..b2bcf78f 100644 --- a/packet.c +++ b/packet.c @@ -925,6 +925,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) @@ -951,6 +969,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 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()) diff --git a/packet.h b/packet.h index 8ccfd2e0..1ad9bc06 100644 --- a/packet.h +++ b/packet.h @@ -155,6 +155,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 *); diff --git a/readconf.c b/readconf.c index f78b4d6f..ecba21ea 100644 --- a/readconf.c +++ b/readconf.c @@ -67,6 +67,7 @@ #include "uidswap.h" #include "myproposal.h" #include "digest.h" +#include "sshbuf.h" /* Format of the configuration file: @@ -167,6 +168,8 @@ typedef enum { oHashKnownHosts, oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand, oRemoteCommand, + oTcpRcvBufPoll, oTcpRcvBuf, oHPNDisabled, oHPNBufferSize, + oNoneEnabled, oNoneSwitch, oVisualHostKey, oKexAlgorithms, oIPQoS, oRequestTTY, oIgnoreUnknown, oProxyUseFdpass, oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots, @@ -295,6 +298,8 @@ static struct { { "kexalgorithms", oKexAlgorithms }, { "ipqos", oIPQoS }, { "requesttty", oRequestTTY }, + { "noneenabled", oNoneEnabled }, + { "noneswitch", oNoneSwitch }, { "proxyusefdpass", oProxyUseFdpass }, { "canonicaldomains", oCanonicalDomains }, { "canonicalizefallbacklocal", oCanonicalizeFallbackLocal }, @@ -311,6 +316,11 @@ static struct { { "ignoreunknown", oIgnoreUnknown }, { "proxyjump", oProxyJump }, + { "tcprcvbufpoll", oTcpRcvBufPoll }, + { "tcprcvbuf", oTcpRcvBuf }, + { "hpndisabled", oHPNDisabled }, + { "hpnbuffersize", oHPNBufferSize }, + { NULL, oBadOption } }; @@ -1000,6 +1010,38 @@ 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; + + /* + * 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; @@ -1195,6 +1237,10 @@ parse_int: *intptr = value; break; + case oTcpRcvBuf: + intptr = &options->tcp_rcv_buf; + goto parse_int; + case oCiphers: arg = strdelim(&s); if (!arg || *arg == '\0') @@ -1935,6 +1981,12 @@ 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->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; @@ -2082,6 +2134,30 @@ 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->control_master == -1) options->control_master = 0; if (options->control_persist == -1) { diff --git a/readconf.h b/readconf.h index 8e36bf32..e4db166c 100644 --- a/readconf.h +++ b/readconf.h @@ -50,6 +50,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. */ @@ -112,7 +116,10 @@ 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 rekey_interval; + int no_host_authentication_for_localhost; int identities_only; int server_alive_interval; diff --git a/servconf.c b/servconf.c index e76f9c39..8ba26315 100644 --- a/servconf.c +++ b/servconf.c @@ -64,6 +64,7 @@ #include "auth.h" #include "myproposal.h" #include "digest.h" +#include "sshbuf.h" static void add_listen_addr(ServerOptions *, const char *, const char *, int); @@ -174,6 +175,10 @@ 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->ip_qos_interactive = -1; options->ip_qos_bulk = -1; options->version_addendum = NULL; @@ -273,6 +278,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) @@ -409,6 +418,43 @@ 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->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_DSCP_AF21; if (options->ip_qos_bulk == -1) @@ -486,6 +532,8 @@ typedef enum { sPasswordAuthentication, sKbdInteractiveAuthentication, sListenAddress, sAddressFamily, sPrintMotd, sPrintLastLog, sIgnoreRhosts, + sNoneEnabled, + sTcpRcvBufPoll, sHPNDisabled, sHPNBufferSize, sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost, sPermitTTY, sStrictModes, sEmptyPasswd, sTCPKeepAlive, sPermitUserEnvironment, sAllowTcpForwarding, sCompression, @@ -642,6 +690,10 @@ 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 }, { "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL }, { "ipqos", sIPQoS, SSHCFG_ALL }, { "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL }, @@ -698,6 +750,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; } @@ -1424,10 +1477,27 @@ 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 sHostbasedAuthentication: intptr = &options->hostbased_authentication; goto parse_flag; diff --git a/servconf.h b/servconf.h index 5483da05..5b5b93a6 100644 --- a/servconf.h +++ b/servconf.h @@ -183,6 +183,11 @@ 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 permit_tun; diff --git a/serverloop.c b/serverloop.c index ea468c95..75ad13e0 100644 --- a/serverloop.c +++ b/serverloop.c @@ -608,7 +608,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) @@ -659,6 +660,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 8f5d7e0a..37b2b373 100644 --- a/session.c +++ b/session.c @@ -223,6 +223,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, @@ -2251,10 +2252,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 a52c1cff..1ef50bf6 100644 --- a/sftp.1 +++ b/sftp.1 @@ -286,7 +286,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 b66037f1..7d1e5037 100644 --- a/sftp.c +++ b/sftp.c @@ -71,7 +71,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 ee51823c..adfabc29 100644 --- a/ssh.c +++ b/ssh.c @@ -960,6 +960,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); @@ -1828,6 +1832,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) @@ -1854,9 +1930,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; } @@ -1865,6 +1943,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); @@ -1881,6 +1963,13 @@ ssh_session2(struct ssh *ssh, struct passwd *pw) int r, 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 ebd64b10..9715203b 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 6230dad3..2e88738d 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -358,6 +358,30 @@ 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 socket for use as the ssh connection. */ @@ -380,6 +404,9 @@ ssh_create_socket(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) return sock; diff --git a/sshconnect2.c b/sshconnect2.c index 87fa70a4..bd2fa3bf 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -82,6 +82,13 @@ extern char *client_version_string; 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(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port) { @@ -161,6 +170,8 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port) char *s, *all_key; int r; + memcpy(&myproposal, &myproposal_default, sizeof(myproposal)); + xxx_host = host; xxx_hostaddr = hostaddr; @@ -422,6 +433,28 @@ ssh_userauth2(struct ssh *ssh, const char *local_user, 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(ssh->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"); + } + } + debug("Authentication succeeded (%s).", authctxt.method->name); } diff --git a/sshd.c b/sshd.c index 11571c01..23a06022 100644 --- a/sshd.c +++ b/sshd.c @@ -950,6 +950,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) @@ -995,6 +997,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) == -1) { error("Bind to port %s on %s failed: %.200s.", @@ -1636,6 +1643,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; @@ -2069,6 +2083,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 @@ -2244,6 +2261,9 @@ do_ssh2_kex(struct ssh *ssh) 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 19b7c91a..64dcb9a6 100644 --- a/sshd_config +++ b/sshd_config @@ -108,6 +108,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 6b3fadf8..ec1d2e27 100644 --- a/version.h +++ b/version.h @@ -3,4 +3,6 @@ #define SSH_VERSION "OpenSSH_8.1" #define SSH_PORTABLE "p1" -#define SSH_RELEASE SSH_VERSION SSH_PORTABLE +#define SSH_HPN "-hpn14v20" +#define SSH_RELEASE SSH_VERSION SSH_PORTABLE SSH_HPN +