Skip to content

Commit 6495401

Browse files
authored
Merge pull request #80 from openssh-rust/feature/resume
Feature/resume
2 parents 102245f + d944a8c commit 6495401

File tree

11 files changed

+215
-20
lines changed

11 files changed

+215
-20
lines changed

.github/workflows/coverage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
uses: taiki-e/install-action@cargo-llvm-cov
3131
- run: |
3232
# Wait for startup of openssh-server
33-
sleep 30
33+
timeout 15 ./wait_for_sshd_start_up.sh
3434
chmod 600 .test-key
3535
mkdir /tmp/openssh-rs
3636
ssh -i .test-key -v -p 2222 -l test-user localhost -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/tmp/openssh-rs/known_hosts whoami

.github/workflows/minimal.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ jobs:
2929
toolchain: stable
3030
- uses: actions/checkout@v2
3131
- run: |
32+
# Wait for startup of openssh-server
33+
timeout 15 ./wait_for_sshd_start_up.sh
3234
chmod 600 .test-key
3335
mkdir /tmp/openssh-rs
3436
ssh -i .test-key -v -p 2222 -l test-user 127.0.0.1 -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/tmp/openssh-rs/known_hosts whoami

.github/workflows/test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ jobs:
2929
toolchain: ${{ matrix.toolchain }}
3030
- uses: actions/checkout@v2
3131
- run: |
32+
# Wait for startup of openssh-server
33+
timeout 15 ./wait_for_sshd_start_up.sh
3234
chmod 600 .test-key
3335
mkdir /tmp/openssh-rs
3436
ssh -i .test-key -v -p 2222 -l test-user 127.0.0.1 -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/tmp/openssh-rs/known_hosts whoami

run_ci_tests.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,5 @@ cargo test \
4141
--all-features \
4242
--no-fail-fast \
4343
-- --test-threads=3 # Use test-threads=3 so that the output is readable
44+
45+
rm -r .ssh-connection*

src/changelog.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
use crate::*;
33

44
/// TODO: RENAME THIS INTO THE NEXT VERSION BEFORE RELEASE
5+
///
6+
/// ## Added
7+
/// - [`Session::resume`]
8+
/// - [`Session::resume_mux`]
9+
/// - [`Session::leak`]
10+
/// - [`Session::force_terminate`]
511
#[doc(hidden)]
612
pub mod unreleased {}
713

src/native_mux_impl/session.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ impl Session {
2424
}
2525
}
2626

27+
pub(crate) fn resume(ctl: Box<Path>, _master_log: Option<Box<Path>>) -> Self {
28+
Self { tempdir: None, ctl }
29+
}
30+
2731
pub(crate) async fn check(&self) -> Result<(), Error> {
2832
Connection::connect(&self.ctl)
2933
.await?
@@ -63,19 +67,37 @@ impl Session {
6367
Ok(())
6468
}
6569

66-
pub(crate) async fn close(mut self) -> Result<(), Error> {
67-
// This also set self.tempdir to None so that Drop::drop would do nothing.
68-
let tempdir = self.tempdir.take().unwrap();
69-
70+
async fn close_impl(&self) -> Result<(), Error> {
7071
Connection::connect(&self.ctl)
7172
.await?
7273
.request_stop_listening()
7374
.await?;
7475

75-
tempdir.close().map_err(Error::Cleanup)?;
76+
Ok(())
77+
}
78+
79+
pub(crate) async fn close(mut self) -> Result<(), Error> {
80+
// This also set self.tempdir to None so that Drop::drop would do nothing.
81+
if let Some(tempdir) = self.tempdir.take() {
82+
self.close_impl().await?;
83+
84+
tempdir.close().map_err(Error::Cleanup)?;
85+
} else {
86+
self.close_impl().await?;
87+
}
7688

7789
Ok(())
7890
}
91+
92+
pub(crate) fn detach(mut self) -> (Box<Path>, Option<Box<Path>>) {
93+
(
94+
self.ctl.clone(),
95+
self.tempdir.take().map(TempDir::into_path).map(|mut path| {
96+
path.push("log");
97+
path.into_boxed_path()
98+
}),
99+
)
100+
}
79101
}
80102

81103
impl Drop for Session {

src/process_impl/session.rs

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub(crate) struct Session {
1515
tempdir: Option<TempDir>,
1616
ctl: Box<Path>,
1717
addr: Box<str>,
18-
master_log: Box<Path>,
18+
master_log: Option<Box<Path>>,
1919
}
2020

2121
impl Session {
@@ -27,7 +27,19 @@ impl Session {
2727
tempdir: Some(tempdir),
2828
ctl,
2929
addr: addr.into(),
30-
master_log: log,
30+
master_log: Some(log),
31+
}
32+
}
33+
34+
pub(crate) fn resume(ctl: Box<Path>, master_log: Option<Box<Path>>) -> Self {
35+
Self {
36+
tempdir: None,
37+
ctl,
38+
// ssh don't care about the addr as long as we have the ctl.
39+
//
40+
// I tested this behavior on OpenSSH_8.9p1 Ubuntu-3, OpenSSL 3.0.2 15 Mar 2022
41+
addr: "none".into(),
42+
master_log,
3143
}
3244
}
3345

@@ -130,13 +142,12 @@ impl Session {
130142
}
131143
}
132144

133-
pub(crate) async fn close(mut self) -> Result<(), Error> {
134-
let mut exit_cmd = self.new_cmd(&["-O", "exit"]);
135-
136-
// Take self.tempdir so that drop would do nothing
137-
let tempdir = self.tempdir.take().unwrap();
138-
139-
let exit = exit_cmd.output().await.map_err(Error::Ssh)?;
145+
async fn close_impl(&self) -> Result<(), Error> {
146+
let exit = self
147+
.new_cmd(&["-O", "exit"])
148+
.output()
149+
.await
150+
.map_err(Error::Ssh)?;
140151

141152
if let Some(master_error) = self.discover_master_error() {
142153
return Err(master_error);
@@ -164,13 +175,29 @@ impl Session {
164175
)));
165176
}
166177

167-
tempdir.close().map_err(Error::Cleanup)?;
178+
Ok(())
179+
}
180+
181+
pub(crate) async fn close(mut self) -> Result<(), Error> {
182+
// Take self.tempdir so that drop would do nothing
183+
if let Some(tempdir) = self.tempdir.take() {
184+
self.close_impl().await?;
185+
186+
tempdir.close().map_err(Error::Cleanup)?;
187+
} else {
188+
self.close_impl().await?;
189+
}
168190

169191
Ok(())
170192
}
171193

