use std::io::{self, Read, Write};
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
use super::codec::{Frame, MuxError, PROTOCOL_VERSION};
use super::{read_frame, write_frame};
#[derive(Debug, PartialEq, Eq)]
pub enum ProbeOutcome {
Live,
Absent,
Stale,
}
#[derive(Clone, Debug)]
pub struct SessionRequest {
pub want_pty: bool,
pub term: String,
pub cols: u32,
pub rows: u32,
pub env: Vec<(String, String)>,
pub command: Option<String>,
}
pub fn open_forward(
path: &Path,
dest_host: &str,
dest_port: u16,
orig_host: &str,
orig_port: u16,
) -> Result<UnixStream, MuxError> {
let mut sock = UnixStream::connect(path).map_err(MuxError::Io)?;
write_frame(
&mut sock,
&Frame::Hello {
version: PROTOCOL_VERSION,
},
)?;
match read_frame(&mut sock)? {
Some(Frame::Hello { version }) if version == PROTOCOL_VERSION => {}
Some(Frame::Hello { version }) => {
return Err(MuxError::VersionMismatch {
ours: PROTOCOL_VERSION,
theirs: version,
});
}
_ => return Err(MuxError::Unexpected("expected HELLO from master")),
}
write_frame(
&mut sock,
&Frame::OpenDirectTcpip {
dest_host: dest_host.to_string(),
dest_port: dest_port as u32,
orig_host: orig_host.to_string(),
orig_port: orig_port as u32,
},
)?;
match read_frame(&mut sock)? {
Some(Frame::OpenOk) => Ok(sock),
Some(Frame::OpenFail { reason }) => Err(MuxError::ForwardFailed(reason)),
_ => Err(MuxError::Unexpected(
"expected OPEN_OK/OPEN_FAIL from master",
)),
}
}
pub fn splice_forward<S>(sock: UnixStream, local: S) -> Result<(), MuxError>
where
S: Read + Write + TryCloneStream + Send + 'static,
{
let local_read = local.try_clone_stream().map_err(MuxError::Io)?;
let mut local_write = local;
let sock_read = sock.try_clone().map_err(MuxError::Io)?;
let sock_write = Arc::new(std::sync::Mutex::new(sock));
let up_write = sock_write.clone();
let t_up = thread::spawn(move || {
let mut local_read = local_read;
let mut buf = [0u8; 32 * 1024];
loop {
match local_read.read(&mut buf) {
Ok(0) => break,
Ok(n) => {
let mut g = match up_write.lock() {
Ok(g) => g,
Err(_) => break,
};
if write_frame(&mut *g, &Frame::StdinData(buf[..n].to_vec())).is_err() {
break;
}
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(_) => break,
}
}
if let Ok(mut g) = up_write.lock() {
let _ = write_frame(&mut *g, &Frame::Eof);
}
});
let mut sock_read = sock_read;
loop {
match read_frame(&mut sock_read) {
Ok(Some(Frame::StdoutData(d))) => {
if local_write.write_all(&d).is_err() {
break;
}
let _ = local_write.flush();
}
Ok(Some(Frame::Eof)) | Ok(None) => break,
Ok(Some(_)) => { }
Err(_) => break,
}
}
if let Ok(g) = sock_write.lock() {
let _ = g.shutdown(std::net::Shutdown::Both);
}
let _ = t_up.join();
Ok(())
}
pub trait TryCloneStream {
fn try_clone_stream(&self) -> io::Result<Self>
where
Self: Sized;
}
impl TryCloneStream for std::net::TcpStream {
fn try_clone_stream(&self) -> io::Result<Self> {
self.try_clone()
}
}
pub fn probe_master(path: &Path) -> ProbeOutcome {
if !path.exists() {
return ProbeOutcome::Absent;
}
match UnixStream::connect(path) {
Ok(mut sock) => {
let _ = sock.set_read_timeout(Some(Duration::from_millis(500)));
let _ = sock.set_write_timeout(Some(Duration::from_millis(500)));
if write_frame(
&mut sock,
&Frame::Hello {
version: PROTOCOL_VERSION,
},
)
.is_err()
{
return ProbeOutcome::Stale;
}
match read_frame(&mut sock) {
Ok(Some(Frame::Hello { version })) if version == PROTOCOL_VERSION => {
ProbeOutcome::Live
}
_ => ProbeOutcome::Stale,
}
}
Err(_) => ProbeOutcome::Stale,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ControlCommand {
Check,
Exit,
}
pub fn send_control_command(path: &Path, cmd: ControlCommand) -> Result<bool, MuxError> {
let mut sock = match UnixStream::connect(path) {
Ok(s) => s,
Err(_) if cmd == ControlCommand::Check => return Ok(false),
Err(e) => return Err(MuxError::Io(e)),
};
let _ = sock.set_read_timeout(Some(Duration::from_millis(2000)));
let _ = sock.set_write_timeout(Some(Duration::from_millis(2000)));
write_frame(
&mut sock,
&Frame::Hello {
version: PROTOCOL_VERSION,
},
)?;
match read_frame(&mut sock) {
Ok(Some(Frame::Hello { version })) if version == PROTOCOL_VERSION => {}
_ if cmd == ControlCommand::Check => return Ok(false),
Ok(Some(Frame::Hello { version })) => {
return Err(MuxError::VersionMismatch {
ours: PROTOCOL_VERSION,
theirs: version,
});
}
Ok(_) => return Err(MuxError::Unexpected("expected HELLO from master")),
Err(e) => return Err(e),
}
match cmd {
ControlCommand::Check => {
write_frame(&mut sock, &Frame::AliveCheck)?;
match read_frame(&mut sock) {
Ok(Some(Frame::AliveOk)) => Ok(true),
_ => Ok(false),
}
}
ControlCommand::Exit => {
write_frame(&mut sock, &Frame::ExitRequest)?;
let _ = read_frame(&mut sock);
Ok(true)
}
}
}
pub fn run_client(
path: &Path,
req: &SessionRequest,
resize: Option<Arc<dyn Fn() -> (u32, u32) + Send + Sync>>,
) -> Result<i32, MuxError> {
let mut sock = UnixStream::connect(path).map_err(MuxError::Io)?;
write_frame(
&mut sock,
&Frame::Hello {
version: PROTOCOL_VERSION,
},
)?;
match read_frame(&mut sock)? {
Some(Frame::Hello { version }) if version == PROTOCOL_VERSION => {}
Some(Frame::Hello { version }) => {
return Err(MuxError::VersionMismatch {
ours: PROTOCOL_VERSION,
theirs: version,
});
}
_ => return Err(MuxError::Unexpected("expected HELLO from master")),
}
write_frame(
&mut sock,
&Frame::OpenSession {
want_pty: req.want_pty,
term: req.term.clone(),
cols: req.cols,
rows: req.rows,
env: req.env.clone(),
command: req.command.clone(),
},
)?;
let done = Arc::new(AtomicBool::new(false));
let write_half = Arc::new(std::sync::Mutex::new(
sock.try_clone().map_err(MuxError::Io)?,
));
let in_write = write_half.clone();
let in_done = done.clone();
let t_in = thread::spawn(move || {
let mut buf = [0u8; 32 * 1024];
let mut stdin = io::stdin();
loop {
if in_done.load(Ordering::Relaxed) {
break;
}
match stdin.read(&mut buf) {
Ok(0) => break,
Ok(n) => {
let mut g = match in_write.lock() {
Ok(g) => g,
Err(_) => break,
};
if write_frame(&mut *g, &Frame::StdinData(buf[..n].to_vec())).is_err() {
break;
}
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(_) => break,
}
}
if let Ok(mut g) = in_write.lock() {
let _ = write_frame(&mut *g, &Frame::Eof);
}
});
let t_winch = resize.map(|get| {
let w = write_half.clone();
let stop = done.clone();
thread::spawn(move || {
let mut last = get();
while !stop.load(Ordering::Relaxed) {
thread::sleep(Duration::from_millis(100));
let cur = get();
if cur != last {
last = cur;
if let Ok(mut g) = w.lock() {
let _ = write_frame(
&mut *g,
&Frame::WindowChange {
cols: cur.0,
rows: cur.1,
},
);
}
}
}
})
});
let mut exit_code: i32 = 255;
let mut stdout = io::stdout();
let mut stderr = io::stderr();
loop {
match read_frame(&mut sock) {
Ok(Some(Frame::StdoutData(d))) => {
if stdout.write_all(&d).is_err() {
break;
}
let _ = stdout.flush();
}
Ok(Some(Frame::StderrData(d))) => {
if stderr.write_all(&d).is_err() {
break;
}
let _ = stderr.flush();
}
Ok(Some(Frame::ExitStatus { code })) => {
exit_code = code as i32;
}
Ok(Some(Frame::ExitSignal { name })) => {
let _ = writeln!(stderr, "\r\nsession terminated by signal: {name}");
exit_code = 255;
}
Ok(Some(Frame::Eof)) => {
break;
}
Ok(Some(Frame::AliveOk)) => { }
Ok(Some(_)) => { }
Ok(None) => break, Err(_) => break,
}
}
done.store(true, Ordering::Relaxed);
let _ = sock.shutdown(std::net::Shutdown::Both);
drop(t_in);
if let Some(h) = t_winch {
drop(h);
}
Ok(exit_code)
}