1
0
Fork 0
mirror of https://gitlab.redox-os.org/CoffeeCode/redox-ssh.git synced 2025-12-28 17:02:19 +01:00

Async IO Hack

This commit is contained in:
Thomas Gatzweiler 2017-07-22 21:58:40 +02:00
parent 46e4bfab79
commit 9480693a10
11 changed files with 325 additions and 181 deletions

View file

@ -2,6 +2,7 @@ extern crate ssh;
extern crate log; extern crate log;
use std::env; use std::env;
use std::error::Error;
use std::fs::File; use std::fs::File;
use std::io::{self, Write}; use std::io::{self, Write};
use std::process; use std::process;

View file

@ -1,25 +1,24 @@
use std::fs::{File, OpenOptions}; use std::fs::OpenOptions;
use std::io::{self, Read, Write}; use std::io;
use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd}; use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::os::unix::process::CommandExt; use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::process::{self, Stdio}; use std::process::{self, Stdio};
use std::thread::{self, JoinHandle}; use std::sync::mpsc;
use sys; use sys;
use connection::ConnectionEvent;
pub type ChannelId = u32; pub type ChannelId = u32;
#[derive(Debug)]
pub struct Channel { pub struct Channel {
id: ChannelId, id: ChannelId,
peer_id: ChannelId, peer_id: ChannelId,
process: Option<process::Child>, process: Option<process::Child>,
pty: Option<(RawFd, PathBuf)>, pty: Option<sys::Pty>,
master: Option<File>,
window_size: u32, window_size: u32,
peer_window_size: u32, peer_window_size: u32,
max_packet_size: u32, max_packet_size: u32,
read_thread: Option<JoinHandle<()>>, events: mpsc::Sender<ConnectionEvent>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -38,18 +37,17 @@ pub enum ChannelRequest {
impl Channel { impl Channel {
pub fn new( pub fn new(
id: ChannelId, peer_id: ChannelId, peer_window_size: u32, id: ChannelId, peer_id: ChannelId, peer_window_size: u32,
max_packet_size: u32 max_packet_size: u32, events: mpsc::Sender<ConnectionEvent>
) -> Channel { ) -> Channel {
Channel { Channel {
id: id, id: id,
peer_id: peer_id, peer_id: peer_id,
process: None, process: None,
master: None,
pty: None, pty: None,
window_size: peer_window_size, window_size: peer_window_size,
peer_window_size: peer_window_size, peer_window_size: peer_window_size,
max_packet_size: max_packet_size, max_packet_size: max_packet_size,
read_thread: None, events: events,
} }
} }
@ -57,6 +55,10 @@ impl Channel {
self.id self.id
} }
pub fn peer_id(&self) -> ChannelId {
self.peer_id
}
pub fn window_size(&self) -> u32 { pub fn window_size(&self) -> u32 {
self.window_size self.window_size
} }
@ -75,83 +77,78 @@ impl Channel {
pixel_height, pixel_height,
.. ..
} => { } => {
let (master_fd, tty_path) = sys::getpty(); if let Ok(mut pty) = sys::Pty::get() {
pty.set_winsize(chars, rows, pixel_width, pixel_height);
sys::set_winsize( let events = self.events.clone();
master_fd, let id = self.id;
chars,
rows,
pixel_width,
pixel_height,
);
self.read_thread = Some(thread::spawn(move || { pty.subscribe(move || {
use libc::dup; events.send(ConnectionEvent::ChannelData(id)).map_err(
let master2 = unsafe { dup(master_fd) }; |_| (),
)
});
println!("dup result: {}", dup as u32); self.pty = Some(pty);
let mut master = unsafe { File::from_raw_fd(master2) }; }
loop {
use std::str::from_utf8_unchecked;
let mut buf = [0; 4096];
let count = master.read(&mut buf).unwrap();
if count == 0 {
break;
}
println!("Read: {}", unsafe {
from_utf8_unchecked(&buf[0..count])
});
}
println!("Quitting read thread.");
}));
self.pty = Some((master_fd, tty_path));
self.master = Some(unsafe { File::from_raw_fd(master_fd) });
} }
ChannelRequest::Shell => { ChannelRequest::Shell => {
if let Some(&(_, ref tty_path)) = self.pty.as_ref() { if let Some(ref pty) = self.pty {
let stdin = OpenOptions::new() let stdin = OpenOptions::new()
.read(true) .read(true)
.write(true) .write(true)
.open(&tty_path) .open(pty.path())
.unwrap() .unwrap()
.into_raw_fd(); .into_raw_fd();
let stdout = OpenOptions::new() let stdout = OpenOptions::new()
.read(true) .read(true)
.write(true) .write(true)
.open(&tty_path) .open(pty.path())
.unwrap() .unwrap()
.into_raw_fd(); .into_raw_fd();
let stderr = OpenOptions::new() let stderr = OpenOptions::new()
.read(true) .read(true)
.write(true) .write(true)
.open(&tty_path) .open(pty.path())
.unwrap() .unwrap()
.into_raw_fd(); .into_raw_fd();
process::Command::new("login") self.process = Some(
.stdin(unsafe { Stdio::from_raw_fd(stdin) }) process::Command::new("login")
.stdout(unsafe { Stdio::from_raw_fd(stdout) }) .stdin(unsafe { Stdio::from_raw_fd(stdin) })
.stderr(unsafe { Stdio::from_raw_fd(stderr) }) .stdout(unsafe { Stdio::from_raw_fd(stdout) })
.before_exec(|| sys::before_exec()) .stderr(unsafe { Stdio::from_raw_fd(stderr) })
.spawn() .before_exec(|| sys::before_exec())
.unwrap(); .spawn()
.unwrap(),
);
} }
} }
} }
debug!("Channel Request: {:?}", request); debug!("Channel Request: {:?}", request);
} }
pub fn data(&mut self, data: &[u8]) -> io::Result<()> { pub fn write(&mut self, data: &[u8]) -> io::Result<()> {
if let Some(ref mut master) = self.master { match self.pty
master.write_all(data)?; {
master.flush() Some(ref mut pty) => pty.write(data),
_ => Ok(()),
} }
else { }
Ok(())
pub fn read(&mut self, data: &mut [u8]) -> io::Result<usize> {
match self.pty
{
Some(ref mut pty) => pty.read(data),
_ => Ok(0),
} }
} }
} }
impl Drop for Channel {
fn drop(&mut self) {
self.process.take().map(|mut p| p.kill());
}
}

