upstream: convert ssh, sshd mainloops from select() to poll();
feedback & ok deraadt@ and markus@ has been in snaps for a few months OpenBSD-Commit-ID: a77e16a667d5b194dcdb3b76308b8bba7fa7239c
This commit is contained in:
parent
5c79952dfe
commit
17877bc81d
407
channels.c
407
channels.c
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: channels.c,v 1.410 2022/01/06 21:46:23 djm Exp $ */
|
/* $OpenBSD: channels.c,v 1.411 2022/01/06 21:48:38 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||||
|
@ -57,6 +57,7 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
|
#include <poll.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#ifdef HAVE_STDINT_H
|
#ifdef HAVE_STDINT_H
|
||||||
# include <stdint.h>
|
# include <stdint.h>
|
||||||
|
@ -84,6 +85,9 @@
|
||||||
#include "pathnames.h"
|
#include "pathnames.h"
|
||||||
#include "match.h"
|
#include "match.h"
|
||||||
|
|
||||||
|
/* XXX remove once we're satisfied there's no lurking bugs */
|
||||||
|
/* #define DEBUG_CHANNEL_POLL 1 */
|
||||||
|
|
||||||
/* -- agent forwarding */
|
/* -- agent forwarding */
|
||||||
#define NUM_SOCKS 10
|
#define NUM_SOCKS 10
|
||||||
|
|
||||||
|
@ -98,7 +102,7 @@
|
||||||
/* Maximum number of fake X11 displays to try. */
|
/* Maximum number of fake X11 displays to try. */
|
||||||
#define MAX_DISPLAYS 1000
|
#define MAX_DISPLAYS 1000
|
||||||
|
|
||||||
/* Per-channel callback for pre/post select() actions */
|
/* Per-channel callback for pre/post IO actions */
|
||||||
typedef void chan_fn(struct ssh *, Channel *c);
|
typedef void chan_fn(struct ssh *, Channel *c);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -160,17 +164,11 @@ struct ssh_channels {
|
||||||
u_int channels_alloc;
|
u_int channels_alloc;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Maximum file descriptor value used in any of the channels. This is
|
* 'channel_pre*' are called just before IO to add any bits
|
||||||
* updated in channel_new.
|
* relevant to channels in the c->io_want bitmasks.
|
||||||
*/
|
|
||||||
int channel_max_fd;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 'channel_pre*' are called just before select() to add any bits
|
|
||||||
* relevant to channels in the select bitmasks.
|
|
||||||
*
|
*
|
||||||
* 'channel_post*': perform any appropriate operations for
|
* 'channel_post*': perform any appropriate operations for
|
||||||
* channels which have events pending.
|
* channels which have c->io_ready events pending.
|
||||||
*/
|
*/
|
||||||
chan_fn **channel_pre;
|
chan_fn **channel_pre;
|
||||||
chan_fn **channel_post;
|
chan_fn **channel_post;
|
||||||
|
@ -304,13 +302,6 @@ static void
|
||||||
channel_register_fds(struct ssh *ssh, Channel *c, int rfd, int wfd, int efd,
|
channel_register_fds(struct ssh *ssh, Channel *c, int rfd, int wfd, int efd,
|
||||||
int extusage, int nonblock, int is_tty)
|
int extusage, int nonblock, int is_tty)
|
||||||
{
|
{
|
||||||
struct ssh_channels *sc = ssh->chanctxt;
|
|
||||||
|
|
||||||
/* Update the maximum file descriptor value. */
|
|
||||||
sc->channel_max_fd = MAXIMUM(sc->channel_max_fd, rfd);
|
|
||||||
sc->channel_max_fd = MAXIMUM(sc->channel_max_fd, wfd);
|
|
||||||
sc->channel_max_fd = MAXIMUM(sc->channel_max_fd, efd);
|
|
||||||
|
|
||||||
if (rfd != -1)
|
if (rfd != -1)
|
||||||
fcntl(rfd, F_SETFD, FD_CLOEXEC);
|
fcntl(rfd, F_SETFD, FD_CLOEXEC);
|
||||||
if (wfd != -1 && wfd != rfd)
|
if (wfd != -1 && wfd != rfd)
|
||||||
|
@ -422,28 +413,9 @@ channel_new(struct ssh *ssh, char *ctype, int type, int rfd, int wfd, int efd,
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
channel_find_maxfd(struct ssh_channels *sc)
|
|
||||||
{
|
|
||||||
u_int i;
|
|
||||||
int max = 0;
|
|
||||||
Channel *c;
|
|
||||||
|
|
||||||
for (i = 0; i < sc->channels_alloc; i++) {
|
|
||||||
c = sc->channels[i];
|
|
||||||
if (c != NULL) {
|
|
||||||
max = MAXIMUM(max, c->rfd);
|
|
||||||
max = MAXIMUM(max, c->wfd);
|
|
||||||
max = MAXIMUM(max, c->efd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sc->channel_max_fd = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
channel_close_fd(struct ssh *ssh, Channel *c, int *fdp)
|
channel_close_fd(struct ssh *ssh, Channel *c, int *fdp)
|
||||||
{
|
{
|
||||||
struct ssh_channels *sc = ssh->chanctxt;
|
|
||||||
int ret, fd = *fdp;
|
int ret, fd = *fdp;
|
||||||
|
|
||||||
if (fd == -1)
|
if (fd == -1)
|
||||||
|
@ -454,10 +426,29 @@ channel_close_fd(struct ssh *ssh, Channel *c, int *fdp)
|
||||||
(*fdp == c->efd && (c->restore_block & CHANNEL_RESTORE_EFD) != 0))
|
(*fdp == c->efd && (c->restore_block & CHANNEL_RESTORE_EFD) != 0))
|
||||||
(void)fcntl(*fdp, F_SETFL, 0); /* restore blocking */
|
(void)fcntl(*fdp, F_SETFL, 0); /* restore blocking */
|
||||||
|
|
||||||
|
if (*fdp == c->rfd) {
|
||||||
|
c->io_want &= ~SSH_CHAN_IO_RFD;
|
||||||
|
c->io_ready &= ~SSH_CHAN_IO_RFD;
|
||||||
|
c->rfd = -1;
|
||||||
|
}
|
||||||
|
if (*fdp == c->wfd) {
|
||||||
|
c->io_want &= ~SSH_CHAN_IO_WFD;
|
||||||
|
c->io_ready &= ~SSH_CHAN_IO_WFD;
|
||||||
|
c->wfd = -1;
|
||||||
|
}
|
||||||
|
if (*fdp == c->efd) {
|
||||||
|
c->io_want &= ~SSH_CHAN_IO_EFD;
|
||||||
|
c->io_ready &= ~SSH_CHAN_IO_EFD;
|
||||||
|
c->efd = -1;
|
||||||
|
}
|
||||||
|
if (*fdp == c->sock) {
|
||||||
|
c->io_want &= ~SSH_CHAN_IO_SOCK;
|
||||||
|
c->io_ready &= ~SSH_CHAN_IO_SOCK;
|
||||||
|
c->sock = -1;
|
||||||
|
}
|
||||||
|
|
||||||
ret = close(fd);
|
ret = close(fd);
|
||||||
*fdp = -1;
|
*fdp = -1; /* probably redundant */
|
||||||
if (fd == sc->channel_max_fd)
|
|
||||||
channel_find_maxfd(sc);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -679,7 +670,6 @@ channel_free_all(struct ssh *ssh)
|
||||||
free(sc->channels);
|
free(sc->channels);
|
||||||
sc->channels = NULL;
|
sc->channels = NULL;
|
||||||
sc->channels_alloc = 0;
|
sc->channels_alloc = 0;
|
||||||
sc->channel_max_fd = 0;
|
|
||||||
|
|
||||||
free(sc->x11_saved_display);
|
free(sc->x11_saved_display);
|
||||||
sc->x11_saved_display = NULL;
|
sc->x11_saved_display = NULL;
|
||||||
|
@ -871,13 +861,14 @@ channel_format_status(const Channel *c)
|
||||||
char *ret = NULL;
|
char *ret = NULL;
|
||||||
|
|
||||||
xasprintf(&ret, "t%d %s%u i%u/%zu o%u/%zu e[%s]/%zu "
|
xasprintf(&ret, "t%d %s%u i%u/%zu o%u/%zu e[%s]/%zu "
|
||||||
"fd %d/%d/%d sock %d cc %d",
|
"fd %d/%d/%d sock %d cc %d io 0x%02x/0x%02x",
|
||||||
c->type,
|
c->type,
|
||||||
c->have_remote_id ? "r" : "nr", c->remote_id,
|
c->have_remote_id ? "r" : "nr", c->remote_id,
|
||||||
c->istate, sshbuf_len(c->input),
|
c->istate, sshbuf_len(c->input),
|
||||||
c->ostate, sshbuf_len(c->output),
|
c->ostate, sshbuf_len(c->output),
|
||||||
channel_format_extended_usage(c), sshbuf_len(c->extended),
|
channel_format_extended_usage(c), sshbuf_len(c->extended),
|
||||||
c->rfd, c->wfd, c->efd, c->sock, c->ctl_chan);
|
c->rfd, c->wfd, c->efd, c->sock, c->ctl_chan,
|
||||||
|
c->io_want, c->io_ready);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1598,7 +1589,7 @@ rdynamic_close(struct ssh *ssh, Channel *c)
|
||||||
|
|
||||||
/* reverse dynamic port forwarding */
|
/* reverse dynamic port forwarding */
|
||||||
static void
|
static void
|
||||||
channel_before_prepare_select_rdynamic(struct ssh *ssh, Channel *c)
|
channel_before_prepare_io_rdynamic(struct ssh *ssh, Channel *c)
|
||||||
{
|
{
|
||||||
const u_char *p;
|
const u_char *p;
|
||||||
u_int have, len;
|
u_int have, len;
|
||||||
|
@ -1899,7 +1890,6 @@ channel_post_connecting(struct ssh *ssh, Channel *c)
|
||||||
if ((sock = connect_next(&c->connect_ctx)) > 0) {
|
if ((sock = connect_next(&c->connect_ctx)) > 0) {
|
||||||
close(c->sock);
|
close(c->sock);
|
||||||
c->sock = c->rfd = c->wfd = sock;
|
c->sock = c->rfd = c->wfd = sock;
|
||||||
channel_find_maxfd(ssh->chanctxt);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* Exhausted all addresses */
|
/* Exhausted all addresses */
|
||||||
|
@ -2422,12 +2412,13 @@ channel_handler(struct ssh *ssh, int table, time_t *unpause_secs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create sockets before allocating the select bitmasks.
|
* Create sockets before preparing IO.
|
||||||
* This is necessary for things that need to happen after reading
|
* This is necessary for things that need to happen after reading
|
||||||
* the network-input but before channel_prepare_select().
|
* the network-input but need to be completed before IO event setup, e.g.
|
||||||
|
* because they may create new channels.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
channel_before_prepare_select(struct ssh *ssh)
|
channel_before_prepare_io(struct ssh *ssh)
|
||||||
{
|
{
|
||||||
struct ssh_channels *sc = ssh->chanctxt;
|
struct ssh_channels *sc = ssh->chanctxt;
|
||||||
Channel *c;
|
Channel *c;
|
||||||
|
@ -2438,112 +2429,264 @@ channel_before_prepare_select(struct ssh *ssh)
|
||||||
if (c == NULL)
|
if (c == NULL)
|
||||||
continue;
|
continue;
|
||||||
if (c->type == SSH_CHANNEL_RDYNAMIC_OPEN)
|
if (c->type == SSH_CHANNEL_RDYNAMIC_OPEN)
|
||||||
channel_before_prepare_select_rdynamic(ssh, c);
|
channel_before_prepare_io_rdynamic(ssh, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static void
|
||||||
* Allocate/update select bitmasks and add any bits relevant to channels in
|
dump_channel_poll(const char *func, const char *what, Channel *c,
|
||||||
* select bitmasks.
|
u_int pollfd_offset, struct pollfd *pfd)
|
||||||
*/
|
{
|
||||||
|
#ifdef DEBUG_CHANNEL_POLL
|
||||||
|
debug3_f("channel %d: rfd r%d w%d e%d s%d "
|
||||||
|
"pfd[%u].fd=%d want 0x%02x ev 0x%02x ready 0x%02x rev 0x%02x",
|
||||||
|
c->self, c->rfd, c->wfd, c->efd, c->sock, pollfd_offset, pfd->fd,
|
||||||
|
c->io_want, pfd->events, c->io_ready, pfd->revents);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prepare pollfd entries for a single channel */
|
||||||
|
static void
|
||||||
|
channel_prepare_pollfd(Channel *c, u_int *next_pollfd,
|
||||||
|
struct pollfd *pfd, u_int npfd)
|
||||||
|
{
|
||||||
|
u_int p = *next_pollfd;
|
||||||
|
|
||||||
|
if (c == NULL)
|
||||||
|
return;
|
||||||
|
if (p + 4 > npfd) {
|
||||||
|
/* Shouldn't happen */
|
||||||
|
fatal_f("channel %d: bad pfd offset %u (max %u)",
|
||||||
|
c->self, p, npfd);
|
||||||
|
}
|
||||||
|
c->pollfd_offset = -1;
|
||||||
|
/*
|
||||||
|
* prepare c->rfd
|
||||||
|
*
|
||||||
|
* This is a special case, since c->rfd might be the same as
|
||||||
|
* c->wfd, c->efd and/or c->sock. Handle those here if they want
|
||||||
|
* IO too.
|
||||||
|
*/
|
||||||
|
if (c->rfd != -1) {
|
||||||
|
if (c->pollfd_offset == -1)
|
||||||
|
c->pollfd_offset = p;
|
||||||
|
pfd[p].fd = c->rfd;
|
||||||
|
pfd[p].events = 0;
|
||||||
|
if ((c->io_want & SSH_CHAN_IO_RFD) != 0)
|
||||||
|
pfd[p].events |= POLLIN;
|
||||||
|
/* rfd == wfd */
|
||||||
|
if (c->wfd == c->rfd &&
|
||||||
|
(c->io_want & SSH_CHAN_IO_WFD) != 0)
|
||||||
|
pfd[p].events |= POLLOUT;
|
||||||
|
/* rfd == efd */
|
||||||
|
if (c->efd == c->rfd &&
|
||||||
|
(c->io_want & SSH_CHAN_IO_EFD_R) != 0)
|
||||||
|
pfd[p].events |= POLLIN;
|
||||||
|
if (c->efd == c->rfd &&
|
||||||
|
(c->io_want & SSH_CHAN_IO_EFD_W) != 0)
|
||||||
|
pfd[p].events |= POLLOUT;
|
||||||
|
/* rfd == sock */
|
||||||
|
if (c->sock == c->rfd &&
|
||||||
|
(c->io_want & SSH_CHAN_IO_SOCK_R) != 0)
|
||||||
|
pfd[p].events |= POLLIN;
|
||||||
|
if (c->sock == c->rfd &&
|
||||||
|
(c->io_want & SSH_CHAN_IO_SOCK_W) != 0)
|
||||||
|
pfd[p].events |= POLLOUT;
|
||||||
|
dump_channel_poll(__func__, "rfd", c, p, &pfd[p]);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
/* prepare c->wfd (if not already handled above) */
|
||||||
|
if (c->wfd != -1 && c->rfd != c->wfd) {
|
||||||
|
if (c->pollfd_offset == -1)
|
||||||
|
c->pollfd_offset = p;
|
||||||
|
pfd[p].fd = c->wfd;
|
||||||
|
pfd[p].events = 0;
|
||||||
|
if ((c->io_want & SSH_CHAN_IO_WFD) != 0)
|
||||||
|
pfd[p].events = POLLOUT;
|
||||||
|
dump_channel_poll(__func__, "wfd", c, p, &pfd[p]);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
/* prepare c->efd (if not already handled above) */
|
||||||
|
if (c->efd != -1 && c->rfd != c->efd) {
|
||||||
|
if (c->pollfd_offset == -1)
|
||||||
|
c->pollfd_offset = p;
|
||||||
|
pfd[p].fd = c->efd;
|
||||||
|
pfd[p].events = 0;
|
||||||
|
if ((c->io_want & SSH_CHAN_IO_EFD_R) != 0)
|
||||||
|
pfd[p].events |= POLLIN;
|
||||||
|
if ((c->io_want & SSH_CHAN_IO_EFD_W) != 0)
|
||||||
|
pfd[p].events |= POLLOUT;
|
||||||
|
dump_channel_poll(__func__, "efd", c, p, &pfd[p]);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
/* prepare c->sock (if not already handled above) */
|
||||||
|
if (c->sock != -1 && c->rfd != c->sock) {
|
||||||
|
if (c->pollfd_offset == -1)
|
||||||
|
c->pollfd_offset = p;
|
||||||
|
pfd[p].fd = c->sock;
|
||||||
|
pfd[p].events = 0;
|
||||||
|
if ((c->io_want & SSH_CHAN_IO_SOCK_R) != 0)
|
||||||
|
pfd[p].events |= POLLIN;
|
||||||
|
if ((c->io_want & SSH_CHAN_IO_SOCK_W) != 0)
|
||||||
|
pfd[p].events |= POLLOUT;
|
||||||
|
dump_channel_poll(__func__, "sock", c, p, &pfd[p]);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
*next_pollfd = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* * Allocate/prepare poll structure */
|
||||||
void
|
void
|
||||||
channel_prepare_select(struct ssh *ssh, fd_set **readsetp, fd_set **writesetp,
|
channel_prepare_poll(struct ssh *ssh, struct pollfd **pfdp, u_int *npfd_allocp,
|
||||||
int *maxfdp, u_int *nallocp, time_t *minwait_secs)
|
u_int *npfd_activep, u_int npfd_reserved, time_t *minwait_secs)
|
||||||
{
|
{
|
||||||
struct ssh_channels *sc = ssh->chanctxt;
|
struct ssh_channels *sc = ssh->chanctxt;
|
||||||
u_int i, n, sz, nfdset, oalloc = sc->channels_alloc;
|
u_int i, oalloc, p, npfd = npfd_reserved;
|
||||||
Channel *c;
|
|
||||||
|
|
||||||
channel_before_prepare_select(ssh); /* might update channel_max_fd */
|
channel_before_prepare_io(ssh); /* might create a new channel */
|
||||||
|
|
||||||
n = MAXIMUM(*maxfdp, ssh->chanctxt->channel_max_fd);
|
|
||||||
|
|
||||||
nfdset = howmany(n+1, NFDBITS);
|
|
||||||
/* Explicitly test here, because xrealloc isn't always called */
|
|
||||||
if (nfdset && SIZE_MAX / nfdset < sizeof(fd_mask))
|
|
||||||
fatal("channel_prepare_select: max_fd (%d) is too large", n);
|
|
||||||
sz = nfdset * sizeof(fd_mask);
|
|
||||||
|
|
||||||
/* perhaps check sz < nalloc/2 and shrink? */
|
|
||||||
if (*readsetp == NULL || sz > *nallocp) {
|
|
||||||
*readsetp = xreallocarray(*readsetp, nfdset, sizeof(fd_mask));
|
|
||||||
*writesetp = xreallocarray(*writesetp, nfdset, sizeof(fd_mask));
|
|
||||||
*nallocp = sz;
|
|
||||||
}
|
|
||||||
*maxfdp = n;
|
|
||||||
memset(*readsetp, 0, sz);
|
|
||||||
memset(*writesetp, 0, sz);
|
|
||||||
|
|
||||||
|
/* Allocate 4x pollfd for each channel (rfd, wfd, efd, sock) */
|
||||||
|
if (sc->channels_alloc >= (INT_MAX / 4) - npfd_reserved)
|
||||||
|
fatal_f("too many channels"); /* shouldn't happen */
|
||||||
if (!ssh_packet_is_rekeying(ssh))
|
if (!ssh_packet_is_rekeying(ssh))
|
||||||
channel_handler(ssh, CHAN_PRE, minwait_secs);
|
npfd += sc->channels_alloc * 4;
|
||||||
|
if (npfd > *npfd_allocp) {
|
||||||
/* Convert c->io_want into FD_SET */
|
*pfdp = xrecallocarray(*pfdp, *npfd_allocp,
|
||||||
for (i = 0; i < oalloc; i++) {
|
npfd, sizeof(**pfdp));
|
||||||
c = sc->channels[i];
|
*npfd_allocp = npfd;
|
||||||
if (c == NULL)
|
|
||||||
continue;
|
|
||||||
if ((c->io_want & SSH_CHAN_IO_RFD) != 0) {
|
|
||||||
if (c->rfd == -1)
|
|
||||||
fatal_f("channel %d: no rfd", c->self);
|
|
||||||
FD_SET(c->rfd, *readsetp);
|
|
||||||
}
|
|
||||||
if ((c->io_want & SSH_CHAN_IO_WFD) != 0) {
|
|
||||||
if (c->wfd == -1)
|
|
||||||
fatal_f("channel %d: no wfd", c->self);
|
|
||||||
FD_SET(c->wfd, *writesetp);
|
|
||||||
}
|
|
||||||
if ((c->io_want & SSH_CHAN_IO_EFD_R) != 0) {
|
|
||||||
if (c->efd == -1)
|
|
||||||
fatal_f("channel %d: no efd(r)", c->self);
|
|
||||||
FD_SET(c->efd, *readsetp);
|
|
||||||
}
|
|
||||||
if ((c->io_want & SSH_CHAN_IO_EFD_W) != 0) {
|
|
||||||
if (c->efd == -1)
|
|
||||||
fatal_f("channel %d: no efd(w)", c->self);
|
|
||||||
FD_SET(c->efd, *writesetp);
|
|
||||||
}
|
|
||||||
if ((c->io_want & SSH_CHAN_IO_SOCK_R) != 0) {
|
|
||||||
if (c->sock == -1)
|
|
||||||
fatal_f("channel %d: no sock(r)", c->self);
|
|
||||||
FD_SET(c->sock, *readsetp);
|
|
||||||
}
|
|
||||||
if ((c->io_want & SSH_CHAN_IO_SOCK_W) != 0) {
|
|
||||||
if (c->sock == -1)
|
|
||||||
fatal_f("channel %d: no sock(w)", c->self);
|
|
||||||
FD_SET(c->sock, *writesetp);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
*npfd_activep = npfd_reserved;
|
||||||
|
if (ssh_packet_is_rekeying(ssh))
|
||||||
|
return;
|
||||||
|
|
||||||
|
oalloc = sc->channels_alloc;
|
||||||
|
|
||||||
|
channel_handler(ssh, CHAN_PRE, minwait_secs);
|
||||||
|
|
||||||
|
if (oalloc != sc->channels_alloc) {
|
||||||
|
/* shouldn't happen */
|
||||||
|
fatal_f("channels_alloc changed during CHAN_PRE "
|
||||||
|
"(was %u, now %u)", oalloc, sc->channels_alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prepare pollfd */
|
||||||
|
p = npfd_reserved;
|
||||||
|
for (i = 0; i < sc->channels_alloc; i++)
|
||||||
|
channel_prepare_pollfd(sc->channels[i], &p, *pfdp, npfd);
|
||||||
|
*npfd_activep = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fd_ready(Channel *c, u_int p, struct pollfd *pfds, int fd,
|
||||||
|
const char *what, u_int revents_mask, u_int ready)
|
||||||
|
{
|
||||||
|
struct pollfd *pfd = &pfds[p];
|
||||||
|
|
||||||
|
if (fd == -1)
|
||||||
|
return;
|
||||||
|
dump_channel_poll(__func__, what, c, p, pfd);
|
||||||
|
if (pfd->fd != fd) {
|
||||||
|
fatal("channel %d: inconsistent %s fd=%d pollfd[%u].fd %d "
|
||||||
|
"r%d w%d e%d s%d", c->self, what, fd, p, pfd->fd,
|
||||||
|
c->rfd, c->wfd, c->efd, c->sock);
|
||||||
|
}
|
||||||
|
if ((pfd->revents & POLLNVAL) != 0) {
|
||||||
|
fatal("channel %d: invalid %s pollfd[%u].fd %d r%d w%d e%d s%d",
|
||||||
|
c->self, what, p, pfd->fd, c->rfd, c->wfd, c->efd, c->sock);
|
||||||
|
}
|
||||||
|
if ((pfd->revents & (revents_mask|POLLHUP|POLLERR)) != 0)
|
||||||
|
c->io_ready |= ready & c->io_want;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* After select, perform any appropriate operations for channels which have
|
* After poll, perform any appropriate operations for channels which have
|
||||||
* events pending.
|
* events pending.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
channel_after_select(struct ssh *ssh, fd_set *readset, fd_set *writeset)
|
channel_after_poll(struct ssh *ssh, struct pollfd *pfd, u_int npfd)
|
||||||
{
|
{
|
||||||
struct ssh_channels *sc = ssh->chanctxt;
|
struct ssh_channels *sc = ssh->chanctxt;
|
||||||
|
u_int i, p;
|
||||||
Channel *c;
|
Channel *c;
|
||||||
u_int i, oalloc = sc->channels_alloc;
|
|
||||||
|
|
||||||
/* Convert FD_SET into c->io_ready */
|
#ifdef DEBUG_CHANNEL_POLL
|
||||||
for (i = 0; i < oalloc; i++) {
|
for (p = 0; p < npfd; p++) {
|
||||||
c = sc->channels[i];
|
if (pfd[p].revents == 0)
|
||||||
if (c == NULL)
|
|
||||||
continue;
|
continue;
|
||||||
|
debug_f("pfd[%u].fd %d rev 0x%04x",
|
||||||
|
p, pfd[p].fd, pfd[p].revents);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Convert pollfd into c->io_ready */
|
||||||
|
for (i = 0; i < sc->channels_alloc; i++) {
|
||||||
|
c = sc->channels[i];
|
||||||
|
if (c == NULL || c->pollfd_offset < 0)
|
||||||
|
continue;
|
||||||
|
if ((u_int)c->pollfd_offset >= npfd) {
|
||||||
|
/* shouldn't happen */
|
||||||
|
fatal_f("channel %d: (before) bad pfd %u (max %u)",
|
||||||
|
c->self, c->pollfd_offset, npfd);
|
||||||
|
}
|
||||||
|
/* if rfd is shared with efd/sock then wfd should be too */
|
||||||
|
if (c->rfd != -1 && c->wfd != -1 && c->rfd != c->wfd &&
|
||||||
|
(c->rfd == c->efd || c->rfd == c->sock)) {
|
||||||
|
/* Shouldn't happen */
|
||||||
|
fatal_f("channel %d: unexpected fds r%d w%d e%d s%d",
|
||||||
|
c->self, c->rfd, c->wfd, c->efd, c->sock);
|
||||||
|
}
|
||||||
c->io_ready = 0;
|
c->io_ready = 0;
|
||||||
if (c->rfd != -1 && FD_ISSET(c->rfd, readset))
|
p = c->pollfd_offset;
|
||||||
c->io_ready |= SSH_CHAN_IO_RFD;
|
/* rfd, potentially shared with wfd, efd and sock */
|
||||||
if (c->wfd != -1 && FD_ISSET(c->wfd, writeset))
|
if (c->rfd != -1) {
|
||||||
c->io_ready |= SSH_CHAN_IO_WFD;
|
fd_ready(c, p, pfd, c->rfd, "rfd", POLLIN,
|
||||||
if (c->efd != -1 && FD_ISSET(c->efd, readset))
|
SSH_CHAN_IO_RFD);
|
||||||
c->io_ready |= SSH_CHAN_IO_EFD_R;
|
if (c->rfd == c->wfd) {
|
||||||
if (c->efd != -1 && FD_ISSET(c->efd, writeset))
|
fd_ready(c, p, pfd, c->wfd, "wfd/r", POLLOUT,
|
||||||
c->io_ready |= SSH_CHAN_IO_EFD_W;
|
SSH_CHAN_IO_WFD);
|
||||||
if (c->sock != -1 && FD_ISSET(c->sock, readset))
|
}
|
||||||
c->io_ready |= SSH_CHAN_IO_SOCK_R;
|
if (c->rfd == c->efd) {
|
||||||
if (c->sock != -1 && FD_ISSET(c->sock, writeset))
|
fd_ready(c, p, pfd, c->efd, "efdr/r", POLLIN,
|
||||||
c->io_ready |= SSH_CHAN_IO_SOCK_W;
|
SSH_CHAN_IO_EFD_R);
|
||||||
|
fd_ready(c, p, pfd, c->efd, "efdw/r", POLLOUT,
|
||||||
|
SSH_CHAN_IO_EFD_W);
|
||||||
|
}
|
||||||
|
if (c->rfd == c->sock) {
|
||||||
|
fd_ready(c, p, pfd, c->sock, "sockr/r", POLLIN,
|
||||||
|
SSH_CHAN_IO_SOCK_R);
|
||||||
|
fd_ready(c, p, pfd, c->sock, "sockw/r", POLLOUT,
|
||||||
|
SSH_CHAN_IO_SOCK_W);
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
/* wfd */
|
||||||
|
if (c->wfd != -1 && c->wfd != c->rfd) {
|
||||||
|
fd_ready(c, p, pfd, c->wfd, "wfd", POLLOUT,
|
||||||
|
SSH_CHAN_IO_WFD);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
/* efd */
|
||||||
|
if (c->efd != -1 && c->efd != c->rfd) {
|
||||||
|
fd_ready(c, p, pfd, c->efd, "efdr", POLLIN,
|
||||||
|
SSH_CHAN_IO_EFD_R);
|
||||||
|
fd_ready(c, p, pfd, c->efd, "efdw", POLLOUT,
|
||||||
|
SSH_CHAN_IO_EFD_W);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
/* sock */
|
||||||
|
if (c->sock != -1 && c->sock != c->rfd) {
|
||||||
|
fd_ready(c, p, pfd, c->sock, "sockr", POLLIN,
|
||||||
|
SSH_CHAN_IO_SOCK_R);
|
||||||
|
fd_ready(c, p, pfd, c->sock, "sockw", POLLOUT,
|
||||||
|
SSH_CHAN_IO_SOCK_W);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p > npfd) {
|
||||||
|
/* shouldn't happen */
|
||||||
|
fatal_f("channel %d: (after) bad pfd %u (max %u)",
|
||||||
|
c->self, c->pollfd_offset, npfd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
channel_handler(ssh, CHAN_POST, NULL);
|
channel_handler(ssh, CHAN_POST, NULL);
|
||||||
}
|
}
|
||||||
|
|
18
channels.h
18
channels.h
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: channels.h,v 1.139 2022/01/06 21:46:23 djm Exp $ */
|
/* $OpenBSD: channels.h,v 1.140 2022/01/06 21:48:38 djm Exp $ */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
|
@ -138,6 +138,7 @@ struct Channel {
|
||||||
int sock; /* sock fd */
|
int sock; /* sock fd */
|
||||||
u_int io_want; /* bitmask of SSH_CHAN_IO_* */
|
u_int io_want; /* bitmask of SSH_CHAN_IO_* */
|
||||||
u_int io_ready; /* bitmask of SSH_CHAN_IO_* */
|
u_int io_ready; /* bitmask of SSH_CHAN_IO_* */
|
||||||
|
int pollfd_offset; /* base offset into pollfd array (or -1) */
|
||||||
int ctl_chan; /* control channel (multiplexed connections) */
|
int ctl_chan; /* control channel (multiplexed connections) */
|
||||||
int isatty; /* rfd is a tty */
|
int isatty; /* rfd is a tty */
|
||||||
#ifdef _AIX
|
#ifdef _AIX
|
||||||
|
@ -146,10 +147,10 @@ struct Channel {
|
||||||
int client_tty; /* (client) TTY has been requested */
|
int client_tty; /* (client) TTY has been requested */
|
||||||
int force_drain; /* force close on iEOF */
|
int force_drain; /* force close on iEOF */
|
||||||
time_t notbefore; /* Pause IO until deadline (time_t) */
|
time_t notbefore; /* Pause IO until deadline (time_t) */
|
||||||
int delayed; /* post-select handlers for newly created
|
int delayed; /* post-IO handlers for newly created
|
||||||
* channels are delayed until the first call
|
* channels are delayed until the first call
|
||||||
* to a matching pre-select handler.
|
* to a matching pre-IO handler.
|
||||||
* this way post-select handlers are not
|
* this way post-IO handlers are not
|
||||||
* accidentally called if a FD gets reused */
|
* accidentally called if a FD gets reused */
|
||||||
int restore_block; /* fd mask to restore blocking status */
|
int restore_block; /* fd mask to restore blocking status */
|
||||||
struct sshbuf *input; /* data read from socket, to be sent over
|
struct sshbuf *input; /* data read from socket, to be sent over
|
||||||
|
@ -238,8 +239,10 @@ struct Channel {
|
||||||
#define SSH_CHAN_IO_WFD 0x02
|
#define SSH_CHAN_IO_WFD 0x02
|
||||||
#define SSH_CHAN_IO_EFD_R 0x04
|
#define SSH_CHAN_IO_EFD_R 0x04
|
||||||
#define SSH_CHAN_IO_EFD_W 0x08
|
#define SSH_CHAN_IO_EFD_W 0x08
|
||||||
|
#define SSH_CHAN_IO_EFD (SSH_CHAN_IO_EFD_R|SSH_CHAN_IO_EFD_W)
|
||||||
#define SSH_CHAN_IO_SOCK_R 0x10
|
#define SSH_CHAN_IO_SOCK_R 0x10
|
||||||
#define SSH_CHAN_IO_SOCK_W 0x20
|
#define SSH_CHAN_IO_SOCK_W 0x20
|
||||||
|
#define SSH_CHAN_IO_SOCK (SSH_CHAN_IO_SOCK_R|SSH_CHAN_IO_SOCK_W)
|
||||||
|
|
||||||
/* Read buffer size */
|
/* Read buffer size */
|
||||||
#define CHAN_RBUF (16*1024)
|
#define CHAN_RBUF (16*1024)
|
||||||
|
@ -308,10 +311,11 @@ int channel_input_window_adjust(int, u_int32_t, struct ssh *);
|
||||||
int channel_input_status_confirm(int, u_int32_t, struct ssh *);
|
int channel_input_status_confirm(int, u_int32_t, struct ssh *);
|
||||||
|
|
||||||
/* file descriptor handling (read/write) */
|
/* file descriptor handling (read/write) */
|
||||||
|
struct pollfd;
|
||||||
|
|
||||||
void channel_prepare_select(struct ssh *, fd_set **, fd_set **, int *,
|
void channel_prepare_poll(struct ssh *, struct pollfd **,
|
||||||
u_int*, time_t*);
|
u_int *, u_int *, u_int, time_t *);
|
||||||
void channel_after_select(struct ssh *, fd_set *, fd_set *);
|
void channel_after_poll(struct ssh *, struct pollfd *, u_int);
|
||||||
void channel_output_poll(struct ssh *);
|
void channel_output_poll(struct ssh *);
|
||||||
|
|
||||||
int channel_not_very_much_buffered_data(struct ssh *);
|
int channel_not_very_much_buffered_data(struct ssh *);
|
||||||
|
|
173
clientloop.c
173
clientloop.c
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: clientloop.c,v 1.373 2022/01/01 01:55:30 jsg Exp $ */
|
/* $OpenBSD: clientloop.c,v 1.374 2022/01/06 21:48:38 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||||
|
@ -76,6 +76,7 @@
|
||||||
#ifdef HAVE_PATHS_H
|
#ifdef HAVE_PATHS_H
|
||||||
#include <paths.h>
|
#include <paths.h>
|
||||||
#endif
|
#endif
|
||||||
|
#include <poll.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
@ -488,37 +489,41 @@ server_alive_check(struct ssh *ssh)
|
||||||
* one of the file descriptors).
|
* one of the file descriptors).
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
client_wait_until_can_do_something(struct ssh *ssh,
|
client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp,
|
||||||
fd_set **readsetp, fd_set **writesetp,
|
u_int *npfd_allocp, u_int *npfd_activep, int rekeying,
|
||||||
int *maxfdp, u_int *nallocp, int rekeying)
|
int *conn_in_readyp, int *conn_out_readyp)
|
||||||
{
|
{
|
||||||
struct timeval tv, *tvp;
|
int timeout_secs, pollwait;
|
||||||
int timeout_secs;
|
|
||||||
time_t minwait_secs = 0, now = monotime();
|
time_t minwait_secs = 0, now = monotime();
|
||||||
int r, ret;
|
int r, ret;
|
||||||
|
u_int p;
|
||||||
|
|
||||||
/* Add any selections by the channel mechanism. */
|
*conn_in_readyp = *conn_out_readyp = 0;
|
||||||
channel_prepare_select(ssh, readsetp, writesetp, maxfdp,
|
|
||||||
nallocp, &minwait_secs);
|
|
||||||
|
|
||||||
/* channel_prepare_select could have closed the last channel */
|
/* Prepare channel poll. First two pollfd entries are reserved */
|
||||||
|
channel_prepare_poll(ssh, pfdp, npfd_allocp, npfd_activep, 2,
|
||||||
|
&minwait_secs);
|
||||||
|
if (*npfd_activep < 2)
|
||||||
|
fatal_f("bad npfd %u", *npfd_activep); /* shouldn't happen */
|
||||||
|
|
||||||
|
/* channel_prepare_poll could have closed the last channel */
|
||||||
if (session_closed && !channel_still_open(ssh) &&
|
if (session_closed && !channel_still_open(ssh) &&
|
||||||
!ssh_packet_have_data_to_write(ssh)) {
|
!ssh_packet_have_data_to_write(ssh)) {
|
||||||
/* clear mask since we did not call select() */
|
/* clear events since we did not call poll() */
|
||||||
memset(*readsetp, 0, *nallocp);
|
for (p = 0; p < *npfd_activep; p++)
|
||||||
memset(*writesetp, 0, *nallocp);
|
(*pfdp)[p].revents = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FD_SET(connection_in, *readsetp);
|
/* Monitor server connection on reserved pollfd entries */
|
||||||
|
(*pfdp)[0].fd = connection_in;
|
||||||
/* Select server connection if have data to write to the server. */
|
(*pfdp)[0].events = POLLIN;
|
||||||
if (ssh_packet_have_data_to_write(ssh))
|
(*pfdp)[1].fd = connection_out;
|
||||||
FD_SET(connection_out, *writesetp);
|
(*pfdp)[1].events = ssh_packet_have_data_to_write(ssh) ? POLLOUT : 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Wait for something to happen. This will suspend the process until
|
* Wait for something to happen. This will suspend the process until
|
||||||
* some selected descriptor can be read, written, or has some other
|
* some polled descriptor can be read, written, or has some other
|
||||||
* event pending, or a timeout expires.
|
* event pending, or a timeout expires.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -538,37 +543,44 @@ client_wait_until_can_do_something(struct ssh *ssh,
|
||||||
if (minwait_secs != 0)
|
if (minwait_secs != 0)
|
||||||
timeout_secs = MINIMUM(timeout_secs, (int)minwait_secs);
|
timeout_secs = MINIMUM(timeout_secs, (int)minwait_secs);
|
||||||
if (timeout_secs == INT_MAX)
|
if (timeout_secs == INT_MAX)
|
||||||
tvp = NULL;
|
pollwait = -1;
|
||||||
else {
|
else if (timeout_secs >= INT_MAX / 1000)
|
||||||
tv.tv_sec = timeout_secs;
|
pollwait = INT_MAX;
|
||||||
tv.tv_usec = 0;
|
else
|
||||||
tvp = &tv;
|
pollwait = timeout_secs * 1000;
|
||||||
}
|
|
||||||
|
ret = poll(*pfdp, *npfd_activep, pollwait);
|
||||||
|
|
||||||
ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp);
|
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
/*
|
/*
|
||||||
* We have to clear the select masks, because we return.
|
* We have to clear the events because we return.
|
||||||
* We have to return, because the mainloop checks for the flags
|
* We have to return, because the mainloop checks for the flags
|
||||||
* set by the signal handlers.
|
* set by the signal handlers.
|
||||||
*/
|
*/
|
||||||
memset(*readsetp, 0, *nallocp);
|
for (p = 0; p < *npfd_activep; p++)
|
||||||
memset(*writesetp, 0, *nallocp);
|
(*pfdp)[p].revents = 0;
|
||||||
if (errno == EINTR)
|
if (errno == EINTR)
|
||||||
return;
|
return;
|
||||||
/* Note: we might still have data in the buffers. */
|
/* Note: we might still have data in the buffers. */
|
||||||
if ((r = sshbuf_putf(stderr_buffer,
|
if ((r = sshbuf_putf(stderr_buffer,
|
||||||
"select: %s\r\n", strerror(errno))) != 0)
|
"poll: %s\r\n", strerror(errno))) != 0)
|
||||||
fatal_fr(r, "sshbuf_putf");
|
fatal_fr(r, "sshbuf_putf");
|
||||||
quit_pending = 1;
|
quit_pending = 1;
|
||||||
} else if (options.server_alive_interval > 0 && !FD_ISSET(connection_in,
|
return;
|
||||||
*readsetp) && monotime() >= server_alive_time)
|
}
|
||||||
|
|
||||||
|
*conn_in_readyp = (*pfdp)[0].revents != 0;
|
||||||
|
*conn_out_readyp = (*pfdp)[1].revents != 0;
|
||||||
|
|
||||||
|
if (options.server_alive_interval > 0 && !*conn_in_readyp &&
|
||||||
|
monotime() >= server_alive_time) {
|
||||||
/*
|
/*
|
||||||
* ServerAlive check is needed. We can't rely on the select
|
* ServerAlive check is needed. We can't rely on the poll
|
||||||
* timing out since traffic on the client side such as port
|
* timing out since traffic on the client side such as port
|
||||||
* forwards can keep waking it up.
|
* forwards can keep waking it up.
|
||||||
*/
|
*/
|
||||||
server_alive_check(ssh);
|
server_alive_check(ssh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -598,7 +610,7 @@ client_suspend_self(struct sshbuf *bin, struct sshbuf *bout, struct sshbuf *berr
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
client_process_net_input(struct ssh *ssh, fd_set *readset)
|
client_process_net_input(struct ssh *ssh)
|
||||||
{
|
{
|
||||||
char buf[SSH_IOBUFSZ];
|
char buf[SSH_IOBUFSZ];
|
||||||
int r, len;
|
int r, len;
|
||||||
|
@ -607,44 +619,39 @@ client_process_net_input(struct ssh *ssh, fd_set *readset)
|
||||||
* Read input from the server, and add any such data to the buffer of
|
* Read input from the server, and add any such data to the buffer of
|
||||||
* the packet subsystem.
|
* the packet subsystem.
|
||||||
*/
|
*/
|
||||||
if (FD_ISSET(connection_in, readset)) {
|
schedule_server_alive_check();
|
||||||
schedule_server_alive_check();
|
/* Read as much as possible. */
|
||||||
/* Read as much as possible. */
|
len = read(connection_in, buf, sizeof(buf));
|
||||||
len = read(connection_in, buf, sizeof(buf));
|
if (len == 0) {
|
||||||
if (len == 0) {
|
/* Received EOF. The remote host has closed the connection. */
|
||||||
/*
|
if ((r = sshbuf_putf(stderr_buffer,
|
||||||
* Received EOF. The remote host has closed the
|
"Connection to %.300s closed by remote host.\r\n",
|
||||||
* connection.
|
host)) != 0)
|
||||||
*/
|
fatal_fr(r, "sshbuf_putf");
|
||||||
if ((r = sshbuf_putf(stderr_buffer,
|
quit_pending = 1;
|
||||||
"Connection to %.300s closed by remote host.\r\n",
|
return;
|
||||||
host)) != 0)
|
|
||||||
fatal_fr(r, "sshbuf_putf");
|
|
||||||
quit_pending = 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* There is a kernel bug on Solaris that causes select to
|
|
||||||
* sometimes wake up even though there is no data available.
|
|
||||||
*/
|
|
||||||
if (len == -1 &&
|
|
||||||
(errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK))
|
|
||||||
len = 0;
|
|
||||||
|
|
||||||
if (len == -1) {
|
|
||||||
/*
|
|
||||||
* An error has encountered. Perhaps there is a
|
|
||||||
* network problem.
|
|
||||||
*/
|
|
||||||
if ((r = sshbuf_putf(stderr_buffer,
|
|
||||||
"Read from remote host %.300s: %.100s\r\n",
|
|
||||||
host, strerror(errno))) != 0)
|
|
||||||
fatal_fr(r, "sshbuf_putf");
|
|
||||||
quit_pending = 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ssh_packet_process_incoming(ssh, buf, len);
|
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* There is a kernel bug on Solaris that causes poll to
|
||||||
|
* sometimes wake up even though there is no data available.
|
||||||
|
*/
|
||||||
|
if (len == -1 &&
|
||||||
|
(errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK))
|
||||||
|
len = 0;
|
||||||
|
|
||||||
|
if (len == -1) {
|
||||||
|
/*
|
||||||
|
* An error has encountered. Perhaps there is a
|
||||||
|
* network problem.
|
||||||
|
*/
|
||||||
|
if ((r = sshbuf_putf(stderr_buffer,
|
||||||
|
"Read from remote host %.300s: %.100s\r\n",
|
||||||
|
host, strerror(errno))) != 0)
|
||||||
|
fatal_fr(r, "sshbuf_putf");
|
||||||
|
quit_pending = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ssh_packet_process_incoming(ssh, buf, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -1209,11 +1216,12 @@ int
|
||||||
client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
|
client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
|
||||||
int ssh2_chan_id)
|
int ssh2_chan_id)
|
||||||
{
|
{
|
||||||
fd_set *readset = NULL, *writeset = NULL;
|
struct pollfd *pfd = NULL;
|
||||||
|
u_int npfd_alloc = 0, npfd_active = 0;
|
||||||
double start_time, total_time;
|
double start_time, total_time;
|
||||||
int r, max_fd = 0, max_fd2 = 0, len;
|
int r, len;
|
||||||
u_int64_t ibytes, obytes;
|
u_int64_t ibytes, obytes;
|
||||||
u_int nalloc = 0;
|
int conn_in_ready, conn_out_ready;
|
||||||
|
|
||||||
debug("Entering interactive session.");
|
debug("Entering interactive session.");
|
||||||
|
|
||||||
|
@ -1255,7 +1263,6 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
|
||||||
exit_status = -1;
|
exit_status = -1;
|
||||||
connection_in = ssh_packet_get_connection_in(ssh);
|
connection_in = ssh_packet_get_connection_in(ssh);
|
||||||
connection_out = ssh_packet_get_connection_out(ssh);
|
connection_out = ssh_packet_get_connection_out(ssh);
|
||||||
max_fd = MAXIMUM(connection_in, connection_out);
|
|
||||||
|
|
||||||
quit_pending = 0;
|
quit_pending = 0;
|
||||||
|
|
||||||
|
@ -1335,19 +1342,20 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
|
||||||
* Wait until we have something to do (something becomes
|
* Wait until we have something to do (something becomes
|
||||||
* available on one of the descriptors).
|
* available on one of the descriptors).
|
||||||
*/
|
*/
|
||||||
max_fd2 = max_fd;
|
client_wait_until_can_do_something(ssh, &pfd, &npfd_alloc,
|
||||||
client_wait_until_can_do_something(ssh, &readset, &writeset,
|
&npfd_active, ssh_packet_is_rekeying(ssh),
|
||||||
&max_fd2, &nalloc, ssh_packet_is_rekeying(ssh));
|
&conn_in_ready, &conn_out_ready);
|
||||||
|
|
||||||
if (quit_pending)
|
if (quit_pending)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Do channel operations unless rekeying in progress. */
|
/* Do channel operations unless rekeying in progress. */
|
||||||
if (!ssh_packet_is_rekeying(ssh))
|
if (!ssh_packet_is_rekeying(ssh))
|
||||||
channel_after_select(ssh, readset, writeset);
|
channel_after_poll(ssh, pfd, npfd_active);
|
||||||
|
|
||||||
/* Buffer input from the connection. */
|
/* Buffer input from the connection. */
|
||||||
client_process_net_input(ssh, readset);
|
if (conn_in_ready)
|
||||||
|
client_process_net_input(ssh);
|
||||||
|
|
||||||
if (quit_pending)
|
if (quit_pending)
|
||||||
break;
|
break;
|
||||||
|
@ -1360,7 +1368,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
|
||||||
* Send as much buffered packet data as possible to the
|
* Send as much buffered packet data as possible to the
|
||||||
* sender.
|
* sender.
|
||||||
*/
|
*/
|
||||||
if (FD_ISSET(connection_out, writeset)) {
|
if (conn_out_ready) {
|
||||||
if ((r = ssh_packet_write_poll(ssh)) != 0) {
|
if ((r = ssh_packet_write_poll(ssh)) != 0) {
|
||||||
sshpkt_fatal(ssh, r,
|
sshpkt_fatal(ssh, r,
|
||||||
"%s: ssh_packet_write_poll", __func__);
|
"%s: ssh_packet_write_poll", __func__);
|
||||||
|
@ -1379,8 +1387,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(readset);
|
free(pfd);
|
||||||
free(writeset);
|
|
||||||
|
|
||||||
/* Terminate the session. */
|
/* Terminate the session. */
|
||||||
|
|
||||||
|
|
133
serverloop.c
133
serverloop.c
|
@ -1,4 +1,4 @@
|
||||||
/* $OpenBSD: serverloop.c,v 1.228 2021/07/16 09:00:23 djm Exp $ */
|
/* $OpenBSD: serverloop.c,v 1.229 2022/01/06 21:48:38 djm Exp $ */
|
||||||
/*
|
/*
|
||||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||||
|
@ -50,6 +50,7 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <poll.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
|
@ -158,27 +159,31 @@ client_alive_check(struct ssh *ssh)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sleep in pselect() until we can do something. This will initialize the
|
* Sleep in ppoll() until we can do something.
|
||||||
* pselect masks. Upon return, the masks will indicate which descriptors
|
* Optionally, a maximum time can be specified for the duration of
|
||||||
* have data or can accept data. Optionally, a maximum time can be specified
|
* the wait (0 = infinite).
|
||||||
* for the duration of the wait (0 = infinite).
|
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
wait_until_can_do_something(struct ssh *ssh,
|
wait_until_can_do_something(struct ssh *ssh,
|
||||||
int connection_in, int connection_out,
|
int connection_in, int connection_out, struct pollfd **pfdp,
|
||||||
fd_set **readsetp, fd_set **writesetp, int *maxfdp,
|
u_int *npfd_allocp, u_int *npfd_activep, u_int64_t max_time_ms,
|
||||||
u_int *nallocp, u_int64_t max_time_ms, sigset_t *sigsetp)
|
sigset_t *sigsetp, int *conn_in_readyp, int *conn_out_readyp)
|
||||||
{
|
{
|
||||||
struct timespec ts, *tsp;
|
struct timespec ts, *tsp;
|
||||||
int ret;
|
int ret;
|
||||||
time_t minwait_secs = 0;
|
time_t minwait_secs = 0;
|
||||||
int client_alive_scheduled = 0;
|
int client_alive_scheduled = 0;
|
||||||
|
u_int p;
|
||||||
/* time we last heard from the client OR sent a keepalive */
|
/* time we last heard from the client OR sent a keepalive */
|
||||||
static time_t last_client_time;
|
static time_t last_client_time;
|
||||||
|
|
||||||
/* Allocate and update pselect() masks for channel descriptors. */
|
*conn_in_readyp = *conn_out_readyp = 0;
|
||||||
channel_prepare_select(ssh, readsetp, writesetp, maxfdp,
|
|
||||||
nallocp, &minwait_secs);
|
/* Prepare channel poll. First two pollfd entries are reserved */
|
||||||
|
channel_prepare_poll(ssh, pfdp, npfd_allocp, npfd_activep,
|
||||||
|
2, &minwait_secs);
|
||||||
|
if (*npfd_activep < 2)
|
||||||
|
fatal_f("bad npfd %u", *npfd_activep); /* shouldn't happen */
|
||||||
|
|
||||||
/* XXX need proper deadline system for rekey/client alive */
|
/* XXX need proper deadline system for rekey/client alive */
|
||||||
if (minwait_secs != 0)
|
if (minwait_secs != 0)
|
||||||
|
@ -208,14 +213,11 @@ wait_until_can_do_something(struct ssh *ssh,
|
||||||
/* wrong: bad condition XXX */
|
/* wrong: bad condition XXX */
|
||||||
if (channel_not_very_much_buffered_data())
|
if (channel_not_very_much_buffered_data())
|
||||||
#endif
|
#endif
|
||||||
FD_SET(connection_in, *readsetp);
|
/* Monitor client connection on reserved pollfd entries */
|
||||||
|
(*pfdp)[0].fd = connection_in;
|
||||||
/*
|
(*pfdp)[0].events = POLLIN;
|
||||||
* If we have buffered packet data going to the client, mark that
|
(*pfdp)[1].fd = connection_out;
|
||||||
* descriptor.
|
(*pfdp)[1].events = ssh_packet_have_data_to_write(ssh) ? POLLOUT : 0;
|
||||||
*/
|
|
||||||
if (ssh_packet_have_data_to_write(ssh))
|
|
||||||
FD_SET(connection_out, *writesetp);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If child has terminated and there is enough buffer space to read
|
* If child has terminated and there is enough buffer space to read
|
||||||
|
@ -234,27 +236,32 @@ wait_until_can_do_something(struct ssh *ssh,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wait for something to happen, or the timeout to expire. */
|
/* Wait for something to happen, or the timeout to expire. */
|
||||||
ret = pselect((*maxfdp)+1, *readsetp, *writesetp, NULL, tsp, sigsetp);
|
ret = ppoll(*pfdp, *npfd_activep, tsp, sigsetp);
|
||||||
|
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
memset(*readsetp, 0, *nallocp);
|
for (p = 0; p < *npfd_activep; p++)
|
||||||
memset(*writesetp, 0, *nallocp);
|
(*pfdp)[p].revents = 0;
|
||||||
if (errno != EINTR)
|
if (errno != EINTR)
|
||||||
error("pselect: %.100s", strerror(errno));
|
fatal_f("ppoll: %.100s", strerror(errno));
|
||||||
} else if (client_alive_scheduled) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*conn_in_readyp = (*pfdp)[0].revents != 0;
|
||||||
|
*conn_out_readyp = (*pfdp)[1].revents != 0;
|
||||||
|
|
||||||
|
if (client_alive_scheduled) {
|
||||||
time_t now = monotime();
|
time_t now = monotime();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the pselect timed out, or returned for some other reason
|
* If the ppoll timed out, or returned for some other reason
|
||||||
* but we haven't heard from the client in time, send keepalive.
|
* but we haven't heard from the client in time, send keepalive.
|
||||||
*/
|
*/
|
||||||
if (ret == 0 || (last_client_time != 0 && last_client_time +
|
if (ret == 0 || (last_client_time != 0 && last_client_time +
|
||||||
options.client_alive_interval <= now)) {
|
options.client_alive_interval <= now)) {
|
||||||
client_alive_check(ssh);
|
client_alive_check(ssh);
|
||||||
last_client_time = now;
|
last_client_time = now;
|
||||||
} else if (FD_ISSET(connection_in, *readsetp)) {
|
} else if (*conn_in_readyp)
|
||||||
last_client_time = now;
|
last_client_time = now;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,31 +270,28 @@ wait_until_can_do_something(struct ssh *ssh,
|
||||||
* in buffers and processed later.
|
* in buffers and processed later.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
process_input(struct ssh *ssh, fd_set *readset, int connection_in)
|
process_input(struct ssh *ssh, int connection_in)
|
||||||
{
|
{
|
||||||
int r, len;
|
int r, len;
|
||||||
char buf[16384];
|
char buf[16384];
|
||||||
|
|
||||||
/* Read and buffer any input data from the client. */
|
/* Read and buffer any input data from the client. */
|
||||||
if (FD_ISSET(connection_in, readset)) {
|
len = read(connection_in, buf, sizeof(buf));
|
||||||
len = read(connection_in, buf, sizeof(buf));
|
if (len == 0) {
|
||||||
if (len == 0) {
|
verbose("Connection closed by %.100s port %d",
|
||||||
verbose("Connection closed by %.100s port %d",
|
ssh_remote_ipaddr(ssh), ssh_remote_port(ssh));
|
||||||
ssh_remote_ipaddr(ssh), ssh_remote_port(ssh));
|
return -1;
|
||||||
return -1;
|
} else if (len == -1) {
|
||||||
} else if (len == -1) {
|
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
|
||||||
if (errno == EINTR || errno == EAGAIN ||
|
return 0;
|
||||||
errno == EWOULDBLOCK)
|
verbose("Read error from remote host %s port %d: %s",
|
||||||
return 0;
|
ssh_remote_ipaddr(ssh), ssh_remote_port(ssh),
|
||||||
verbose("Read error from remote host %s port %d: %s",
|
strerror(errno));
|
||||||
ssh_remote_ipaddr(ssh), ssh_remote_port(ssh),
|
cleanup_exit(255);
|
||||||
strerror(errno));
|
|
||||||
cleanup_exit(255);
|
|
||||||
}
|
|
||||||
/* Buffer any received data. */
|
|
||||||
if ((r = ssh_packet_process_incoming(ssh, buf, len)) != 0)
|
|
||||||
fatal_fr(r, "ssh_packet_process_incoming");
|
|
||||||
}
|
}
|
||||||
|
/* Buffer any received data. */
|
||||||
|
if ((r = ssh_packet_process_incoming(ssh, buf, len)) != 0)
|
||||||
|
fatal_fr(r, "ssh_packet_process_incoming");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,16 +299,14 @@ process_input(struct ssh *ssh, fd_set *readset, int connection_in)
|
||||||
* Sends data from internal buffers to client program stdin.
|
* Sends data from internal buffers to client program stdin.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
process_output(struct ssh *ssh, fd_set *writeset, int connection_out)
|
process_output(struct ssh *ssh, int connection_out)
|
||||||
{
|
{
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
/* Send any buffered packet data to the client. */
|
/* Send any buffered packet data to the client. */
|
||||||
if (FD_ISSET(connection_out, writeset)) {
|
if ((r = ssh_packet_write_poll(ssh)) != 0) {
|
||||||
if ((r = ssh_packet_write_poll(ssh)) != 0) {
|
sshpkt_fatal(ssh, r, "%s: ssh_packet_write_poll",
|
||||||
sshpkt_fatal(ssh, r, "%s: ssh_packet_write_poll",
|
__func__);
|
||||||
__func__);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,9 +335,10 @@ collect_children(struct ssh *ssh)
|
||||||
void
|
void
|
||||||
server_loop2(struct ssh *ssh, Authctxt *authctxt)
|
server_loop2(struct ssh *ssh, Authctxt *authctxt)
|
||||||
{
|
{
|
||||||
fd_set *readset = NULL, *writeset = NULL;
|
struct pollfd *pfd = NULL;
|
||||||
int r, max_fd;
|
u_int npfd_alloc = 0, npfd_active = 0;
|
||||||
u_int nalloc = 0, connection_in, connection_out;
|
int r, conn_in_ready, conn_out_ready;
|
||||||
|
u_int connection_in, connection_out;
|
||||||
u_int64_t rekey_timeout_ms = 0;
|
u_int64_t rekey_timeout_ms = 0;
|
||||||
sigset_t bsigset, osigset;
|
sigset_t bsigset, osigset;
|
||||||
|
|
||||||
|
@ -354,8 +357,6 @@ server_loop2(struct ssh *ssh, Authctxt *authctxt)
|
||||||
ssh_signal(SIGQUIT, sigterm_handler);
|
ssh_signal(SIGQUIT, sigterm_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
max_fd = MAXIMUM(connection_in, connection_out);
|
|
||||||
|
|
||||||
server_init_dispatch(ssh);
|
server_init_dispatch(ssh);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
@ -374,15 +375,15 @@ server_loop2(struct ssh *ssh, Authctxt *authctxt)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Block SIGCHLD while we check for dead children, then pass
|
* Block SIGCHLD while we check for dead children, then pass
|
||||||
* the old signal mask through to pselect() so that it'll wake
|
* the old signal mask through to ppoll() so that it'll wake
|
||||||
* up immediately if a child exits after we've called waitpid().
|
* up immediately if a child exits after we've called waitpid().
|
||||||
*/
|
*/
|
||||||
if (sigprocmask(SIG_BLOCK, &bsigset, &osigset) == -1)
|
if (sigprocmask(SIG_BLOCK, &bsigset, &osigset) == -1)
|
||||||
error_f("bsigset sigprocmask: %s", strerror(errno));
|
error_f("bsigset sigprocmask: %s", strerror(errno));
|
||||||
collect_children(ssh);
|
collect_children(ssh);
|
||||||
wait_until_can_do_something(ssh, connection_in, connection_out,
|
wait_until_can_do_something(ssh, connection_in, connection_out,
|
||||||
&readset, &writeset, &max_fd, &nalloc, rekey_timeout_ms,
|
&pfd, &npfd_alloc, &npfd_active, rekey_timeout_ms, &osigset,
|
||||||
&osigset);
|
&conn_in_ready, &conn_out_ready);
|
||||||
if (sigprocmask(SIG_UNBLOCK, &bsigset, &osigset) == -1)
|
if (sigprocmask(SIG_UNBLOCK, &bsigset, &osigset) == -1)
|
||||||
error_f("osigset sigprocmask: %s", strerror(errno));
|
error_f("osigset sigprocmask: %s", strerror(errno));
|
||||||
|
|
||||||
|
@ -393,18 +394,18 @@ server_loop2(struct ssh *ssh, Authctxt *authctxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ssh_packet_is_rekeying(ssh))
|
if (!ssh_packet_is_rekeying(ssh))
|
||||||
channel_after_select(ssh, readset, writeset);
|
channel_after_poll(ssh, pfd, npfd_active);
|
||||||
if (process_input(ssh, readset, connection_in) < 0)
|
if (conn_in_ready &&
|
||||||
|
process_input(ssh, connection_in) < 0)
|
||||||
break;
|
break;
|
||||||
/* A timeout may have triggered rekeying */
|
/* A timeout may have triggered rekeying */
|
||||||
if ((r = ssh_packet_check_rekey(ssh)) != 0)
|
if ((r = ssh_packet_check_rekey(ssh)) != 0)
|
||||||
fatal_fr(r, "cannot start rekeying");
|
fatal_fr(r, "cannot start rekeying");
|
||||||
process_output(ssh, writeset, connection_out);
|
if (conn_out_ready)
|
||||||
|
process_output(ssh, connection_out);
|
||||||
}
|
}
|
||||||
collect_children(ssh);
|
collect_children(ssh);
|
||||||
|
free(pfd);
|
||||||
free(readset);
|
|
||||||
free(writeset);
|
|
||||||
|
|
||||||
/* free all channels, no more reads and writes */
|
/* free all channels, no more reads and writes */
|
||||||
channel_free_all(ssh);
|
channel_free_all(ssh);
|
||||||
|
|
Loading…
Reference in New Issue