diff --git a/async/examples/sunsetc.rs b/async/examples/sunsetc.rs
index fb0171c2b52eefb003210a6fed986e3b6b598fd9..b4a36cc141b7e6e6b4be216bbdd410df933f14a9 100644
--- a/async/examples/sunsetc.rs
+++ b/async/examples/sunsetc.rs
@@ -179,9 +179,9 @@ fn setup_log(args: &Args) -> Result<()> {
     .add_filter_allow_str("sunset")
     .add_filter_allow_str("sshclient")
     // not debugging these bits of the stack at present
-    // .add_filter_ignore_str("sunset::traffic")
-    // .add_filter_ignore_str("sunset::runner")
-    // .add_filter_ignore_str("sunset_embassy")
+    .add_filter_ignore_str("sunset::traffic")
+    .add_filter_ignore_str("sunset::runner")
+    .add_filter_ignore_str("sunset_embassy")
     .set_time_offset_to_local().expect("Couldn't get local timezone")
     .build();
 
diff --git a/async/src/cmdline_client.rs b/async/src/cmdline_client.rs
index ecc54d9ce0b928c8569fd9e42dda62cae038df84..a5c08af522ac0b888f1e7e7a17cc062283566cd5 100644
--- a/async/src/cmdline_client.rs
+++ b/async/src/cmdline_client.rs
@@ -271,6 +271,7 @@ impl<'a> CmdlineRunner<'a> {
     /// Runs the `CmdlineClient` session. Requests a shell or command, performs
     /// channel IO.
     pub async fn run(&mut self, cli: &'a SSHClient<'a, CmdlineHooks<'a>>) -> Result<()> {
+        // chanio is only set once a channel is opened below
         let chanio = Fuse::terminated();
         pin_mut!(chanio);
 
diff --git a/src/cliauth.rs b/src/cliauth.rs
index 9d453a45807652ea17363fd0e77a3bc390be10c6..9ac2435f135d8795eb90c12c12afda4b9b3664d3 100644
--- a/src/cliauth.rs
+++ b/src/cliauth.rs
@@ -74,12 +74,15 @@ pub(crate) struct CliAuth {
 
     username: ResponseString,
 
-    // Starts as true, set to false if hook.auth_password() returns None.
-    // Not set false if the server rejects auth.
+    /// Starts as true, set to false if hook.auth_password() returns None.
+    /// Not set false if the server rejects auth.
     try_password: bool,
 
-    // Set to false if hook.next_authkey() returns None.
+    /// Set to false if hook.next_authkey() returns None.
     try_pubkey: bool,
+
+    /// Set once we are OKed from MSG_EXT_INFO
+    allow_rsa_sha2: bool,
 }
 
 impl CliAuth {
@@ -90,6 +93,7 @@ impl CliAuth {
             username: ResponseString::new(),
             try_password: true,
             try_pubkey: true,
+            allow_rsa_sha2: false,
         }
     }
 
@@ -308,4 +312,12 @@ impl CliAuth {
         // TODO errors
         Ok(())
     }
+
+    pub fn handle_ext_info(&mut self, p: &packets::ExtInfo<'_>) {
+        if let Some(ref algs) = p.server_sig_algs {
+            // OK unwrap: is a remote namelist
+            self.allow_rsa_sha2 = algs.has_algo(SSH_NAME_RSA_SHA256).unwrap();
+            trace!("setting allow_rsa_sha2 = {}", self.allow_rsa_sha2);
+        }
+    }
 }
diff --git a/src/client.rs b/src/client.rs
index 4aaf7ac4f33409f2875fae9feb12d989e59a4a0c..d9f02f8197b29072f4d519649bfe5b93fd729f37 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -24,8 +24,6 @@ impl Client {
         Client { auth: CliAuth::new() }
     }
 
-    // pub fn check_hostkey(hostkey: )
-
     pub(crate) fn auth_success(
         &mut self,
         parse_ctx: &mut ParseContext,
diff --git a/src/conn.rs b/src/conn.rs
index a2502a3c027a2216f8a162daef2bd20c4a51a044..e42c82fd77df41d98f12b41a9ed158815e3918d1 100644
--- a/src/conn.rs
+++ b/src/conn.rs
@@ -263,6 +263,12 @@ impl<C: CliBehaviour, S: ServBehaviour> Conn<C, S> {
             Packet::NewKeys(_) => {
                 self.kex.handle_newkeys(&mut self.sess_id, s)?;
             }
+            Packet::ExtInfo(p) => {
+                if let ClientServer::Client(cli) = &mut self.cliserv {
+                    cli.auth.handle_ext_info(&p);
+                }
+                // could potentially pass it to other handlers too
+            }
             Packet::ServiceRequest(p) => {
                 if let ClientServer::Server(serv) = &mut self.cliserv {
                     serv.service_request(&p, s)?;
@@ -282,7 +288,11 @@ impl<C: CliBehaviour, S: ServBehaviour> Conn<C, S> {
                 warn!("Received SSH unimplemented message");
             }
             Packet::DebugPacket(p) => {
-                warn!("SSH debug message from remote host: '{:?}'", p.message);
+                let level = match p.always_display {
+                    true => log::Level::Info,
+                    false => log::Level::Debug,
+                };
+                log!(level, "SSH debug message from remote host: {}", p.message);
             }
             Packet::Disconnect(p) => {
                 // We ignore p.reason.
diff --git a/src/kex.rs b/src/kex.rs
index b79b6cec35008e239e6203287315f8aac092fffb..7ee639b0f16ce2803e7519db3597dd5b3a69c0bc 100644
--- a/src/kex.rs
+++ b/src/kex.rs
@@ -34,6 +34,10 @@ use pretty_hex::PrettyHex;
 const fixed_options_kex: &[&'static str] =
     &[SSH_NAME_CURVE25519, SSH_NAME_CURVE25519_LIBSSH];
 
+/// Options that can't be negotiated
+const marker_only_kexs: &[&'static str] =
+    &[SSH_NAME_EXT_INFO_C, SSH_NAME_EXT_INFO_S, SSH_NAME_KEXGUESS2];
+
 const fixed_options_hostsig: &[&'static str] = &[
     SSH_NAME_ED25519,
     #[cfg(rsa)]
@@ -56,10 +60,20 @@ pub(crate) struct AlgoConfig {
 impl AlgoConfig {
     /// Creates the standard algorithm configuration
     /// TODO: ext-info-s and ext-info-c
-    pub fn new(_is_client: bool) -> Self {
-        AlgoConfig {
+    pub fn new(is_client: bool) -> Self {
+        // OK unwrap: static arrays are < MAX_LOCAL_NAMES
+        let mut kexs: LocalNames = fixed_options_kex.try_into().unwrap();
+
+        // Only clients are interested in ext-info
+        // TODO perhaps it could go behind cfg(rsa)?
+        if is_client {
             // OK unwrap: static arrays are < MAX_LOCAL_NAMES
-            kexs: fixed_options_kex.try_into().unwrap(),
+            kexs.0.push(SSH_NAME_EXT_INFO_C).unwrap();
+
+        }
+
+        AlgoConfig {
+            kexs,
             hostsig: fixed_options_hostsig.try_into().unwrap(),
             ciphers: fixed_options_cipher.try_into().unwrap(),
             macs: fixed_options_mac.try_into().unwrap(),
@@ -201,6 +215,9 @@ pub(crate) struct Algos {
     // avoid having to keep passing it separately, though this
     // is global state.
     pub is_client: bool,
+
+    // whether the remote side supports ext-info
+    pub ext_info: bool,
 }
 
 impl fmt::Display for Algos {
@@ -367,10 +384,13 @@ impl Kex {
             .kex
             .first_match(is_client, &conf.kexs)?
             .ok_or(Error::AlgoNoMatch { algo: "kex" })?;
-        if kex_method == SSH_NAME_KEXGUESS2 {
-            trace!("kexguess2 was negotiated, returning AlgoNoMatch");
+
+        // Certain kex method names aren't actual algorithms, just markers.
+        // If they are negotiated it means no valid method matched
+        if marker_only_kexs.contains(&kex_method) {
             return Err(Error::AlgoNoMatch { algo: "kex" });
         }
+
         let kex = SharedSecret::from_name(kex_method)?;
         let goodguess_kex = if kexguess2 {
             p.kex.first() == kex_method
@@ -378,6 +398,14 @@ impl Kex {
             p.kex.first() == conf.kexs.first()
         };
 
+        // we only send MSG_EXT_INFO to a client, don't look
+        // for SSH_NAME_EXT_INFO_S
+        let ext_info = match is_client {
+            true => false,
+            // OK unwrap: p.kex is a remote list
+            false => p.kex.has_algo(SSH_NAME_EXT_INFO_C).unwrap(),
+        };
+
         let hostsig_method = p
             .hostsig
             .first_match(is_client, &conf.hostsig)?
@@ -445,6 +473,7 @@ impl Kex {
             integ_dec,
             discard_next,
             is_client,
+            ext_info,
         })
     }
 }
diff --git a/src/namelist.rs b/src/namelist.rs
index 49460c44a1ece2c6b6d5f2a0c028b2be4fa21a7c..318eedae35cf90703ff81afdc68fbf0f2f931942 100644
--- a/src/namelist.rs
+++ b/src/namelist.rs
@@ -72,7 +72,6 @@ impl SSHEncode for &LocalNames {
     }
 }
 
-// for tests
 impl<'a> TryFrom<&'a str> for StringNames<'a> {
     type Error = ();
     fn try_from(s: &'a str) -> Result<Self, Self::Error> {
@@ -86,6 +85,7 @@ impl<'a> TryFrom<&'a str> for NameList<'a> {
     }
 }
 
+// for tests
 impl TryFrom<&[&'static str]> for LocalNames {
     type Error = ();
     fn try_from(s: &[&'static str]) -> Result<Self, ()> {
@@ -123,6 +123,8 @@ impl NameList<'_> {
     }
 
     /// Returns whether the `algo` is contained in this list
+    ///
+    /// Fails iff given a Local variant
     pub fn has_algo(&self, algo: &str) -> Result<bool> {
         match self {
             NameList::String(s) => Ok(s.has_algo(algo)),
diff --git a/src/packets.rs b/src/packets.rs
index c4f58b26b2cf6e3870718827853a13d2f8609f94..b0278913092f20bbeb512a7f7490a8048ce39876 100644
--- a/src/packets.rs
+++ b/src/packets.rs
@@ -97,6 +97,55 @@ pub struct ServiceAccept<'a> {
     pub name: &'a str,
 }
 
+/// MSG_EXT_INFO
+///
+/// `ExtInfo` differs from most packet structs, it only tracks known extension types
+/// rather than an unknown-sized list.
+#[derive(Debug)]
+pub struct ExtInfo<'a> {
+    // Wire format is
+    // byte       SSH_MSG_EXT_INFO (value 7)
+    // uint32     nr-extensions
+    // repeat the following 2 fields "nr-extensions" times:
+    //   string   extension-name
+    //   string   extension-value (binary)
+
+    pub server_sig_algs: Option<NameList<'a>>,
+}
+
+impl<'de: 'a, 'a> SSHDecode<'de> for ExtInfo<'a> {
+    fn dec<S>(s: &mut S) -> WireResult<Self> where S: SSHSource<'de> {
+        let mut server_sig_algs = None;
+        let num = u32::dec(s)?;
+        for _ in 0..num {
+            let ext: &str = SSHDecode::dec(s)?;
+            match ext {
+                SSH_EXT_SERVER_SIG_ALGS => {
+                    server_sig_algs = Some(SSHDecode::dec(s)?);
+                },
+                _ => {
+                    // skip over
+                    let _: BinString = SSHDecode::dec(s)?;
+                },
+            }
+        }
+        Ok(Self {
+            server_sig_algs
+        })
+    }
+}
+
+impl SSHEncode for ExtInfo<'_> {
+    fn enc<S>(&self, s: &mut S) -> WireResult<()> where S: SSHSink {
+        if let Some(ref algs) = self.server_sig_algs {
+            1u32.enc(s)?;
+            SSH_EXT_SERVER_SIG_ALGS.enc(s)?;
+            algs.enc(s)?;
+        }
+        Ok(())
+    }
+}
+
 #[derive(Debug, SSHEncode, SSHDecode)]
 pub struct UserauthRequest<'a> {
     pub username: TextString<'a>,
@@ -436,7 +485,7 @@ pub enum RequestSuccess {
 }
 
 impl<'de> SSHDecode<'de> for RequestSuccess {
-    fn dec<S>(s: &mut S) -> WireResult<Self> where S: SSHSource<'de> {
+    fn dec<S>(_s: &mut S) -> WireResult<Self> where S: SSHSource<'de> {
         // if s.ctx().last_req_port {
         //     Ok(Self::TcpPort(TcpPort::dec(s)?))
         // } else {
@@ -852,7 +901,7 @@ messagetypes![
 (4, DebugPacket, DebugPacket<'a>, SSH_MSG_DEBUG, All),
 (5, ServiceRequest, ServiceRequest<'a>, SSH_MSG_SERVICE_REQUEST, Auth),
 (6, ServiceAccept, ServiceAccept<'a>, SSH_MSG_SERVICE_ACCEPT, Auth),
-// 7        SSH_MSG_EXT_INFO       RFC 8308
+(7, ExtInfo, ExtInfo<'a>, SSH_MSG_EXT_INFO, All),
 // 8        SSH_MSG_NEWCOMPRESS    RFC 8308
 (20, KexInit, KexInit<'a>, SSH_MSG_KEXINIT, All),
 (21, NewKeys, NewKeys, SSH_MSG_NEWKEYS, Kex),
diff --git a/src/sshnames.rs b/src/sshnames.rs
index 1114a66ad7cb5485e846e8072f70baaff983848a..57ec5bff669702a4db8f2b6e93cb12f4328db100 100644
--- a/src/sshnames.rs
+++ b/src/sshnames.rs
@@ -59,6 +59,9 @@ pub const SSH_AUTHMETHOD_INTERACTIVE: &str = "keyboard-interactive";
 /// [RFC4254](https://tools.ietf.org/html/rfc4254)
 pub const SSH_EXTENDED_DATA_STDERR: u32 = 1;
 
+/// [RFC8308](https://tools.ietf.org/html/rfc8308) Extension Negotiation
+pub const SSH_EXT_SERVER_SIG_ALGS: &str = "server-sig-algs";
+
 /// [RFC4254](https://tools.ietf.org/html/rfc4254)
 #[allow(non_camel_case_types)]
 #[derive(Debug)]