194+
pub(crate) fn detach(mut self) -> (Box<Path>, Option<Box<Path>>) {
195+
self.tempdir.take().map(TempDir::into_path);
196+
(self.ctl.clone(), self.master_log.take())
197+
}
198+
172199
fn discover_master_error(&self) -> Option<Error> {
173-
let err = match fs::read_to_string(&self.master_log) {
200+
let err = match fs::read_to_string(self.master_log.as_ref()?) {
174201
Ok(err) => err,
175202
Err(e) => return Some(Error::Master(e)),
176203
};

src/session.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,31 @@ impl From<native_mux_impl::Session> for Session {
6565
// TODO: UserKnownHostsFile for custom known host fingerprint.
6666

6767
impl Session {
68+
/// Resume the connection using path to control socket and
69+
/// path to ssh multiplex output log.
70+
///
71+
/// If you do not use `-E` option (or redirection) to write
72+
/// the log of the ssh multiplex master to the disk, you can
73+
/// simply pass `None` to `master_log`.
74+
///
75+
/// [`Session`] created this way will not be terminated on drop,
76+
/// but can be forced terminated by [`Session::close`].
77+
///
78+
/// This connects to the ssh multiplex master using process mux impl.
79+
#[cfg(feature = "process-mux")]
80+
#[cfg_attr(docsrs, doc(cfg(feature = "process-mux")))]
81+
pub fn resume(ctl: Box<Path>, master_log: Option<Box<Path>>) -> Self {
82+
process_impl::Session::resume(ctl, master_log).into()
83+
}
84+
85+
/// Same as [`Session::resume`] except that it connects to
86+
/// the ssh multiplex master using native mux impl.
87+
#[cfg(feature = "native-mux")]
88+
#[cfg_attr(docsrs, doc(cfg(feature = "native-mux")))]
89+
pub fn resume_mux(ctl: Box<Path>, master_log: Option<Box<Path>>) -> Self {
90+
native_mux_impl::Session::resume(ctl, master_log).into()
91+
}
92+
6893
/// Connect to the host at the given `host` over SSH using process impl, which will
6994
/// spawn a new ssh process for each `Child` created.
7095
///
@@ -292,7 +317,18 @@ impl Session {
292317
}
293318

294319
/// Terminate the remote connection.
320+
///
321+
/// This destructor terminates the ssh multiplex server
322+
/// regardless of how it was created.
295323
pub async fn close(self) -> Result<(), Error> {
296324
delegate!(self.0, imp, { imp.close().await })
297325
}
326+
327+
/// Detach the lifetime of underlying ssh multiplex master
328+
/// from this `Session`.
329+
///
330+
/// Return (path to control socket, path to ssh multiplex output log)
331+
pub fn detach(self) -> (Box<Path>, Option<Box<Path>>) {
332+
delegate!(self.0, imp, { imp.detach() })
333+
}
298334
}

start_sshd.sh

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ chmod 600 .test-key
2828
mkdir -p "$RUNTIME_DIR/openssh-rs/"
2929

3030
echo Waiting for sshd to be up
31-
while ! ssh -i .test-key -v -p 2222 -l test-user $HOSTNAME -o StrictHostKeyChecking=no -o UserKnownHostsFile="$RUNTIME_DIR/openssh-rs/known_hosts" whoami; do
32-
sleep 3
33-
done
31+
timeout 30 ./wait_for_sshd_start_up.sh
32+
33+
# Add the ip to known_hosts file
34+
ssh -i .test-key -v -p 2222 -l test-user $HOSTNAME -o StrictHostKeyChecking=no -o UserKnownHostsFile="$RUNTIME_DIR/openssh-rs/known_hosts" whoami
3435

3536
# Create sshd_started in runtime directory so that it is auto removed on restart.
3637
touch "$RUNTIME_DIR/openssh-rs/sshd_started"

tests/openssh.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,96 @@ async fn local_socket_forward() {
829829
}
830830
}
831831

832+
#[tokio::test]
833+
#[cfg_attr(not(ci), ignore)]
834+
#[cfg(feature = "process-mux")]
835+
async fn test_detach_and_resume_process_mux() {
836+
for session1 in connects().await {
837+
session1.check().await.unwrap();
838+
839+
// First detach
840+
let (ctl1, master_log1) = session1.detach();
841+
842+
// First resume
843+
let session2 = Session::resume(ctl1, master_log1);
844+
session2.check().await.unwrap();
845+
846+
// Second detach to ensure detach handles tempdir
847+
// set to None correctly.
848+
let (ctl2, master_log2) = session2.detach();
849+
850+
// Second resume to ensure close handles tempdir set to None correctly
851+
let session3 = Session::resume(ctl2, master_log2);
852+
session3.check().await.unwrap();
853+
854+
session3.close().await.unwrap();
855+
}
856+
857+
// test close
858+
for session1 in connects().await {
859+
session1.check().await.unwrap();
860+
861+
let (ctl1, master_log1) = session1.detach();
862+
863+
let ctl = ctl1.clone();
864+
865+
let session2 = Session::resume(ctl1, master_log1);
866+
session2.check().await.unwrap();
867+
868+
session2.close().await.unwrap();
869+
870+
// Wait for ssh multiplex master to clean up and exit.
871+
sleep(Duration::from_secs(3)).await;
872+
873+
assert!(!ctl.exists());
874+
}
875+
}
876+
877+
#[tokio::test]
878+
#[cfg_attr(not(ci), ignore)]
879+
#[cfg(feature = "native-mux")]
880+
async fn test_detach_and_resume_native_mux() {
881+
for session1 in connects().await {
882+
session1.check().await.unwrap();
883+
884+
// First detach
885+
let (ctl1, master_log1) = session1.detach();
886+
887+
// First resume_mux
888+
let session2 = Session::resume_mux(ctl1, master_log1);
889+
session2.check().await.unwrap();
890+
891+
// Second detach to ensure detach handles tempdir
892+
// set to None correctly.
893+
let (ctl2, master_log2) = session2.detach();
894+
895+
// Second resume_mux to ensure close handles tempdir set to None correctly
896+
let session3 = Session::resume_mux(ctl2, master_log2);
897+
session3.check().await.unwrap();
898+
899+
session3.close().await.unwrap();
900+
}
901+
902+
// test close
903+
for session1 in connects().await {
904+
session1.check().await.unwrap();
905+
906+
let (ctl1, master_log1) = session1.detach();
907+
908+
let ctl = ctl1.clone();
909+
910+
let session2 = Session::resume_mux(ctl1, master_log1);
911+
session2.check().await.unwrap();
912+
913+
session2.close().await.unwrap();
914+
915+
// Wait for ssh multiplex master to clean up and exit.
916+
sleep(Duration::from_secs(3)).await;
917+
918+
assert!(!ctl.exists());
919+
}
920+
}
921+
832922
#[tokio::test]
833923
#[cfg_attr(not(ci), ignore)]
834924
async fn test_sftp_subsystem() {

0 commit comments

Comments
 (0)