diff options
Diffstat (limited to 'channels.c')
-rw-r--r-- | channels.c | 554 |
1 files changed, 390 insertions, 164 deletions
diff --git a/channels.c b/channels.c index 1c714c845e18..73e93c032765 100644 --- a/channels.c +++ b/channels.c @@ -1,4 +1,4 @@ -/* $OpenBSD: channels.c,v 1.408 2021/09/14 11:04:21 mbuhl Exp $ */ +/* $OpenBSD: channels.c,v 1.413 2022/02/17 10:58:27 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -57,6 +57,9 @@ #include <fcntl.h> #include <limits.h> #include <netdb.h> +#ifdef HAVE_POLL_H +#include <poll.h> +#endif #include <stdarg.h> #ifdef HAVE_STDINT_H # include <stdint.h> @@ -84,6 +87,9 @@ #include "pathnames.h" #include "match.h" +/* XXX remove once we're satisfied there's no lurking bugs */ +/* #define DEBUG_CHANNEL_POLL 1 */ + /* -- agent forwarding */ #define NUM_SOCKS 10 @@ -98,9 +104,8 @@ /* Maximum number of fake X11 displays to try. */ #define MAX_DISPLAYS 1000 -/* Per-channel callback for pre/post select() actions */ -typedef void chan_fn(struct ssh *, Channel *c, - fd_set *readset, fd_set *writeset); +/* Per-channel callback for pre/post IO actions */ +typedef void chan_fn(struct ssh *, Channel *c); /* * Data structure for storing which hosts are permitted for forward requests. @@ -161,17 +166,11 @@ struct ssh_channels { u_int channels_alloc; /* - * Maximum file descriptor value used in any of the channels. This is - * updated in channel_new. - */ - int channel_max_fd; - - /* - * 'channel_pre*' are called just before select() to add any bits - * relevant to channels in the select bitmasks. + * 'channel_pre*' are called just before IO to add any bits + * relevant to channels in the c->io_want bitmasks. * * '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_post; @@ -305,13 +304,6 @@ static void channel_register_fds(struct ssh *ssh, Channel *c, int rfd, int wfd, int efd, 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) fcntl(rfd, F_SETFD, FD_CLOEXEC); if (wfd != -1 && wfd != rfd) @@ -423,28 +415,9 @@ channel_new(struct ssh *ssh, char *ctype, int type, int rfd, int wfd, int efd, 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 channel_close_fd(struct ssh *ssh, Channel *c, int *fdp) { - struct ssh_channels *sc = ssh->chanctxt; int ret, fd = *fdp; if (fd == -1) @@ -455,10 +428,29 @@ channel_close_fd(struct ssh *ssh, Channel *c, int *fdp) (*fdp == c->efd && (c->restore_block & CHANNEL_RESTORE_EFD) != 0)) (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); - *fdp = -1; - if (fd == sc->channel_max_fd) - channel_find_maxfd(sc); + *fdp = -1; /* probably redundant */ return ret; } @@ -543,7 +535,7 @@ permission_set_get_array(struct ssh *ssh, int who, int where, } } -/* Adds an entry to the spcified forwarding list */ +/* Adds an entry to the specified forwarding list */ static int permission_set_add(struct ssh *ssh, int who, int where, const char *host_to_connect, int port_to_connect, @@ -680,7 +672,6 @@ channel_free_all(struct ssh *ssh) free(sc->channels); sc->channels = NULL; sc->channels_alloc = 0; - sc->channel_max_fd = 0; free(sc->x11_saved_display); sc->x11_saved_display = NULL; @@ -872,13 +863,14 @@ channel_format_status(const Channel *c) char *ret = NULL; 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->have_remote_id ? "r" : "nr", c->remote_id, c->istate, sshbuf_len(c->input), c->ostate, sshbuf_len(c->output), 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; } @@ -1096,33 +1088,31 @@ channel_set_fds(struct ssh *ssh, int id, int rfd, int wfd, int efd, } static void -channel_pre_listener(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_pre_listener(struct ssh *ssh, Channel *c) { - FD_SET(c->sock, readset); + c->io_want = SSH_CHAN_IO_SOCK_R; } static void -channel_pre_connecting(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_pre_connecting(struct ssh *ssh, Channel *c) { debug3("channel %d: waiting for connection", c->self); - FD_SET(c->sock, writeset); + c->io_want = SSH_CHAN_IO_SOCK_W; } static void -channel_pre_open(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_pre_open(struct ssh *ssh, Channel *c) { + c->io_want = 0; if (c->istate == CHAN_INPUT_OPEN && c->remote_window > 0 && sshbuf_len(c->input) < c->remote_window && sshbuf_check_reserve(c->input, CHAN_RBUF) == 0) - FD_SET(c->rfd, readset); + c->io_want |= SSH_CHAN_IO_RFD; if (c->ostate == CHAN_OUTPUT_OPEN || c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { if (sshbuf_len(c->output) > 0) { - FD_SET(c->wfd, writeset); + c->io_want |= SSH_CHAN_IO_WFD; } else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { if (CHANNEL_EFD_OUTPUT_ACTIVE(c)) debug2("channel %d: " @@ -1137,12 +1127,12 @@ channel_pre_open(struct ssh *ssh, Channel *c, c->ostate == CHAN_OUTPUT_CLOSED)) { if (c->extended_usage == CHAN_EXTENDED_WRITE && sshbuf_len(c->extended) > 0) - FD_SET(c->efd, writeset); + c->io_want |= SSH_CHAN_IO_EFD_W; else if (c->efd != -1 && !(c->flags & CHAN_EOF_SENT) && (c->extended_usage == CHAN_EXTENDED_READ || c->extended_usage == CHAN_EXTENDED_IGNORE) && sshbuf_len(c->extended) < c->remote_window) - FD_SET(c->efd, readset); + c->io_want |= SSH_CHAN_IO_EFD_R; } /* XXX: What about efd? races? */ } @@ -1224,8 +1214,7 @@ x11_open_helper(struct ssh *ssh, struct sshbuf *b) } static void -channel_pre_x11_open(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_pre_x11_open(struct ssh *ssh, Channel *c) { int ret = x11_open_helper(ssh, c->output); @@ -1233,7 +1222,7 @@ channel_pre_x11_open(struct ssh *ssh, Channel *c, if (ret == 1) { c->type = SSH_CHANNEL_OPEN; - channel_pre_open(ssh, c, readset, writeset); + channel_pre_open(ssh, c); } else if (ret == -1) { logit("X11 connection rejected because of wrong authentication."); debug2("X11 rejected %d i%d/o%d", @@ -1248,12 +1237,12 @@ channel_pre_x11_open(struct ssh *ssh, Channel *c, } static void -channel_pre_mux_client(struct ssh *ssh, - Channel *c, fd_set *readset, fd_set *writeset) +channel_pre_mux_client(struct ssh *ssh, Channel *c) { + c->io_want = 0; if (c->istate == CHAN_INPUT_OPEN && !c->mux_pause && sshbuf_check_reserve(c->input, CHAN_RBUF) == 0) - FD_SET(c->rfd, readset); + c->io_want |= SSH_CHAN_IO_RFD; if (c->istate == CHAN_INPUT_WAIT_DRAIN) { /* clear buffer immediately (discard any partial packet) */ sshbuf_reset(c->input); @@ -1264,7 +1253,7 @@ channel_pre_mux_client(struct ssh *ssh, if (c->ostate == CHAN_OUTPUT_OPEN || c->ostate == CHAN_OUTPUT_WAIT_DRAIN) { if (sshbuf_len(c->output) > 0) - FD_SET(c->wfd, writeset); + c->io_want |= SSH_CHAN_IO_WFD; else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) chan_obuf_empty(ssh, c); } @@ -1543,20 +1532,20 @@ channel_connect_stdio_fwd(struct ssh *ssh, /* dynamic port forwarding */ static void -channel_pre_dynamic(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_pre_dynamic(struct ssh *ssh, Channel *c) { const u_char *p; u_int have; int ret; + c->io_want = 0; have = sshbuf_len(c->input); debug2("channel %d: pre_dynamic: have %d", c->self, have); /* sshbuf_dump(c->input, stderr); */ /* check if the fixed size part of the packet is in buffer. */ if (have < 3) { /* need more */ - FD_SET(c->sock, readset); + c->io_want |= SSH_CHAN_IO_RFD; return; } /* try to guess the protocol */ @@ -1578,9 +1567,9 @@ channel_pre_dynamic(struct ssh *ssh, Channel *c, } else if (ret == 0) { debug2("channel %d: pre_dynamic: need more", c->self); /* need more */ - FD_SET(c->sock, readset); + c->io_want |= SSH_CHAN_IO_RFD; if (sshbuf_len(c->output)) - FD_SET(c->sock, writeset); + c->io_want |= SSH_CHAN_IO_WFD; } else { /* switch to the next state */ c->type = SSH_CHANNEL_OPENING; @@ -1602,7 +1591,7 @@ rdynamic_close(struct ssh *ssh, Channel *c) /* reverse dynamic port forwarding */ 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; u_int have, len; @@ -1660,8 +1649,7 @@ channel_before_prepare_select_rdynamic(struct ssh *ssh, Channel *c) /* This is our fake X11 server socket. */ static void -channel_post_x11_listener(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_post_x11_listener(struct ssh *ssh, Channel *c) { Channel *nc; struct sockaddr_storage addr; @@ -1669,7 +1657,7 @@ channel_post_x11_listener(struct ssh *ssh, Channel *c, socklen_t addrlen; char buf[16384], *remote_ipaddr; - if (!FD_ISSET(c->sock, readset)) + if ((c->io_ready & SSH_CHAN_IO_SOCK_R) == 0) return; debug("X11 connection requested."); @@ -1778,8 +1766,7 @@ channel_set_x11_refuse_time(struct ssh *ssh, u_int refuse_time) * This socket is listening for connections to a forwarded TCP/IP port. */ static void -channel_post_port_listener(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_post_port_listener(struct ssh *ssh, Channel *c) { Channel *nc; struct sockaddr_storage addr; @@ -1787,7 +1774,7 @@ channel_post_port_listener(struct ssh *ssh, Channel *c, socklen_t addrlen; char *rtype; - if (!FD_ISSET(c->sock, readset)) + if ((c->io_ready & SSH_CHAN_IO_SOCK_R) == 0) return; debug("Connection to port %d forwarding to %.100s port %d requested.", @@ -1838,15 +1825,14 @@ channel_post_port_listener(struct ssh *ssh, Channel *c, * clients. */ static void -channel_post_auth_listener(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_post_auth_listener(struct ssh *ssh, Channel *c) { Channel *nc; int r, newsock; struct sockaddr_storage addr; socklen_t addrlen; - if (!FD_ISSET(c->sock, readset)) + if ((c->io_ready & SSH_CHAN_IO_SOCK_R) == 0) return; addrlen = sizeof(addr); @@ -1867,13 +1853,12 @@ channel_post_auth_listener(struct ssh *ssh, Channel *c, } static void -channel_post_connecting(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_post_connecting(struct ssh *ssh, Channel *c) { int err = 0, sock, isopen, r; socklen_t sz = sizeof(err); - if (!FD_ISSET(c->sock, writeset)) + if ((c->io_ready & SSH_CHAN_IO_SOCK_W) == 0) return; if (!c->have_remote_id) fatal_f("channel %d: no remote id", c->self); @@ -1907,7 +1892,6 @@ channel_post_connecting(struct ssh *ssh, Channel *c, if ((sock = connect_next(&c->connect_ctx)) > 0) { close(c->sock); c->sock = c->rfd = c->wfd = sock; - channel_find_maxfd(ssh->chanctxt); return; } /* Exhausted all addresses */ @@ -1932,31 +1916,64 @@ channel_post_connecting(struct ssh *ssh, Channel *c, } static int -channel_handle_rfd(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_handle_rfd(struct ssh *ssh, Channel *c) { char buf[CHAN_RBUF]; ssize_t len; int r, force; + size_t have, avail, maxlen = CHANNEL_MAX_READ; + int pty_zeroread = 0; + +#ifdef PTY_ZEROREAD + /* Bug on AIX: read(1) can return 0 for a non-closed fd */ + pty_zeroread = c->isatty; +#endif force = c->isatty && c->detach_close && c->istate != CHAN_INPUT_CLOSED; - if (c->rfd == -1 || (!force && !FD_ISSET(c->rfd, readset))) + if (!force && (c->io_ready & SSH_CHAN_IO_RFD) == 0) + return 1; + if ((avail = sshbuf_avail(c->input)) == 0) + return 1; /* Shouldn't happen */ + + /* + * For "simple" channels (i.e. not datagram or filtered), we can + * read directly to the channel buffer. + */ + if (!pty_zeroread && c->input_filter == NULL && !c->datagram) { + /* Only OPEN channels have valid rwin */ + if (c->type == SSH_CHANNEL_OPEN) { + if ((have = sshbuf_len(c->input)) >= c->remote_window) + return 1; /* shouldn't happen */ + if (maxlen > c->remote_window - have) + maxlen = c->remote_window - have; + } + if (maxlen > avail) + maxlen = avail; + if ((r = sshbuf_read(c->rfd, c->input, maxlen, NULL)) != 0) { + if (errno == EINTR || (!force && + (errno == EAGAIN || errno == EWOULDBLOCK))) + return 1; + debug2("channel %d: read failed rfd %d maxlen %zu: %s", + c->self, c->rfd, maxlen, ssh_err(r)); + goto rfail; + } return 1; + } errno = 0; len = read(c->rfd, buf, sizeof(buf)); + /* fixup AIX zero-length read with errno set to look more like errors */ + if (pty_zeroread && len == 0 && errno != 0) + len = -1; if (len == -1 && (errno == EINTR || ((errno == EAGAIN || errno == EWOULDBLOCK) && !force))) return 1; -#ifndef PTY_ZEROREAD - if (len <= 0) { -#else - if ((!c->isatty && len <= 0) || - (c->isatty && (len < 0 || (len == 0 && errno != 0)))) { -#endif - debug2("channel %d: read<=0 rfd %d len %zd", - c->self, c->rfd, len); + if (len < 0 || (!pty_zeroread && len == 0)) { + debug2("channel %d: read<=0 rfd %d len %zd: %s", + c->self, c->rfd, len, + len == 0 ? "closed" : strerror(errno)); + rfail: if (c->type != SSH_CHANNEL_OPEN) { debug2("channel %d: not open", c->self); chan_mark_dead(ssh, c); @@ -1976,20 +1993,21 @@ channel_handle_rfd(struct ssh *ssh, Channel *c, fatal_fr(r, "channel %i: put datagram", c->self); } else if ((r = sshbuf_put(c->input, buf, len)) != 0) fatal_fr(r, "channel %i: put data", c->self); + return 1; } static int -channel_handle_wfd(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_handle_wfd(struct ssh *ssh, Channel *c) { struct termios tio; u_char *data = NULL, *buf; /* XXX const; need filter API change */ size_t dlen, olen = 0; int r, len; - if (c->wfd == -1 || !FD_ISSET(c->wfd, writeset) || - sshbuf_len(c->output) == 0) + if ((c->io_ready & SSH_CHAN_IO_WFD) == 0) + return 1; + if (sshbuf_len(c->output) == 0) return 1; /* Send buffered output data to the socket. */ @@ -2027,7 +2045,7 @@ channel_handle_wfd(struct ssh *ssh, Channel *c, #ifdef _AIX /* XXX: Later AIX versions can't push as much data to tty */ if (c->wfd_isatty) - dlen = MIN(dlen, 8*1024); + dlen = MINIMUM(dlen, 8*1024); #endif len = write(c->wfd, buf, dlen); @@ -2070,13 +2088,14 @@ channel_handle_wfd(struct ssh *ssh, Channel *c, } static int -channel_handle_efd_write(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_handle_efd_write(struct ssh *ssh, Channel *c) { int r; ssize_t len; - if (!FD_ISSET(c->efd, writeset) || sshbuf_len(c->extended) == 0) + if ((c->io_ready & SSH_CHAN_IO_EFD_W) == 0) + return 1; + if (sshbuf_len(c->extended) == 0) return 1; len = write(c->efd, sshbuf_ptr(c->extended), @@ -2097,8 +2116,7 @@ channel_handle_efd_write(struct ssh *ssh, Channel *c, } static int -channel_handle_efd_read(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_handle_efd_read(struct ssh *ssh, Channel *c) { char buf[CHAN_RBUF]; ssize_t len; @@ -2106,7 +2124,7 @@ channel_handle_efd_read(struct ssh *ssh, Channel *c, force = c->isatty && c->detach_close && c->istate != CHAN_INPUT_CLOSED; - if (c->efd == -1 || (!force && !FD_ISSET(c->efd, readset))) + if (!force && (c->io_ready & SSH_CHAN_IO_EFD_R) == 0) return 1; len = read(c->efd, buf, sizeof(buf)); @@ -2125,8 +2143,7 @@ channel_handle_efd_read(struct ssh *ssh, Channel *c, } static int -channel_handle_efd(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_handle_efd(struct ssh *ssh, Channel *c) { if (c->efd == -1) return 1; @@ -2134,10 +2151,10 @@ channel_handle_efd(struct ssh *ssh, Channel *c, /** XXX handle drain efd, too */ if (c->extended_usage == CHAN_EXTENDED_WRITE) - return channel_handle_efd_write(ssh, c, readset, writeset); + return channel_handle_efd_write(ssh, c); else if (c->extended_usage == CHAN_EXTENDED_READ || c->extended_usage == CHAN_EXTENDED_IGNORE) - return channel_handle_efd_read(ssh, c, readset, writeset); + return channel_handle_efd_read(ssh, c); return 1; } @@ -2171,12 +2188,11 @@ channel_check_window(struct ssh *ssh, Channel *c) } static void -channel_post_open(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_post_open(struct ssh *ssh, Channel *c) { - channel_handle_rfd(ssh, c, readset, writeset); - channel_handle_wfd(ssh, c, readset, writeset); - channel_handle_efd(ssh, c, readset, writeset); + channel_handle_rfd(ssh, c); + channel_handle_wfd(ssh, c); + channel_handle_efd(ssh, c); channel_check_window(ssh, c); } @@ -2205,12 +2221,11 @@ read_mux(struct ssh *ssh, Channel *c, u_int need) } static void -channel_post_mux_client_read(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_post_mux_client_read(struct ssh *ssh, Channel *c) { u_int need; - if (c->rfd == -1 || !FD_ISSET(c->rfd, readset)) + if ((c->io_ready & SSH_CHAN_IO_RFD) == 0) return; if (c->istate != CHAN_INPUT_OPEN && c->istate != CHAN_INPUT_WAIT_DRAIN) return; @@ -2242,14 +2257,14 @@ channel_post_mux_client_read(struct ssh *ssh, Channel *c, } static void -channel_post_mux_client_write(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_post_mux_client_write(struct ssh *ssh, Channel *c) { ssize_t len; int r; - if (c->wfd == -1 || !FD_ISSET(c->wfd, writeset) || - sshbuf_len(c->output) == 0) + if ((c->io_ready & SSH_CHAN_IO_WFD) == 0) + return; + if (sshbuf_len(c->output) == 0) return; len = write(c->wfd, sshbuf_ptr(c->output), sshbuf_len(c->output)); @@ -2264,16 +2279,14 @@ channel_post_mux_client_write(struct ssh *ssh, Channel *c, } static void -channel_post_mux_client(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_post_mux_client(struct ssh *ssh, Channel *c) { - channel_post_mux_client_read(ssh, c, readset, writeset); - channel_post_mux_client_write(ssh, c, readset, writeset); + channel_post_mux_client_read(ssh, c); + channel_post_mux_client_write(ssh, c); } static void -channel_post_mux_listener(struct ssh *ssh, Channel *c, - fd_set *readset, fd_set *writeset) +channel_post_mux_listener(struct ssh *ssh, Channel *c) { Channel *nc; struct sockaddr_storage addr; @@ -2282,7 +2295,7 @@ channel_post_mux_listener(struct ssh *ssh, Channel *c, uid_t euid; gid_t egid; - if (!FD_ISSET(c->sock, readset)) + if ((c->io_ready & SSH_CHAN_IO_SOCK_R) == 0) return; debug("multiplexing control connection"); @@ -2388,8 +2401,7 @@ channel_garbage_collect(struct ssh *ssh, Channel *c) enum channel_table { CHAN_PRE, CHAN_POST }; static void -channel_handler(struct ssh *ssh, int table, - fd_set *readset, fd_set *writeset, time_t *unpause_secs) +channel_handler(struct ssh *ssh, int table, time_t *unpause_secs) { struct ssh_channels *sc = ssh->chanctxt; chan_fn **ftab = table == CHAN_PRE ? sc->channel_pre : sc->channel_post; @@ -2415,7 +2427,7 @@ channel_handler(struct ssh *ssh, int table, * Run handlers that are not paused. */ if (c->notbefore <= now) - (*ftab[c->type])(ssh, c, readset, writeset); + (*ftab[c->type])(ssh, c); else if (unpause_secs != NULL) { /* * Collect the time that the earliest @@ -2437,12 +2449,13 @@ channel_handler(struct ssh *ssh, int table, } /* - * Create sockets before allocating the select bitmasks. + * Create sockets before preparing IO. * 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 -channel_before_prepare_select(struct ssh *ssh) +channel_before_prepare_io(struct ssh *ssh) { struct ssh_channels *sc = ssh->chanctxt; Channel *c; @@ -2453,53 +2466,266 @@ channel_before_prepare_select(struct ssh *ssh) if (c == NULL) continue; if (c->type == SSH_CHANNEL_RDYNAMIC_OPEN) - channel_before_prepare_select_rdynamic(ssh, c); + channel_before_prepare_io_rdynamic(ssh, c); } } -/* - * Allocate/update select bitmasks and add any bits relevant to channels in - * select bitmasks. - */ +static void +dump_channel_poll(const char *func, const char *what, Channel *c, + 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 -channel_prepare_select(struct ssh *ssh, fd_set **readsetp, fd_set **writesetp, - int *maxfdp, u_int *nallocp, time_t *minwait_secs) +channel_prepare_poll(struct ssh *ssh, struct pollfd **pfdp, u_int *npfd_allocp, + u_int *npfd_activep, u_int npfd_reserved, time_t *minwait_secs) { - u_int n, sz, nfdset; + struct ssh_channels *sc = ssh->chanctxt; + u_int i, oalloc, p, npfd = npfd_reserved; + + channel_before_prepare_io(ssh); /* might create a new channel */ - channel_before_prepare_select(ssh); /* might update channel_max_fd */ + /* 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)) + npfd += sc->channels_alloc * 4; + if (npfd > *npfd_allocp) { + *pfdp = xrecallocarray(*pfdp, *npfd_allocp, + npfd, sizeof(**pfdp)); + *npfd_allocp = npfd; + } + *npfd_activep = npfd_reserved; + if (ssh_packet_is_rekeying(ssh)) + return; - n = MAXIMUM(*maxfdp, ssh->chanctxt->channel_max_fd); + oalloc = sc->channels_alloc; - 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); + channel_handler(ssh, CHAN_PRE, minwait_secs); - /* 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; + if (oalloc != sc->channels_alloc) { + /* shouldn't happen */ + fatal_f("channels_alloc changed during CHAN_PRE " + "(was %u, now %u)", oalloc, sc->channels_alloc); } - *maxfdp = n; - memset(*readsetp, 0, sz); - memset(*writesetp, 0, sz); - if (!ssh_packet_is_rekeying(ssh)) - channel_handler(ssh, CHAN_PRE, *readsetp, *writesetp, - minwait_secs); + /* 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. */ 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) { - channel_handler(ssh, CHAN_POST, readset, writeset, NULL); + struct ssh_channels *sc = ssh->chanctxt; + u_int i, p; + Channel *c; + +#ifdef DEBUG_CHANNEL_POLL + for (p = 0; p < npfd; p++) { + if (pfd[p].revents == 0) + 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; + p = c->pollfd_offset; + /* rfd, potentially shared with wfd, efd and sock */ + if (c->rfd != -1) { + fd_ready(c, p, pfd, c->rfd, "rfd", POLLIN, + SSH_CHAN_IO_RFD); + if (c->rfd == c->wfd) { + fd_ready(c, p, pfd, c->wfd, "wfd/r", POLLOUT, + SSH_CHAN_IO_WFD); + } + if (c->rfd == c->efd) { + fd_ready(c, p, pfd, c->efd, "efdr/r", POLLIN, + 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); } /* |