diff --git a/sshproto/src/async_behaviour.rs b/sshproto/src/async_behaviour.rs
index 5fac8f1c19c52771c7703ccbadbb9c2fd03ff1ec..f64e8747f6cc84ebe4516b7b197cf04797b342af 100644
--- a/sshproto/src/async_behaviour.rs
+++ b/sshproto/src/async_behaviour.rs
@@ -11,6 +11,7 @@ use {
 use async_trait::async_trait;
 
 use crate::{*, conn::RespPackets};
+use packets::{ForwardedTcpip,DirectTcpip};
 use behaviour::*;
 
 pub(crate) enum AsyncCliServ<'a> {
@@ -95,9 +96,11 @@ pub trait AsyncCliBehaviour: Sync+Send {
     // TODO: postauth channel callbacks
 
     // TODO: do we want this to be async? probably not.
-    fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened;
+    fn open_tcp_forwarded(&self, chan: u32,
+        t: &ForwardedTcpip) -> channel::ChanOpened;
 
-    fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened;
+    fn open_tcp_direct(&self, chan: u32,
+        t: &DirectTcpip) -> channel::ChanOpened;
 }
 
 // #[async_trait(?Send)]
@@ -126,7 +129,9 @@ pub trait AsyncServBehaviour: Sync+Send {
     /// Returns whether a session can be opened
     fn open_session(&self, chan: u32) -> channel::ChanOpened;
 
-    fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened;
+    fn open_tcp_forwarded(&self, chan: u32,
+        t: &ForwardedTcpip) -> channel::ChanOpened;
 
-    fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened;
+    fn open_tcp_direct(&self, chan: u32,
+        t: &ForwardedTcpip) -> channel::ChanOpened;
 }
diff --git a/sshproto/src/behaviour.rs b/sshproto/src/behaviour.rs
index 8d8a3a5724bb65643e626ad488cf8e05bc14da10..a2c2259adbd28ae571e42789d45a9efc2f94430b 100644
--- a/sshproto/src/behaviour.rs
+++ b/sshproto/src/behaviour.rs
@@ -13,7 +13,7 @@ use core::fmt;
 use heapless::spsc::{Queue,Producer,Consumer};
 
 use crate::*;
-use packets::{self,Packet};
+use packets::{self,Packet,ForwardedTcpip,DirectTcpip};
 use runner::{self,Runner};
 use channel::ChanMsg;
 use conn::RespPackets;
@@ -82,20 +82,22 @@ impl<'a> Behaviour<'a> {
     }
 
     /// Calls either client or server
-    pub(crate) fn open_tcp_forwarded(&mut self, chan: u32) -> channel::ChanOpened {
+    pub(crate) fn open_tcp_forwarded(&mut self, chan: u32,
+        t: &ForwardedTcpip) -> channel::ChanOpened {
         if self.is_client() {
-            self.client().unwrap().open_tcp_forwarded(chan)
+            self.client().unwrap().open_tcp_forwarded(chan, t)
         } else {
-            self.server().unwrap().open_tcp_forwarded(chan)
+            self.server().unwrap().open_tcp_forwarded(chan, t)
         }
     }
 
     /// Calls either client or server
-    pub(crate) fn open_tcp_direct(&mut self, chan: u32) -> channel::ChanOpened {
+    pub(crate) fn open_tcp_direct(&mut self, chan: u32,
+        t: &DirectTcpip) -> channel::ChanOpened {
         if self.is_client() {
-            self.client().unwrap().open_tcp_direct(chan)
+            self.client().unwrap().open_tcp_direct(chan, t)
         } else {
-            self.server().unwrap().open_tcp_direct(chan)
+            self.server().unwrap().open_tcp_direct(chan, t)
         }
     }
 }
@@ -134,20 +136,22 @@ impl<'a> Behaviour<'a>
     }
 
     /// Calls either client or server
-    pub(crate) fn open_tcp_forwarded(&mut self, chan: u32) -> channel::ChanOpened {
+    pub(crate) fn open_tcp_forwarded(&mut self, chan: u32,
+        t: &ForwardedTcpip) -> channel::ChanOpened {
         if self.is_client() {
-            self.client().unwrap().open_tcp_forwarded(chan)
+            self.client().unwrap().open_tcp_forwarded(chan, t)
         } else {
-            self.server().unwrap().open_tcp_forwarded(chan)
+            self.server().unwrap().open_tcp_forwarded(chan, t)
         }
     }
 
     /// Calls either client or server
-    pub(crate) fn open_tcp_direct(&mut self, chan: u32) -> channel::ChanOpened {
+    pub(crate) fn open_tcp_direct(&mut self, chan: u32,
+        t: &DirectTcpip) -> channel::ChanOpened {
         if self.is_client() {
-            self.client().unwrap().open_tcp_direct(chan)
+            self.client().unwrap().open_tcp_direct(chan, t)
         } else {
-            self.server().unwrap().open_tcp_direct(chan)
+            self.server().unwrap().open_tcp_direct(chan, t)
         }
     }
 }
@@ -190,12 +194,14 @@ impl<'a> CliBehaviour<'a> {
         Ok(())
     }
 
-    pub(crate) fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened {
-        self.inner.open_tcp_forwarded(chan)
+    pub(crate) fn open_tcp_forwarded(&self, chan: u32,
+        t: &ForwardedTcpip) -> channel::ChanOpened {
+        self.inner.open_tcp_forwarded(chan, t)
     }
 
-    pub(crate) fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened {
-        self.inner.open_tcp_direct(chan)
+    pub(crate) fn open_tcp_direct(&self, chan: u32,
+        t: &DirectTcpip) -> channel::ChanOpened {
+        self.inner.open_tcp_direct(chan, t)
     }
 }
 
@@ -231,12 +237,14 @@ impl<'a> CliBehaviour<'a> {
         Ok(())
     }
 
-    pub(crate) fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened {
-        self.inner.open_tcp_forwarded(chan)
+    pub(crate) fn open_tcp_forwarded(&self, chan: u32,
+        t: &ForwardedTcpip) -> channel::ChanOpened {
+        self.inner.open_tcp_forwarded(chan, t)
     }
 
-    pub(crate) fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened {
-        self.inner.open_tcp_direct(chan)
+    pub(crate) fn open_tcp_direct(&self, chan: u32,
+        t: &DirectTcpip) -> channel::ChanOpened {
+        self.inner.open_tcp_direct(chan, t)
     }
 }
 
@@ -271,12 +279,14 @@ impl<'a> ServBehaviour<'a> {
         self.inner.open_session(chan)
     }
 
-    pub(crate) fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened {
-        self.inner.open_tcp_forwarded(chan)
+    pub(crate) fn open_tcp_forwarded(&self, chan: u32,
+        t: &ForwardedTcpip) -> channel::ChanOpened {
+        self.inner.open_tcp_forwarded(chan, t)
     }
 
-    pub(crate) fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened {
-        self.inner.open_tcp_direct(chan)
+    pub(crate) fn open_tcp_direct(&self, chan: u32,
+        t: &DirectTcpip) -> channel::ChanOpened {
+        self.inner.open_tcp_direct(chan, t)
     }
 }
 
@@ -291,12 +301,14 @@ impl<'a> ServBehaviour<'a> {
         self.inner.open_session(chan)
     }
 
-    pub(crate) fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened {
-        self.inner.open_tcp_forwarded(chan)
+    pub(crate) fn open_tcp_forwarded(&self, chan: u32,
+        t: &ForwardedTcpip) -> channel::ChanOpened {
+        self.inner.open_tcp_forwarded(chan, t)
     }
 
-    pub(crate) fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened {
-        self.inner.open_tcp_direct(chan)
+    pub(crate) fn open_tcp_direct(&self, chan: u32,
+        t: &DirectTcpip) -> channel::ChanOpened {
+        self.inner.open_tcp_direct(chan, t)
     }
 }
 
diff --git a/sshproto/src/block_behaviour.rs b/sshproto/src/block_behaviour.rs
index fb72694d7ee3dfb51e7de39dc5ba8275b8824c35..85e87575bf6a1a2d9c04939f5a812ec69e788e4f 100644
--- a/sshproto/src/block_behaviour.rs
+++ b/sshproto/src/block_behaviour.rs
@@ -8,6 +8,7 @@ use {
 };
 
 use crate::{conn::RespPackets, *};
+use packets::{ForwardedTcpip,DirectTcpip};
 use behaviour::*;
 
 pub(crate) enum BlockCliServ<'a> {
@@ -83,9 +84,11 @@ pub trait BlockCliBehaviour {
     }
     // TODO: postauth channel callbacks
 
-    fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened;
+    fn open_tcp_forwarded(&self, chan: u32,
+        t: &ForwardedTcpip) -> channel::ChanOpened;
 
-    fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened;
+    fn open_tcp_direct(&self, chan: u32,
+        t: &DirectTcpip) -> channel::ChanOpened;
 }
 
 pub trait BlockServBehaviour {
@@ -101,7 +104,9 @@ pub trait BlockServBehaviour {
     /// Returns whether a session can be opened
     fn open_session(&self, chan: u32) -> channel::ChanOpened;
 
-    fn open_tcp_forwarded(&self, chan: u32) -> channel::ChanOpened;
+    fn open_tcp_forwarded(&self, chan: u32,
+        t: &ForwardedTcpip) -> channel::ChanOpened;
 
-    fn open_tcp_direct(&self, chan: u32) -> channel::ChanOpened;
+    fn open_tcp_direct(&self, chan: u32,
+        t: &DirectTcpip) -> channel::ChanOpened;
 }
diff --git a/sshproto/src/channel.rs b/sshproto/src/channel.rs
index 3c08cc8be6551534fffa47210028ee4744d3c10d..9522b8e7223e05545dbb8230935e41e74a2efe7b 100644
--- a/sshproto/src/channel.rs
+++ b/sshproto/src/channel.rs
@@ -8,7 +8,7 @@ use core::mem;
 
 use heapless::{Deque, String, Vec};
 
-use crate::{conn::RespPackets, *};
+use crate::{conn::RespPackets, *, packets::ChannelOpenFailure};
 use config::*;
 use packets::{ChannelReqType, ChannelRequest, Packet, ChannelOpen, ChannelOpenType, ChannelData, ChannelDataExt};
 use sshwire::{BinString, TextString};
@@ -32,6 +32,27 @@ pub(crate) struct Channels {
 
 pub(crate) type InitReqs = Vec<ReqDetails, MAX_INIT_REQS>;
 
+// for dispatch_open_inner()
+enum DispatchOpenError {
+    Error(Error),
+    Failure(ChanFail),
+}
+
+impl From<Error> for DispatchOpenError {
+    fn from(e: Error) -> Self {
+        match e {
+            Error::NoChannels => Self::Failure(ChanFail::SSH_OPEN_RESOURCE_SHORTAGE),
+            e => Self::Error(e)
+        }
+    }
+}
+
+impl From<ChanFail> for DispatchOpenError {
+    fn from(f: ChanFail) -> Self {
+        Self::Failure(f)
+    }
+}
+
 impl Channels {
     pub fn new() -> Self {
         Channels {
@@ -174,82 +195,82 @@ impl Channels {
         self.get(num).map_or(Some(0), |c| c.send_allowed())
     }
 
-    pub fn channel_open(&mut self, p: &ChannelOpen<'_>,
+    fn dispatch_open(&mut self, p: &ChannelOpen<'_>,
         resp: &mut RespPackets<'_>,
         b: &mut Behaviour<'_>,
         ) -> Result<()> {
-        let mut failure = None;
-        let open_res = match &p.ty {
-            ChannelOpenType::Session => {
-                // only server should receive session opens
-                let bserv = b.server().map_err(|_| Error::SSHProtoError)?;
 
-                match self.reserve_chan(p) {
-                    Ok(ch) => {
-                        let r = bserv.open_session(ch.recv.num);
-                        Some((ch, r))
-                    }
-                    Err(_) => {
-                        failure = Some(ChanFail::SSH_OPEN_RESOURCE_SHORTAGE);
-                        None
-                    },
-                }
+        match self.dispatch_open_inner(p, resp, b) {
+            Err(DispatchOpenError::Failure(f)) => {
+                let r = packets::ChannelOpenFailure {
+                    num: p.num,
+                    reason: f as u32,
+                    desc: "".into(),
+                    lang: "",
+                };
+                let r: Packet = r.into();
+                resp.push(r.into()).trap()?;
+                Ok(())
+            },
+            Err(DispatchOpenError::Error(e)) => Err(e),
+            Ok(()) => Ok(())
+        }
+    }
+
+    // the caller will send failure messages if required
+    fn dispatch_open_inner(&mut self, p: &ChannelOpen<'_>,
+        resp: &mut RespPackets<'_>,
+        b: &mut Behaviour<'_>,
+        ) -> Result<(), DispatchOpenError> {
+
+        if b.is_client() && matches!(p.ty, ChannelOpenType::Session) {
+            // only server should receive session opens
+            return Err(Error::SSHProtoError.into());
+        }
+
+        // get a channel
+        let ch = match &p.ty {
+            ChannelOpenType::Unknown(u) => {
+                debug!("Rejecting unknown channel type '{u}'");
+                return Err(ChanFail::SSH_OPEN_UNKNOWN_CHANNEL_TYPE.into());
             }
-            ChannelOpenType::ForwardedTcpip(t) => {
-                match self.reserve_chan(p) {
-                    Ok(ch) => {
-                        let r = b.open_tcp_forwarded(ch.recv.num);
-                        Some((ch, r))
-                    }
-                    Err(_) => {
-                        failure = Some(ChanFail::SSH_OPEN_RESOURCE_SHORTAGE);
-                        None
-                    },
-                }
+            _ => {
+                self.reserve_chan(p)?
+            }
+        };
 
+        // beware that a reserved channel must be cleaned up on failure
+
+        // run the Behaviour function
+        let r = match &p.ty {
+            ChannelOpenType::Session => {
+                // unwrap: earlier test ensures b.server() succeeds
+                let bserv = b.server().unwrap();
+                bserv.open_session(ch.recv.num)
+            }
+            ChannelOpenType::ForwardedTcpip(t) => {
+                b.open_tcp_forwarded(ch.recv.num, t)
             }
             ChannelOpenType::DirectTcpip(t) => {
-                match self.reserve_chan(p) {
-                    Ok(ch) => {
-                        let r = b.open_tcp_direct(ch.recv.num);
-                        Some((ch, r))
-                    }
-                    Err(_) => {
-                        failure = Some(ChanFail::SSH_OPEN_RESOURCE_SHORTAGE);
-                        None
-                    },
-                }
+                b.open_tcp_direct(ch.recv.num, t)
             }
-            ChannelOpenType::Unknown(u) => {
-                debug!("Rejecting unknown channel type '{u}'");
-                failure = Some(ChanFail::SSH_OPEN_UNKNOWN_CHANNEL_TYPE);
-                None
+            ChannelOpenType::Unknown(_) => {
+                unreachable!()
             }
         };
 
-        if let Some((ch, r)) = open_res {
-            match r {
-                ChanOpened::Success => {
-                    ch.open_done();
-                },
-                ChanOpened::Failure(f) => {
-                    failure = Some(f);
-                }
-                ChanOpened::Defer => {
-                    // application will reply later
-                }
+        match r {
+            ChanOpened::Success => {
+                resp.push(ch.open_done().into()).trap()?
+            },
+            ChanOpened::Failure(f) => {
+                let n = ch.recv.num;
+                self.remove(n);
+                return Err(f.into())
+            }
+            ChanOpened::Defer => {
+                // application will reply later
             }
-        }
-
-        if let Some(reason) = failure {
-            let r = packets::ChannelOpenFailure {
-                num: p.num,
-                reason: reason as u32,
-                desc: "".into(),
-                lang: "",
-            };
-            let r: Packet = r.into();
-            resp.push(r.into()).trap()?;
         }
 
         Ok(())
@@ -267,7 +288,7 @@ impl Channels {
         trace!("chan dispatch");
         let r = match packet {
             Packet::ChannelOpen(p) => {
-                self.channel_open(&p, resp, b)
+                self.dispatch_open(&p, resp, b)
                 .map(|_| None)
             }
 
@@ -490,8 +511,9 @@ pub struct ChanDir {
 }
 
 enum ChanState {
-    /// An incoming channel open request that has not yet been responded to,
-    /// should not be used
+    /// An incoming channel open request that has not yet been responded to.
+    ///
+    /// Not to be used for normal channel messages
     InOpen,
     /// `init_req` are the request messages to be sent once the ChannelOpenConfirmation
     /// is received
@@ -515,7 +537,7 @@ pub(crate) struct Channel {
     last_req: heapless::Deque<ReqKind, MAX_OUTSTANDING_REQS>,
 
     recv: ChanDir,
-    // filled after confirmation when we initiate the channel
+    /// populated in all states except `Opening`
     send: Option<ChanDir>,
 
     /// Accumulated bytes for the next window adjustment (inbound data direction)
@@ -554,9 +576,19 @@ impl Channel {
         self.recv.num
     }
 
-    fn open_done(&mut self) {
+    /// Returns an open confirmation reply packet to send.
+    /// Must be called with state of `InOpen`.
+    fn open_done<'p>(&mut self) -> Packet<'p> {
         debug_assert!(matches!(self.state, ChanState::InOpen));
-        self.state = ChanState::Normal
+
+        self.state = ChanState::Normal;
+        packets::ChannelOpenConfirmation {
+            num: self.recv.num,
+            // unwrap: state is InOpen
+            sender_num: self.send.as_ref().unwrap().num,
+            initial_window: self.recv.window as u32,
+            max_packet: self.recv.max_packet as u32,
+        }.into()
     }
 
     fn finished_input(&mut self, len: usize ) {
diff --git a/sshproto/src/error.rs b/sshproto/src/error.rs
index 4b719793e061b5fd66bfc11bd6a14f0d6e86ff66..bfbb14b2affd80ef11ae51fcd7579cd1dbd017d8 100644
--- a/sshproto/src/error.rs
+++ b/sshproto/src/error.rs
@@ -42,7 +42,7 @@ pub enum Error {
     /// Signature doesn't match key type
     SigMismatch,
 
-    /// Error in received SSH protocol
+    /// Error in received SSH protocol. Will disconnect.
     SSHProtoError,
 
     /// Remote peer isn't SSH