View file

@ -1,6 +1,7 @@
use std::collections::{BTreeMap, VecDeque}; use std::collections::{BTreeMap, VecDeque};
use std::io::{self, BufReader, Read, Write}; use std::io::{self, BufReader, Read, Write};
use std::sync::Arc; use std::sync::Arc;
use std::sync::mpsc;
use channel::{Channel, ChannelId, ChannelRequest}; use channel::{Channel, ChannelId, ChannelRequest};
use encryption::{AesCtr, Decryptor, Encryption}; use encryption::{AesCtr, Decryptor, Encryption};
@ -18,6 +19,11 @@ enum ConnectionState {
Established, Established,
} }
pub enum ConnectionEvent {
ChannelData(ChannelId),
StreamData,
}
#[derive(Clone)] #[derive(Clone)]
pub enum ConnectionType { pub enum ConnectionType {
Server(Arc<ServerConfig>), Server(Arc<ServerConfig>),
@ -42,10 +48,14 @@ pub struct Connection {
seq: (u32, u32), seq: (u32, u32),
tx_queue: VecDeque<Packet>, tx_queue: VecDeque<Packet>,
channels: BTreeMap<ChannelId, Channel>, channels: BTreeMap<ChannelId, Channel>,
pub events_tx: mpsc::Sender<ConnectionEvent>,
events_rx: mpsc::Receiver<ConnectionEvent>,
} }
impl<'a> Connection { impl<'a> Connection {
pub fn new(conn_type: ConnectionType) -> Connection { pub fn new(conn_type: ConnectionType) -> Connection {
let (events_tx, events_rx) = mpsc::channel();
Connection { Connection {
conn_type: conn_type, conn_type: conn_type,
hash_data: HashData::default(), hash_data: HashData::default(),
@ -57,6 +67,8 @@ impl<'a> Connection {
seq: (0, 0), seq: (0, 0),
tx_queue: VecDeque::new(), tx_queue: VecDeque::new(),
channels: BTreeMap::new(), channels: BTreeMap::new(),
events_rx: events_rx,
events_tx: events_tx,
} }
} }
@ -67,23 +79,43 @@ impl<'a> Connection {
let mut reader = BufReader::new(stream); let mut reader = BufReader::new(stream);
loop { loop {
let packet = self.recv(&mut reader)?; match self.events_rx.recv()
let response = self.process(packet)?; {
Ok(ConnectionEvent::ChannelData(id)) => {
if let Some(ref mut channel) = self.channels.get_mut(&id) {
let mut buf = [0; 4096];
let count = channel.read(&mut buf)?;
if count > 0 {
let mut res = Packet::new(MessageType::ChannelData);
res.write_uint32(channel.peer_id())?;
res.write_bytes(&buf[..count])?;
}
}
}
Ok(ConnectionEvent::StreamData) => {
let packet = self.recv(&mut reader)?;
let response = self.process(packet)?;
let mut stream = reader.get_mut(); let mut stream = reader.get_mut();
if let Some(packet) = response { if let Some(packet) = response {
self.send(&mut stream, packet)?; self.send(&mut stream, packet)?;
}
}
Err(_) => {}
} }
// Send additional packets from the queue // Send additional packets from the queue
let mut packets: Vec<Packet> = self.tx_queue.drain(..).collect(); let mut packets: Vec<Packet> = self.tx_queue.drain(..).collect();
let mut stream = reader.get_mut();
for packet in packets.drain(..) { for packet in packets.drain(..) {
self.send(&mut stream, packet)?; self.send(&mut stream, packet)?;
} }
} }
} }
fn recv(&mut self, mut stream: &mut Read) -> Result<Packet> { fn recv(&mut self, mut stream: &mut Read) -> Result<Packet> {
let packet = if let Some((ref mut c2s, _)) = self.encryption { let packet = if let Some((ref mut c2s, _)) = self.encryption {
let mut decryptor = Decryptor::new(&mut **c2s, &mut stream); let mut decryptor = Decryptor::new(&mut **c2s, &mut stream);
@ -296,7 +328,13 @@ impl<'a> Connection {
0 0
}; };
let channel = Channel::new(id, peer_id, window_size, max_packet_size); let channel = Channel::new(
id,
peer_id,
window_size,
max_packet_size,
self.events_tx.clone(),
);
let mut res = Packet::new(MessageType::ChannelOpenConfirmation); let mut res = Packet::new(MessageType::ChannelOpenConfirmation);
res.write_uint32(peer_id)?; res.write_uint32(peer_id)?;
@ -304,7 +342,7 @@ impl<'a> Connection {
res.write_uint32(channel.window_size())?; res.write_uint32(channel.window_size())?;
res.write_uint32(channel.max_packet_size())?; res.write_uint32(channel.max_packet_size())?;
debug!("Open {:?}", channel); debug!("Open Channel {}", id);
self.channels.insert(id, channel); self.channels.insert(id, channel);
@ -357,7 +395,7 @@ impl<'a> Connection {
let data = reader.read_string()?; let data = reader.read_string()?;
let mut channel = self.channels.get_mut(&channel_id).unwrap(); let mut channel = self.channels.get_mut(&channel_id).unwrap();
channel.data(data.as_slice())?; channel.write(data.as_slice())?;
Ok(None) Ok(None)
} }

