diff --git a/Cargo.toml b/Cargo.toml index 57b7373a..6ad1cec9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,6 +113,10 @@ embedded-io = ["dep:embedded-io"] # Arbitrary for fuzzing. std is required for derive(Arbitrary) arbitrary = ["dep:arbitrary", "std"] +# Raise config::MAX_CHANNELS from 4 to 16. The channel array is fixed size, so +# this costs memory; it is off by default for `no_std` targets. +many-channels = [] + # Allocate larger buffers for things such as usernames. # See config.rs for details larger = [] diff --git a/src/channel.rs b/src/channel.rs index a136e978..698dbb30 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -228,6 +228,14 @@ impl Channels { self.get(num).is_ok_and(|c| c.valid_send(dt)) } + /// Whether a channel can no longer carry data, in any of the ways that can + /// happen: refused, closed, or already gone. + pub(crate) fn is_finished(&self, num: ChanNum) -> bool { + self.get_any(num).is_ok_and(|c| { + matches!(c.state, ChanState::PendingDone | ChanState::RecvClose) + }) || self.get_any(num).is_err() + } + pub fn progress(&mut self, s: &mut TrafSend) -> DispatchEvent { for ch in self.ch.iter_mut().filter_map(|c| c.as_mut()) { ch.check_send_window_adjust(s); diff --git a/src/config.rs b/src/config.rs index 12ff8ebf..5ae225a7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,7 +13,11 @@ pub(crate) const SSH_MAX_PACKET: usize = 35000; // // This size is arbitrary and may be increased, though note that some code paths assume // a linear scan of channels can happen quickly, so may need reworking for performance. -pub const MAX_CHANNELS: usize = 4; +// +// The channel array is fixed size, so the default stays small for `no_std` +// targets. Hosts that multiplex several sessions or forwarded connections over +// one transport can opt in to more with the `many-channels` feature. +pub const MAX_CHANNELS: usize = if cfg!(feature = "many-channels") { 16 } else { 4 }; // Enough for longest 23 of "screen.konsole-256color" on my system // Unsure if this is specified somewhere diff --git a/src/runner.rs b/src/runner.rs index 8f5568ef..1909486f 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -681,6 +681,20 @@ impl<'a, CS: CliServ> Runner<'a, CS> { self.conn.channels.valid_send(chan.0, dt) } + /// Returns `true` once the channel can no longer carry data. + /// + /// This covers a channel the peer refused to open, one that has been closed, + /// and one already released. The handle must still be returned with + /// [`Runner::channel_done`]. + /// + /// Callers waiting for an open to complete need this to tell "not yet" from + /// "never". A refused open leaves the channel in a finished state rather + /// than removing it, so without this a rejection is indistinguishable from a + /// slow open until the caller's own deadline expires. + pub fn is_channel_finished(&self, chan: &ChanHandle) -> bool { + self.conn.channels.is_finished(chan.0) + } + /// Must be called when an application has finished with a channel. /// /// Channel numbers will not be re-used without calling this, so @@ -887,6 +901,37 @@ impl<'a> Runner<'a, client::Client> { self.wake(); Ok(ChanHandle(chan)) } + + /// Open a `direct-tcpip` channel, asking the server to connect to + /// `address:port` on our behalf. + /// + /// This is the channel type behind `ssh -L` and `ssh -J`. `origin` and + /// `origin_port` describe the local end the server is told about; they are + /// informational, and servers commonly log them. + /// + /// The returned handle is not usable until the server confirms the open. + /// [`Runner::is_write_channel_valid`] reports `false` until then, because a + /// channel that is still opening is not yet a channel that can carry data. + pub fn open_client_tcpip( + &mut self, + address: &str, + port: u16, + origin: &str, + origin_port: u16, + ) -> Result { + trace!("open_client_tcpip {address}:{port}"); + + let ty = packets::ChannelOpenType::DirectTcpip(packets::DirectTcpip { + address: address.into(), + port: port as u32, + origin: origin.into(), + origin_port: origin_port as u32, + }); + let (chan, p) = self.conn.channels.open(ty)?; + self.traf_out.send_packet(p, &mut self.keys)?; + self.wake(); + Ok(ChanHandle(chan)) + } } /// Sets a waker, waking any existing waker