Skip to content

Commit dc0bc29

Browse files
committed
Bump rust edition to 2024 and refactor code
1 parent f58f1ec commit dc0bc29

5 files changed

Lines changed: 103 additions & 95 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "sshping"
33
version = "0.2.2"
4-
edition = "2021"
4+
edition = "2024"
55
description = "SSH-based ping that measures interactive character echo latency and file transfer throughput. Pronounced \"shipping\"."
66
authors = ["Nan Huang <teddyhuangnan@gmail.com>"]
77
license = "MIT"
@@ -28,12 +28,12 @@ rand = "0.9.0"
2828
regex = "1.11.1"
2929
russh = "0.54.6"
3030
russh-sftp = "2.1.1"
31-
ssh2-config = "0.6.0"
3231
serde = { version = "1.0.217", features = ["derive"] }
3332
serde_json = "1.0.138"
3433
shellexpand = "3.1.0"
3534
simple_logger = "5.0.0"
3635
size = "0.5.0"
36+
ssh2-config = "0.6.0"
3737
tabled = "0.20.0"
3838
tokio = { version = "1.42.0", features = ["full"] }
3939
whoami = "1.5.1"

src/auth.rs

Lines changed: 65 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,57 @@ use std::{
55
};
66

77
use log::{info, warn};
8-
use russh::client;
9-
use russh::keys::{decode_secret_key, PrivateKeyWithHashAlg};
8+
use russh::{
9+
client,
10+
keys::{decode_secret_key, PrivateKeyWithHashAlg},
11+
};
12+
13+
async fn authenticate_publickey<H: client::Handler>(
14+
session: &mut client::Handle<H>,
15+
user: &str,
16+
identity: &PathBuf,
17+
timeout: f64,
18+
) -> Result<(), String> {
19+
let key_content = std::fs::read_to_string(identity)
20+
.map_err(|e| format!("Failed to read identity file: {e}"))?;
21+
let key = decode_secret_key(&key_content, None)
22+
.map_err(|e| format!("Failed to decode secret key: {e}"))?;
23+
let timeout_result = tokio::time::timeout(
24+
Duration::from_secs_f64(timeout),
25+
session.authenticate_publickey(user, PrivateKeyWithHashAlg::new(Arc::new(key), None)),
26+
)
27+
.await
28+
.map_err(|_| format!("Public key authentication timed out after {timeout} seconds"))?;
29+
let auth_result =
30+
timeout_result.map_err(|e| format!("Public key authentication failed: {e}"))?;
31+
if !auth_result.success() {
32+
return Err("Public key authentication returned false".to_string());
33+
}
34+
35+
info!("Public key authentication succeeded");
36+
Ok(())
37+
}
38+
39+
async fn authenticate_password<H: client::Handler>(
40+
session: &mut client::Handle<H>,
41+
user: &str,
42+
password: &str,
43+
timeout: f64,
44+
) -> Result<(), String> {
45+
let timeout_result = tokio::time::timeout(
46+
Duration::from_secs_f64(timeout),
47+
session.authenticate_password(user, password),
48+
)
49+
.await
50+
.map_err(|_| format!("Password authentication timed out after {timeout} seconds"))?;
51+
let auth_result = timeout_result.map_err(|e| format!("Password authentication failed: {e}"))?;
52+
if !auth_result.success() {
53+
return Err("Password authentication returned false".to_string());
54+
}
55+
56+
info!("Password authentication succeeded");
57+
Ok(())
58+
}
1059