View file

@ -9,8 +9,6 @@ extern crate syscall;
#[cfg(not(target_os = "redox"))] #[cfg(not(target_os = "redox"))]
extern crate libc; extern crate libc;
mod error;
mod algorithm;
mod packet; mod packet;
mod message; mod message;
mod connection; mod connection;
@ -19,15 +17,17 @@ mod encryption;
mod mac; mod mac;
mod channel; mod channel;
pub mod error;
pub mod algorithm;
pub mod public_key; pub mod public_key;
pub mod server; pub mod server;
pub use self::server::{Server, ServerConfig};
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
#[path = "sys/redox.rs"] #[path = "sys/redox/mod.rs"]
pub mod sys; pub mod sys;
#[cfg(not(target_os = "redox"))] #[cfg(not(target_os = "redox"))]
#[path = "sys/unix.rs"] #[path = "sys/linux/mod.rs"]
pub mod sys; pub mod sys;
pub use self::server::{Server, ServerConfig};

View file

@ -1,10 +1,12 @@
use std::io; use std::io;
use std::net::TcpListener; use std::net::TcpListener;
use std::os::unix::io::AsRawFd;
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use connection::{Connection, ConnectionType}; use connection::{Connection, ConnectionEvent, ConnectionType};
use public_key::KeyPair; use public_key::KeyPair;
use sys;
pub struct ServerConfig { pub struct ServerConfig {
pub host: String, pub host: String,
@ -38,7 +40,7 @@ impl Server {
let result = connection.run(&mut stream); let result = connection.run(&mut stream);
if let Some(error) = result.err() { if let Some(error) = result.err() {
println!("sshd: {}", error) println!("sshd: {}", error);
} }
}); });
} }

