-/* $OpenBSD: channels.c,v 1.447 2025/08/18 03:28:02 djm Exp $ */
+/* $OpenBSD: channels.c,v 1.448 2025/08/18 03:43:01 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
/* Global timeout for all OPEN channels */
int global_deadline;
time_t lastused;
+ /* pattern-lists used to classify channels as bulk */
+ char *bulk_classifier_tty, *bulk_classifier_notty;
+ /* Number of active bulk channels (set by channel_handler) */
+ u_int nbulk;
};
/* helper */
sc->channels_alloc = 10;
sc->channels = xcalloc(sc->channels_alloc, sizeof(*sc->channels));
sc->IPv4or6 = AF_UNSPEC;
+ sc->bulk_classifier_tty = xstrdup(CHANNEL_BULK_TTY);
+ sc->bulk_classifier_notty = xstrdup(CHANNEL_BULK_NOTTY);
channel_handler_init(sc);
ssh->chanctxt = sc;
return 0;
}
+static void
+channel_classify(struct ssh *ssh, Channel *c)
+{
+ struct ssh_channels *sc = ssh->chanctxt;
+ const char *type = c->xctype == NULL ? c->ctype : c->xctype;
+ const char *classifier = c->isatty ?
+ sc->bulk_classifier_tty : sc->bulk_classifier_notty;
+
+ c->bulk = type != NULL && match_pattern_list(type, classifier, 0) == 1;
+}
+
/*
* Sets "extended type" of a channel; used by session layer to add additional
* information about channel types (e.g. shell, login, subsystem) that can then
c->xctype = xstrdup(xctype);
/* Type has changed, so look up inactivity deadline again */
c->inactive_deadline = lookup_timeout(ssh, c->xctype);
+ channel_classify(ssh, c);
debug2_f("labeled channel %d as %s (inactive timeout %u)", id, xctype,
c->inactive_deadline);
}
return expiry;
}
+/* Returns non-zero if there is an open, non-interactive channel */
+int
+channel_has_bulk(struct ssh *ssh)
+{
+ return ssh->chanctxt != NULL && ssh->chanctxt->nbulk != 0;
+}
+
/*
* Register filedescriptors for a channel, used when allocating a channel or
* when the channel consumer/producer is ready, e.g. shell exec'd
}
/* channel might be entering a larval state, so reset global timeout */
channel_set_used_time(ssh, NULL);
+ channel_classify(ssh, c);
}
/*
c->delayed = 1; /* prevent call to channel_post handler */
c->inactive_deadline = lookup_timeout(ssh, c->ctype);
TAILQ_INIT(&c->status_confirms);
+ channel_classify(ssh, c);
debug("channel %d: new %s [%s] (inactive timeout: %u)",
found, c->ctype, remote_name, c->inactive_deadline);
return c;
}
+void
+channel_set_tty(struct ssh *ssh, Channel *c)
+{
+ c->isatty = 1;
+ channel_classify(ssh, c);
+}
+
int
channel_close_fd(struct ssh *ssh, Channel *c, int *fdp)
{
char *ret = NULL;
xasprintf(&ret, "t%d [%s] %s%u %s%u i%u/%zu o%u/%zu e[%s]/%zu "
- "fd %d/%d/%d sock %d cc %d %s%u io 0x%02x/0x%02x",
+ "fd %d/%d/%d sock %d cc %d %s%u io 0x%02x/0x%02x %s%s",
c->type, c->xctype != NULL ? c->xctype : c->ctype,
c->have_remote_id ? "r" : "nr", c->remote_id,
c->mux_ctx != NULL ? "m" : "nm", c->mux_downstream_id,
channel_format_extended_usage(c), sshbuf_len(c->extended),
c->rfd, c->wfd, c->efd, c->sock, c->ctl_chan,
c->have_ctl_child_id ? "c" : "nc", c->ctl_child_id,
- c->io_want, c->io_ready);
+ c->io_want, c->io_ready,
+ c->isatty ? "T" : "", c->bulk ? "B" : "I");
return ret;
}
time_t now;
now = monotime();
- for (i = 0, oalloc = sc->channels_alloc; i < oalloc; i++) {
+ for (sc->nbulk = i = 0, oalloc = sc->channels_alloc; i < oalloc; i++) {
c = sc->channels[i];
if (c == NULL)
continue;
+ /* Count open channels in bulk state */
+ if (c->type == SSH_CHANNEL_OPEN && c->bulk)
+ sc->nbulk++;
/* Try to keep IO going while rekeying */
if (ssh_packet_is_rekeying(ssh) && c->type != SSH_CHANNEL_OPEN)
continue;
-/* $OpenBSD: channels.h,v 1.159 2025/08/18 03:28:02 djm Exp $ */
+/* $OpenBSD: channels.h,v 1.160 2025/08/18 03:43:01 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
#define FORWARD_ADM 0x100
#define FORWARD_USER 0x101
+/* default pattern-lists used to classify channel types as bulk */
+#define CHANNEL_BULK_TTY ""
+#define CHANNEL_BULK_NOTTY "direct-*,forwarded-*,tun-*,x11-*,session*"
+
struct ssh;
struct Channel;
typedef struct Channel Channel;
char *ctype; /* const type - NB. not freed on channel_free */
char *xctype; /* extended type */
+ int bulk; /* channel is non-interactive */
/* callback */
channel_open_fn *open_confirm;
u_int, u_int, int, const char *, int);
void channel_set_fds(struct ssh *, int, int, int, int, int,
int, int, u_int);
+void channel_set_tty(struct ssh *, Channel *);
void channel_free(struct ssh *, Channel *);
void channel_free_all(struct ssh *);
void channel_stop_listening(struct ssh *);
void channel_cancel_cleanup(struct ssh *, int);
int channel_close_fd(struct ssh *, Channel *, int *);
void channel_send_window_changes(struct ssh *);
+int channel_has_bulk(struct ssh *);
/* channel inactivity timeouts */
void channel_add_timeout(struct ssh *, const char *, int);
-/* $OpenBSD: clientloop.c,v 1.413 2025/08/18 03:28:36 djm Exp $ */
+/* $OpenBSD: clientloop.c,v 1.414 2025/08/18 03:43:01 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
struct pollfd *pfd = NULL;
u_int npfd_alloc = 0, npfd_active = 0;
double start_time, total_time;
- int channel_did_enqueue = 0, r;
+ int interactive = -1, channel_did_enqueue = 0, r;
u_int64_t ibytes, obytes;
int conn_in_ready, conn_out_ready;
sigset_t bsigset, osigset;
* sender.
*/
if (conn_out_ready) {
+ if (interactive != !channel_has_bulk(ssh)) {
+ interactive = !channel_has_bulk(ssh);
+ debug2_f("session QoS is now %s", interactive ?
+ "interactive" : "non-interactive");
+ ssh_packet_set_interactive(ssh, interactive);
+ }
if ((r = ssh_packet_write_poll(ssh)) != 0) {
sshpkt_fatal(ssh, r,
"%s: ssh_packet_write_poll", __func__);
if ((c = channel_lookup(ssh, id)) == NULL)
fatal_f("channel %d: unknown channel", id);
- ssh_packet_set_interactive(ssh, want_tty,
- options.ip_qos_interactive, options.ip_qos_bulk);
-
if (want_tty) {
struct winsize ws;
-/* $OpenBSD: misc.c,v 1.202 2025/08/11 14:37:43 deraadt Exp $ */
+/* $OpenBSD: misc.c,v 1.203 2025/08/18 03:43:01 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2005-2020 Damien Miller. All rights reserved.
#ifndef IP_TOS_IS_BROKEN
int af;
+ if (tos < 0 || tos == INT_MAX) {
+ debug_f("invalid TOS %d", tos);
+ return;
+ }
switch ((af = get_sock_af(fd))) {
case -1:
/* assume not a socket */
-/* $OpenBSD: mux.c,v 1.104 2025/07/04 00:17:55 djm Exp $ */
+/* $OpenBSD: mux.c,v 1.105 2025/08/18 03:43:01 djm Exp $ */
/*
* Copyright (c) 2002-2008 Damien Miller <djm@openbsd.org>
*
nc = channel_new(ssh, "session", SSH_CHANNEL_OPENING,
new_fd[0], new_fd[1], new_fd[2], window, packetmax,
CHAN_EXTENDED_WRITE, "client-session", CHANNEL_NONBLOCK_STDIO);
+ if (cctx->want_tty)
+ channel_set_tty(ssh, nc);
nc->ctl_chan = c->self; /* link session -> control channel */
c->ctl_child_id = nc->self; /* link control -> session channel */
-/* $OpenBSD: packet.c,v 1.319 2025/08/06 23:44:09 djm Exp $ */
+/* $OpenBSD: packet.c,v 1.320 2025/08/18 03:43:01 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
/* Used in ssh_packet_send_mux() */
int mux;
- /* Used in packet_set_interactive */
- int set_interactive_called;
+ /* QoS handling */
+ int qos_interactive, qos_other;
/* Used in packet_set_maxsize */
int set_maxsize_called;
*/
int disconnecting;
+ /* Nagle disabled on socket */
+ int nodelay_set;
+
/* Hook for fuzzing inbound packets */
ssh_packet_hook_fn *hook_in;
void *hook_in_ctx;
state->connection_out = -1;
state->max_packet_size = 32768;
state->packet_timeout_ms = -1;
+ state->interactive_mode = 1;
+ state->qos_interactive = state->qos_other = -1;
state->p_send.packets = state->p_read.packets = 0;
state->initialized = 1;
/*
sshbuf_len(ssh->state->output) < 256;
}
-void
-ssh_packet_set_tos(struct ssh *ssh, int tos)
+static void
+apply_qos(struct ssh *ssh)
{
- if (!ssh_packet_connection_is_on_socket(ssh) || tos == INT_MAX)
+ struct session_state *state = ssh->state;
+ int qos = state->interactive_mode ?
+ state->qos_interactive : state->qos_other;
+
+ if (!ssh_packet_connection_is_on_socket(ssh))
return;
- set_sock_tos(ssh->state->connection_in, tos);
+ if (!state->nodelay_set) {
+ set_nodelay(state->connection_in);
+ state->nodelay_set = 1;
+ }
+ set_sock_tos(ssh->state->connection_in, qos);
}
-/* Informs that the current session is interactive. Sets IP flags for that. */
-
+/* Informs that the current session is interactive. */
void
-ssh_packet_set_interactive(struct ssh *ssh, int interactive, int qos_interactive, int qos_bulk)
+ssh_packet_set_interactive(struct ssh *ssh, int interactive)
{
struct session_state *state = ssh->state;
- if (state->set_interactive_called)
- return;
- state->set_interactive_called = 1;
-
- /* Record that we are in interactive mode. */
state->interactive_mode = interactive;
+ apply_qos(ssh);
+}
- /* Only set socket options if using a socket. */
- if (!ssh_packet_connection_is_on_socket(ssh))
- return;
- set_nodelay(state->connection_in);
- ssh_packet_set_tos(ssh, interactive ? qos_interactive : qos_bulk);
+/* Set QoS flags to be used for interactive and non-interactive sessions */
+void
+ssh_packet_set_qos(struct ssh *ssh, int qos_interactive, int qos_other)
+{
+ struct session_state *state = ssh->state;
+
+ state->qos_interactive = qos_interactive;
+ state->qos_other = qos_other;
+ apply_qos(ssh);
}
/* Returns true if the current connection is interactive. */
-
int
ssh_packet_is_interactive(struct ssh *ssh)
{
struct session_state *state = ssh->state;
int r;
+#define ENCODE_INT(v) (((v) < 0) ? 0xFFFFFFFF : (u_int)v)
if ((r = kex_to_blob(m, ssh->kex)) != 0 ||
(r = newkeys_to_blob(m, ssh, MODE_OUT)) != 0 ||
(r = newkeys_to_blob(m, ssh, MODE_IN)) != 0 ||
(r = sshbuf_put_u32(m, state->p_read.packets)) != 0 ||
(r = sshbuf_put_u64(m, state->p_read.bytes)) != 0 ||
(r = sshbuf_put_stringb(m, state->input)) != 0 ||
- (r = sshbuf_put_stringb(m, state->output)) != 0)
+ (r = sshbuf_put_stringb(m, state->output)) != 0 ||
+ (r = sshbuf_put_u32(m, ENCODE_INT(state->interactive_mode))) != 0 ||
+ (r = sshbuf_put_u32(m, ENCODE_INT(state->qos_interactive))) != 0 ||
+ (r = sshbuf_put_u32(m, ENCODE_INT(state->qos_other))) != 0)
return r;
-
+#undef ENCODE_INT
return 0;
}
const u_char *input, *output;
size_t ilen, olen;
int r;
+ u_int interactive, qos_interactive, qos_other;
if ((r = kex_from_blob(m, &ssh->kex)) != 0 ||
(r = newkeys_from_blob(m, ssh, MODE_OUT)) != 0 ||
(r = sshbuf_put(state->output, output, olen)) != 0)
return r;
+ if ((r = sshbuf_get_u32(m, &interactive)) != 0 ||
+ (r = sshbuf_get_u32(m, &qos_interactive)) != 0 ||
+ (r = sshbuf_get_u32(m, &qos_other)) != 0)
+ return r;
+#define DECODE_INT(v) ((v) > INT_MAX ? -1 : (v))
+ state->interactive_mode = DECODE_INT(interactive);
+ state->qos_interactive = DECODE_INT(qos_interactive);
+ state->qos_other = DECODE_INT(qos_other);
+#undef DECODE_INT
+
if (sshbuf_len(m))
return SSH_ERR_INVALID_FORMAT;
debug3_f("done");
-/* $OpenBSD: packet.h,v 1.99 2024/08/15 00:51:51 djm Exp $ */
+/* $OpenBSD: packet.h,v 1.100 2025/08/18 03:43:01 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
void ssh_packet_set_protocol_flags(struct ssh *, u_int);
u_int ssh_packet_get_protocol_flags(struct ssh *);
void ssh_packet_set_tos(struct ssh *, int);
-void ssh_packet_set_interactive(struct ssh *, int, int, int);
+void ssh_packet_set_interactive(struct ssh *, int);
int ssh_packet_is_interactive(struct ssh *);
+void ssh_packet_set_qos(struct ssh *, int, int);
void ssh_packet_set_server(struct ssh *);
void ssh_packet_set_authenticated(struct ssh *);
void ssh_packet_set_mux(struct ssh *);
-/* $OpenBSD: serverloop.c,v 1.242 2025/08/18 03:29:11 djm Exp $ */
+/* $OpenBSD: serverloop.c,v 1.243 2025/08/18 03:43:01 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
process_output(struct ssh *ssh, int connection_out)
{
int r;
+ static int interactive = -1;
/* Send any buffered packet data to the client. */
+ if (interactive != !channel_has_bulk(ssh)) {
+ interactive = !channel_has_bulk(ssh);
+ debug2_f("session QoS is now %s", interactive ?
+ "interactive" : "non-interactive");
+ ssh_packet_set_interactive(ssh, interactive);
+ }
if ((r = ssh_packet_write_poll(ssh)) != 0) {
sshpkt_fatal(ssh, r, "%s: ssh_packet_write_poll",
__func__);
-/* $OpenBSD: session.c,v 1.342 2025/05/05 02:48:06 djm Exp $ */
+/* $OpenBSD: session.c,v 1.343 2025/08/18 03:43:01 djm Exp $ */
/*
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
#endif
s->pid = pid;
- /* Set interactive/non-interactive mode. */
- ssh_packet_set_interactive(ssh, s->display != NULL,
- options.ip_qos_interactive, options.ip_qos_bulk);
/*
* Clear loginmsg, since it's the child's responsibility to display
/* Enter interactive session. */
s->ptymaster = ptymaster;
- ssh_packet_set_interactive(ssh, 1,
- options.ip_qos_interactive, options.ip_qos_bulk);
session_set_fds(ssh, s, ptyfd, fdout, -1, 1, 1);
return 0;
}
-/* $OpenBSD: ssh.c,v 1.614 2025/06/19 05:49:05 djm Exp $ */
+/* $OpenBSD: ssh.c,v 1.615 2025/08/18 03:43:01 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
fatal("Couldn't allocate session state");
channel_init_channels(ssh);
-
/* Parse command-line arguments. */
args = argv_assemble(ac, av); /* logged later */
host = NULL;
if (options.port == 0)
options.port = default_ssh_port();
channel_set_af(ssh, options.address_family);
+ ssh_packet_set_qos(ssh, options.ip_qos_interactive,
+ options.ip_qos_bulk);
/* Tidy and check options */
if (options.host_key_alias != NULL)
{
extern char **environ;
const char *display, *term;
- int r, interactive = tty_flag;
+ int r;
char *proto = NULL, *data = NULL;
if (!success)
data, 1);
client_expect_confirm(ssh, id, "X11 forwarding", CONFIRM_WARN);
/* XXX exit_on_forward_failure */
- interactive = 1;
}
check_agent_present();
fatal_fr(r, "send packet");
}
- /* Tell the packet module whether this is an interactive session. */
- ssh_packet_set_interactive(ssh, interactive,
- options.ip_qos_interactive, options.ip_qos_bulk);
-
if ((term = lookup_env_in_list("TERM", options.setenv,
options.num_setenv)) == NULL || *term == '\0')
term = getenv("TERM");
"session", SSH_CHANNEL_OPENING, in, out, err,
window, packetmax, CHAN_EXTENDED_WRITE,
"client-session", CHANNEL_NONBLOCK_STDIO);
-
- debug3_f("channel_new: %d", c->self);
+ if (tty_flag)
+ channel_set_tty(ssh, c);
+ debug3_f("channel_new: %d%s", c->self, tty_flag ? " (tty)" : "");
channel_send_open(ssh, c->self);
if (options.session_type != SESSION_TYPE_NONE)
static int
ssh_session2(struct ssh *ssh, const struct ssh_conn_info *cinfo)
{
- int r, interactive, id = -1;
+ int r, id = -1;
char *cp, *tun_fwd_ifname = NULL;
/* XXX should be pre-session */
if (options.session_type != SESSION_TYPE_NONE)
id = ssh_session2_open(ssh);
- else {
- interactive = options.control_master == SSHCTL_MASTER_NO;
- /* ControlPersist may have clobbered ControlMaster, so check */
- if (need_controlpersist_detach)
- interactive = otty_flag != 0;
- ssh_packet_set_interactive(ssh, interactive,
- options.ip_qos_interactive, options.ip_qos_bulk);
- }
/* If we don't expect to open a new session, then disallow it */
if (options.control_master == SSHCTL_MASTER_NO &&
-/* $OpenBSD: sshd-auth.c,v 1.5 2025/08/18 01:59:53 djm Exp $ */
+/* $OpenBSD: sshd-auth.c,v 1.6 2025/08/18 03:43:01 djm Exp $ */
/*
* SSH2 implementation:
* Privilege Separation:
/* Fill in default values for those options not explicitly set. */
fill_default_server_options(&options);
options.timing_secret = timing_secret; /* XXX eliminate from unpriv */
+ ssh_packet_set_qos(ssh, options.ip_qos_interactive,
+ options.ip_qos_bulk);
/* Reinit logging in case config set Level, Facility or Verbose. */
log_init(__progname, options.log_level, options.log_facility, 1);
-/* $OpenBSD: sshd-session.c,v 1.13 2025/05/06 05:40:56 djm Exp $ */
+/* $OpenBSD: sshd-session.c,v 1.14 2025/08/18 03:43:01 djm Exp $ */
/*
* SSH2 implementation:
* Privilege Separation:
fatal("Unable to create connection");
the_active_state = ssh;
ssh_packet_set_server(ssh);
+ ssh_packet_set_qos(ssh, options.ip_qos_interactive,
+ options.ip_qos_bulk);
check_ip_options(ssh);