1160
pub async fn authenticate_all<H: client::Handler>(
1261
session: &mut client::Handle<H>,
@@ -18,58 +67,23 @@ pub async fn authenticate_all<H: client::Handler>(
1867
let start = Instant::now();
1968

2069
// Try public key authentication if identity file is provided
21-
if let Some(identity_path) = identity {
22-
match std::fs::read_to_string(identity_path) {
23-
Ok(key_content) => {
24-
match decode_secret_key(&key_content, password) {
25-
Ok(key) => {
26-
match tokio::time::timeout(
27-
Duration::from_secs_f64(timeout),
28-
session.authenticate_publickey(
29-
user,
30-
PrivateKeyWithHashAlg::new(Arc::new(key), None),
31-
),
32-
)
33-
.await
34-
{
35-
Ok(Ok(auth_result)) => {
36-
if auth_result.success() {
37-
info!("Public key authentication succeeded");
38-
return Ok(start.elapsed());
39-
} else {
40-
warn!("Public key authentication returned false");
41-
}
42-
}
43-
Ok(Err(e)) => warn!("Public key authentication failed: {e}"),
44-
Err(_) => warn!("Public key authentication timed out"),
45-
}
46-
}
47-
Err(e) => warn!("Failed to decode secret key: {e}"),
48-
}
49-
}
50-
Err(e) => warn!("Failed to read identity file: {e}"),
51-
}
70+
if let Some(identity_path) = identity
71+
&& authenticate_publickey(session, user, identity_path, timeout)
72+
.await
73+
.inspect_err(|e| warn!("{e}"))
74+
.is_ok()
75+
{
76+
return Ok(start.elapsed());
5277
}
5378

5479
// Try password authentication
55-
if let Some(pwd) = password {
56-
match tokio::time::timeout(
57-
Duration::from_secs_f64(timeout),
58-
session.authenticate_password(user, pwd),
59-
)
60-
.await
61-
{
62-
Ok(Ok(auth_result)) => {
63-
if auth_result.success() {
64-
info!("Password authentication succeeded");
65-
return Ok(start.elapsed());
66-
} else {
67-
warn!("Password authentication returned false");
68-
}
69-
}
70-
Ok(Err(e)) => warn!("Password authentication failed: {e}"),
71-
Err(_) => warn!("Password authentication timed out"),
72-
}
80+
if let Some(pwd) = password
81+
&& authenticate_password(session, user, pwd, timeout)
82+
.await
83+
.inspect_err(|e| warn!("{e}"))
84+
.is_ok()
85+
{
86+
return Ok(start.elapsed());
7387
}
7488

7589
// Fails if all authentication methods fail

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ async fn main() -> ExitCode {
9999
..Default::default()
100100
});
101101
let handler = SshHandler;
102-
102+
103103
let addr = (opts.target.host.as_str(), opts.target.port);
104104
let mut session = match tokio::time::timeout(
105105
std::time::Duration::from_secs_f64(opts.ssh_timeout),

src/tests.rs

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ use rand::{
99
distr::{Distribution, Uniform},
1010
rng,
1111
};
12-
use russh::client;
13-
use russh::ChannelMsg;
12+
use russh::{client, ChannelMsg};
1413
use russh_sftp::client::SftpSession;
1514
use tokio::io::{AsyncReadExt, AsyncWriteExt};
1615

@@ -45,33 +44,33 @@ pub async fn run_echo_test<H: client::Handler>(
4544
debug!("Running echo test with command: {echo_cmd:?}");
4645
debug!("Number of characters to echo: {char_count:?}");
4746
debug!("Time limit for echo: {time_limit:?} seconds");
48-
47+
4948
// Start the channel server
5049
trace!("Preparing channel session");
5150
let mut channel = session
5251
.channel_open_session()
5352
.await
5453
.map_err(|e| e.to_string())?;
55-
54+
5655
// Request a pseudo-terminal for the interactive shell
5756
channel
5857
.request_pty(true, "sshping", 10, 5, 0, 0, &[])
5958
.await
6059
.map_err(|e| e.to_string())?;
61-
60+
6261
channel
6362
.request_shell(false)
6463
.await
6564
.map_err(|e| e.to_string())?;
66-
65+
6766
// Send the echo command to accept input
6867
trace!("Starting echo command");
6968
let echo_cmd_bytes = format!("{echo_cmd}\n").into_bytes();
7069
channel
7170
.data(&echo_cmd_bytes[..])
7271
.await
7372
.map_err(|e| e.to_string())?;
74-
73+
7574
// Read the initial buffer to clear the echo command
7675
tokio::time::sleep(Duration::from_millis(100)).await;
7776
while let Some(msg) = channel.wait().await {
@@ -93,14 +92,11 @@ pub async fn run_echo_test<H: client::Handler>(
9392

9493
for (n, idx) in (0..char_count).zip((0..write_buffer.len()).cycle()) {
9594
let start = Instant::now();
96-
95+
9796
// Send one character
9897
let byte_slice = &write_buffer[idx..idx + 1];
99-
channel
100-
.data(byte_slice)
101-
.await
102-
.map_err(|e| e.to_string())?;
103-
98+
channel.data(byte_slice).await.map_err(|e| e.to_string())?;
99+
104100
// Wait for echo back
105101
loop {
106102
if let Some(msg) = channel.wait().await {
@@ -117,14 +113,14 @@ pub async fn run_echo_test<H: client::Handler>(
117113
}
118114
}
119115
}
120-
116+
121117
let latency = start.elapsed().as_nanos();
122118
latencies.push(latency);
123-
124-
if let Some(timeout) = timeout {
125-
if start_time.elapsed() > timeout {
126-
break;
127-
}
119+
120+
if let Some(timeout) = timeout
121+
&& start_time.elapsed() > timeout
122+
{
123+
break;
128124
}
129125
progress_bar.set_position((n as u64) + 1);
130126
}
@@ -189,7 +185,7 @@ async fn run_upload_test<H: client::Handler>(
189185
formatter: &Formatter,
190186
) -> Result<SpeedTestResult, String> {
191187
info!("Running upload speed test");
192-
188+
193189
// Establish SFTP channel
194190
trace!("Establishing SFTP channel");
195191
let channel = session
@@ -203,7 +199,7 @@ async fn run_upload_test<H: client::Handler>(
203199
let sftp = SftpSession::new(channel.into_stream())
204200
.await
205201
.map_err(|e| e.to_string())?;
206-
202+
207203
// Generate random data to upload
208204
trace!("Generating random data");
209205
let dist = Uniform::try_from(0..128_u8).unwrap();
@@ -212,14 +208,11 @@ async fn run_upload_test<H: client::Handler>(
212208
.take(size as usize)
213209
.map(|v| (v & 0x3f) + 32)
214210
.collect();
215-
211+
216212
// Open remote file for writing
217213
let remote_path = remote_file.to_str().ok_or("Invalid remote file path")?;
218-
let mut file = sftp
219-
.create(remote_path)
220-
.await
221-
.map_err(|e| e.to_string())?;
222-
214+
let mut file = sftp.create(remote_path).await.map_err(|e| e.to_string())?;
215+
223216
// Preparing logging variables
224217
let mut total_bytes_sent = 0;
225218
let start_time: Instant = Instant::now();
@@ -234,7 +227,7 @@ async fn run_upload_test<H: client::Handler>(
234227
progress_bar.set_position(total_bytes_sent as u64);
235228
}
236229
progress_bar.finish_and_clear();
237-
230+
238231
// Close the file
239232
file.shutdown().await.map_err(|e| e.to_string())?;
240233

@@ -254,7 +247,7 @@ async fn run_download_test<H: client::Handler>(
254247
formatter: &Formatter,
255248
) -> Result<SpeedTestResult, String> {
256249
info!("Running download speed test");
257-
250+
258251
// Establish SFTP channel
259252
trace!("Establishing SFTP channel");
260253
let channel = session
@@ -268,25 +261,22 @@ async fn run_download_test<H: client::Handler>(
268261
let sftp = SftpSession::new(channel.into_stream())
269262
.await
270263
.map_err(|e| e.to_string())?;
271-
264+
272265
// Get file size
273266
let remote_path = remote_file.to_str().ok_or("Invalid remote file path")?;
274267
let metadata = sftp
275268
.metadata(remote_path)
276269
.await
277270
.map_err(|e| e.to_string())?;
278271
let size = metadata.len();
279-
272+
280273
if size == 0 {
281274
return Err("Remote file is empty".to_string());
282275
}
283-
276+
284277
// Open remote file for reading
285-
let mut file = sftp
286-
.open(remote_path)
287-
.await
288-
.map_err(|e| e.to_string())?;
289-
278+
let mut file = sftp.open(remote_path).await.map_err(|e| e.to_string())?;
279+
290280
// Prepare buffer for downloading
291281
trace!("Preparing buffer for downloading");
292282
let mut buffer = vec![0; chunk_size as usize];
@@ -299,13 +289,17 @@ async fn run_download_test<H: client::Handler>(
299289
// Starting downloading file
300290
trace!("Receiving file in chunks");
301291
while size - total_bytes_recv > chunk_size {
302-
file.read_exact(&mut buffer).await.map_err(|e| e.to_string())?;
292+
file.read_exact(&mut buffer)
293+
.await
294+
.map_err(|e| e.to_string())?;
303295
total_bytes_recv += chunk_size;
304296
progress_bar.set_position(total_bytes_recv);
305297
}
306298
if size - total_bytes_recv > 0 {
307299
let mut remaining = vec![0; (size - total_bytes_recv) as usize];
308-
file.read_exact(&mut remaining).await.map_err(|e| e.to_string())?;
300+
file.read_exact(&mut remaining)
301+
.await
302+
.map_err(|e| e.to_string())?;
309303
total_bytes_recv += remaining.len() as u64;
310304
progress_bar.set_position(total_bytes_recv);
311305
}

0 commit comments

Comments
 (0)