19
src/sys/linux/mod.rs Normal file
View file

@ -0,0 +1,19 @@
use std::io::Result;
use std::os::unix::io::RawFd;
mod pty;
pub use self::pty::Pty;
pub fn before_exec() -> Result<()> {
use libc;
unsafe {
libc::setsid();
libc::ioctl(0, libc::TIOCSCTTY, 1);
}
Ok(())
}
pub fn fork() -> usize {
use libc;
unsafe { libc::fork() as usize }
}

160
src/sys/linux/pty.rs Normal file
View file

@ -0,0 +1,160 @@
use libc;
use std::ffi::CStr;
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Write};
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
use std::path::PathBuf;
use std::sync::mpsc;
use std::thread::{self, JoinHandle};
pub struct Pty {
master: File,
path: PathBuf,
sub_thread: Option<JoinHandle<()>>,
sub_thread_tx: Option<mpsc::Sender<ThreadCommand>>,
}
enum ThreadCommand {
WaitForData,
Stop,
}
impl Pty {
pub fn get() -> Result<Pty, ()> {
const TIOCPKT: libc::c_ulong = 0x5420;
let master_fd = OpenOptions::new()
.read(true)
.write(true)
.custom_flags(libc::O_NONBLOCK)
.open("/dev/ptmx")
.unwrap()
.into_raw_fd();
unsafe {
use std::io::Error;
let mut flag: libc::c_int = 1;
if libc::ioctl(
master_fd,
TIOCPKT,
&mut flag as *mut libc::c_int,
) < 0
{
error!("ioctl: {:?}", Error::last_os_error());
return Err(());
}
if libc::grantpt(master_fd) < 0 {
error!("grantpt: {:?}", Error::last_os_error());
return Err(());
}
if libc::unlockpt(master_fd) < 0 {
error!("unlockpt: {:?}", Error::last_os_error());
return Err(());
}
}
let tty_path = unsafe {
PathBuf::from(
CStr::from_ptr(libc::ptsname(master_fd))
.to_string_lossy()
.into_owned(),
)
};
let master = unsafe { File::from_raw_fd(master_fd) };
Ok(Pty {
master: master,
path: tty_path,
sub_thread: None,
sub_thread_tx: None,
})
}
pub fn subscribe<F>(&mut self, callback: F)
where
F: Fn() -> Result<(), ()> + Send + 'static,
{
let (thread_tx, thread_rx) = mpsc::channel();
let mut pollfd = libc::pollfd {
fd: self.master.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
self.sub_thread = Some(thread::spawn(move || {
loop {
match thread_rx.recv()
{
Ok(ThreadCommand::WaitForData) => {}
Ok(ThreadCommand::Stop) => return,
Err(_) => return,
}
// Clear receive queue
while !thread_rx.try_recv().is_err() {}
unsafe { libc::poll(&mut pollfd as *mut libc::pollfd, 1, -1) };
if callback().is_err() {
return;
}
}
}));
self.sub_thread_tx = Some(thread_tx);
}
pub fn path<'a>(&'a self) -> &'a PathBuf {
&self.path
}
pub fn set_winsize(&self, row: u16, col: u16, xpixel: u16, ypixel: u16) {
let size = libc::winsize {
ws_row: row,
ws_col: col,
ws_xpixel: xpixel,
ws_ypixel: ypixel,
};
unsafe {
let fd = self.master.as_raw_fd();
libc::ioctl(fd, libc::TIOCSWINSZ, &size as *const libc::winsize);
}
}
pub fn write(&mut self, data: &[u8]) -> io::Result<()> {
self.master.write_all(data)?;
self.master.flush()
}
pub fn read(&mut self, data: &mut [u8]) -> io::Result<usize> {
match self.master.read(data)
{
Ok(count) => {
self.sub_thread_tx.as_ref().map(|tx| {
tx.send(ThreadCommand::WaitForData)
});
Ok(count)
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
self.sub_thread_tx.as_ref().map(|tx| {
tx.send(ThreadCommand::WaitForData)
});
Ok(0)
}
Err(e) => Err(e),
}
}
}
impl Drop for Pty {
fn drop(&mut self) {
self.sub_thread_tx.take().map(
|tx| tx.send(ThreadCommand::Stop),
);
self.sub_thread = None;
}
}

