Replies: 2 comments 6 replies
-
More generally, how does socat do this? It is unclear to me. |
Beta Was this translation helpful? Give feedback.
4 replies
-
Here is TCP forwarder that passes the tests like socat (unrefactored): use std::net::{TcpListener,TcpStream,SocketAddr,Shutdown};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync + 'static>>;
fn forward(mut s1: TcpStream, mut s2: TcpStream) -> Result<()> {
use std::os::unix::io::AsRawFd;
// nix = "0.19.0"
use nix::fcntl::{fcntl,OFlag,FcntlArg};
use nix::poll::{poll, PollFd, PollFlags};
use std::io::{Read,Write,ErrorKind};
type UnwrittedDataInBuffer = std::ops::Range<usize>;
fcntl ( s1.as_raw_fd(), FcntlArg::F_SETFL(OFlag::O_NONBLOCK) )?;
fcntl ( s2.as_raw_fd(), FcntlArg::F_SETFL(OFlag::O_NONBLOCK) )?;
let mut buf_1to2 = [0u8; 1024];
let mut buf_2to1 = [0u8; 1024];
let mut datarange_1to2 : Option<UnwrittedDataInBuffer> = None;
let mut datarange_2to1 : Option<UnwrittedDataInBuffer> = None;
let mut finished_1to2 = false;
let mut finished_2to1 = false;
let mut polls : [PollFd; 2] = [PollFd::new(-1, PollFlags::empty()); 2];
let mut pollflags : [PollFlags; 2] = [PollFlags::empty(); 2];
//let mut active_polls : &mut [PollFd] ;
loop {
if finished_1to2 && finished_2to1 {
drop(s1);
drop(s2);
return Ok(());
}
pollflags[0] = PollFlags::POLLHUP;
pollflags[1] = PollFlags::POLLHUP;
if datarange_1to2.is_none() {
pollflags[0] |= PollFlags::POLLIN;
} else {
pollflags[1] |= PollFlags::POLLOUT;
}
if datarange_2to1.is_none() {
pollflags[1] |= PollFlags::POLLIN;
} else {
pollflags[0] |= PollFlags::POLLOUT;
}
polls[0] = PollFd::new(s1.as_raw_fd(), pollflags[0]);
polls[1] = PollFd::new(s2.as_raw_fd(), pollflags[1]);
poll(&mut polls[..], -1)?;
macro_rules! can_definitely_be_done_using_usual_function {
($pollidx_r:literal, $pollidx_w:literal, $datarange:ident, $buf:ident, $finished:ident, $socket_r:ident, $socket_w:ident,) => {
if let Some(r) = $datarange.take() {
if polls[$pollidx_w].revents().map(|x|x.contains(PollFlags::POLLOUT)) == Some(true) {
match $socket_w.write(& $buf[r.clone()]) {
Ok(x) => {
if x == r.len() {
$datarange = None;
} else {
$datarange = Some((r.start + x)..(r.end));
}
}
Err(e) if e.kind() == ErrorKind::WouldBlock => {
}
Err(e) => Err(e)?,
}
} else {
$datarange = Some(r);
}
} else {
if polls[$pollidx_w].revents().map(|x|x.contains(PollFlags::POLLHUP)) == Some(true) {
// Abort reading from socket when we know that
// writing to the other socket is destined to fail
$finished = true;
let _ = $socket_w.shutdown(Shutdown::Write);
// But for some strange reason absence of this section does not affect test results.
} else
if polls[$pollidx_r].revents().map(|x|x.contains(PollFlags::POLLIN)) == Some(true) {
match $socket_r.read(&mut $buf[..]) {
Ok(0) => {
$finished = true;
$socket_w.shutdown(Shutdown::Write)?;
}
Ok(x) => {
$datarange = Some(0..x);
}
Err(e) if e.kind() == ErrorKind::WouldBlock => {
}
Err(e) => Err(e)?,
};
}
}
}
}
can_definitely_be_done_using_usual_function!(
0,
1,
datarange_1to2,
buf_1to2,
finished_1to2,
s1,
s2,
);
can_definitely_be_done_using_usual_function!(
1,
0,
datarange_2to1,
buf_2to1,
finished_2to1,
s2,
s1,
);
}
}
fn main() -> Result<()> {
let ss = TcpListener::bind("127.0.0.1:5747".parse::<SocketAddr>().unwrap())?;
while let Ok((cs,_)) = ss.accept() {
std::thread::spawn(move || {
let g = TcpStream::connect("127.0.0.1:5746".parse::<SocketAddr>().unwrap()).unwrap();
if let Err(e) = forward(cs, g) {
eprintln!("{}", e);
}
});
}
Ok(())
} Also The naive forwarder above fails only the clogged-in-both-directions test. |
Beta Was this translation helpful? Give feedback.
2 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Naive attempt:
works reasonably, but fails to propagate socket close when the tunnel is clogged (unavailable for writing) in both directions.
I made a special checker for TCP tunnels: https://github.com/vi/tcptunnelchecker. It shows that
socat tcp-l:5747,fork,reuseaddr tcp:127.0.0.1:5746
is a properly implemented tunnel, but the one above is not:Beta Was this translation helpful? Give feedback.
All reactions