View file

@ -1,31 +0,0 @@
use std::io::Result;
use std::os::unix::io::RawFd;
use std::path::PathBuf;
pub fn before_exec() -> Result<()> {
Ok(())
}
pub fn fork() -> usize {
extern crate syscall;
unsafe { syscall::clone(0).unwrap() }
}
pub fn set_winsize(fd: RawFd, row: u16, col: u16, xpixel: u16, ypixel: u16) {}
pub fn getpty() -> (RawFd, PathBuf) {
use syscall;
let master = syscall::open("pty:", syscall::O_RDWR | syscall::O_CREAT)
.unwrap();
let mut buf: [u8; 4096] = [0; 4096];
let count = syscall::fpath(master, &mut buf).unwrap();
(
master,
PathBuf::from(unsafe {
String::from_utf8_unchecked(Vec::from(&buf[..count]))
}),
)
}

14
src/sys/redox/mod.rs Normal file
View file

@ -0,0 +1,14 @@
use std::os::unix::io::RawFd;
pub mod pty;
pub fn before_exec() -> Result<()> {
Ok(())
}
pub fn fork() -> usize {
extern crate syscall;
unsafe { syscall::clone(0).unwrap() }
}
pub fn set_winsize(fd: RawFd, row: u16, col: u16, xpixel: u16, ypixel: u16) {}

19
src/sys/redox/pty.rs Normal file
View file

@ -0,0 +1,19 @@
use std::io::Result;
use std::os::unix::io::RawFd;
use std::path::PathBuf;
pub fn getpty() -> (RawFd, PathBuf) {
use syscall;
let master = syscall::open(
"pty:",
syscall::O_RDWR | syscall::O_CREAT | syscall::O_NONBLOCK,
).unwrap();
let mut buf: [u8; 4096] = [0; 4096];
let count = syscall::fpath(master, &mut buf).unwrap();
let path = String::from_utf8(Vec::from(&buf[..count]).or(())).unwrap();
(master, PathBuf::from(path))
}

View file

@ -1,75 +0,0 @@
use std::io::Result;
use std::os::unix::io::RawFd;
use std::path::PathBuf;
pub fn before_exec() -> Result<()> {
use libc;
unsafe {
libc::setsid();
libc::ioctl(0, libc::TIOCSCTTY, 1);
}
Ok(())
}
pub fn fork() -> usize {
use libc;
unsafe { libc::fork() as usize }
}
pub fn set_winsize(fd: RawFd, row: u16, col: u16, xpixel: u16, ypixel: u16) {
use libc;
unsafe {
let size = libc::winsize {
ws_row: row,
ws_col: col,
ws_xpixel: xpixel,
ws_ypixel: ypixel,
};
libc::ioctl(fd, libc::TIOCSWINSZ, &size as *const libc::winsize);
}
}
pub fn getpty() -> (RawFd, PathBuf) {
use libc;
use std::ffi::CStr;
use std::fs::OpenOptions;
use std::io::Error;
use std::os::unix::io::IntoRawFd;
const TIOCPKT: libc::c_ulong = 0x5420;
extern "C" {
fn ptsname(fd: libc::c_int) -> *const libc::c_char;
fn grantpt(fd: libc::c_int) -> libc::c_int;
fn unlockpt(fd: libc::c_int) -> libc::c_int;
fn ioctl(fd: libc::c_int, request: libc::c_ulong, ...) -> libc::c_int;
}
let master_fd = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/ptmx")
.unwrap()
.into_raw_fd();
unsafe {
let mut flag: libc::c_int = 1;
if ioctl(master_fd, TIOCPKT, &mut flag as *mut libc::c_int) < 0 {
panic!("ioctl: {:?}", Error::last_os_error());
}
if grantpt(master_fd) < 0 {
panic!("grantpt: {:?}", Error::last_os_error());
}
if unlockpt(master_fd) < 0 {
panic!("unlockpt: {:?}", Error::last_os_error());
}
}
let tty_path = unsafe {
PathBuf::from(
CStr::from_ptr(ptsname(master_fd))
.to_string_lossy()
.into_owned(),
)
};
(master_fd, tty_path)
}