aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--README.md29
-rw-r--r--src/main.rs28
4 files changed, 45 insertions, 16 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a207ddf..aaadc13 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -622,7 +622,7 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "webcat"
-version = "0.1.0"
+version = "0.2.0"
dependencies = [
"anyhow",
"atty",
diff --git a/Cargo.toml b/Cargo.toml
index cef5659..d06d4af 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "webcat"
-version = "0.1.0"
+version = "0.2.0"
description = "netcat for websockets"
authors = ["Martin Fischer <martin@push-f.com>"]
license = "MIT"
diff --git a/README.md b/README.md
index da92a72..65d2da9 100644
--- a/README.md
+++ b/README.md
@@ -12,26 +12,39 @@ The server only accepts one client at a time. The client auto-reconnects.
## Setting up a MITM debugging proxy
-To debug stateful protocols on top of websockets, it is helpful to be able to
-inject messages to the server or the client in a real session, acting as a
-man-in-the-middle (MITM). On Linux you can achieve this using `webcat` and
-FIFOs as follows:
+By redirecting the standard input/output streams of webcat you can turn it into
+a man-in-the-middle proxy, particularily useful for debugging stateful protocols
+on top of websocket.
+
+ [some client] <-> [webcat server] <-> [webcat client] <-> [some server]
+ FIFOs
+
+All it takes to set this up is four commands, for example:
mkfifo client-in server-in
webcat ws://example.com:3000/ < client-in > server-in
webcat -l 4000 < server-in > client-in
echo > server-in # unblock the FIFO deadlock
-You can now connect your client to ws://localhost:4000/.
-And inject messages by writing to the named pipes:
+You can now connect your client to `ws://localhost:4000/`
+and inject messages by writing to the named pipes:
echo "Hello from webcat" > client-in
-Note that when redirecting stdout the messages are automatically printed to
-stderr, so you can still observe what's happening.
+Webcat does two tricks to make this setup even more convenient:
+
+* When redirecting stdout, the messages are automatically printed
+ to stderr, so you can still observe what's happening.
+
+* When the client recognizes the server output `accepted new client`,
+ it automatically disconnects and reconnects its server connection
+ to prevent stateful application protocols from becoming out of sync.
+
## Limitations
+* no support for binary messages
+
* no support for messages containing newlines
(cannot send them, cannot distinguish them from separate messages)
diff --git a/src/main.rs b/src/main.rs
index cc4163d..d93b24f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -42,7 +42,7 @@ async fn start_client(url: Url) -> Result<()> {
match tokio_tungstenite::connect_async(&url).await {
Ok((stream, _resp)) => {
eprintln!("connected to server");
- webcat("client >>>", stream, &mut stdin_receiver).await?;
+ webcat(Mode::Client, stream, &mut stdin_receiver).await?;
}
Err(err) => {
eprintln!("failed to connect to server: {}", err);
@@ -58,6 +58,8 @@ async fn start_client(url: Url) -> Result<()> {
}
}
+const SERVER_ACCEPTED_NEW_CONN: &str = "accepted new client";
+
async fn start_server(port: u16) -> Result<()> {
let listen_addr: SocketAddr = ([127, 0, 0, 1], port).into();
let listener = TcpListener::bind(listen_addr).await?;
@@ -70,10 +72,10 @@ async fn start_server(port: u16) -> Result<()> {
loop {
let (stream, _client_addr) = listener.accept().await?;
- eprintln!("accepted TCP connection");
match tokio_tungstenite::accept_async(stream).await {
Ok(stream) => {
- webcat("server <<<", stream, &mut stdin_receiver).await?;
+ println!("{}", SERVER_ACCEPTED_NEW_CONN);
+ webcat(Mode::Server, stream, &mut stdin_receiver).await?;
}
Err(err) => {
eprintln!("failed to upgrade {}", err);
@@ -89,8 +91,14 @@ async fn send_stdin_to_channel(sender: UnboundedSender<String>) -> Result<()> {
Ok(())
}
+#[derive(PartialEq)]
+enum Mode {
+ Server,
+ Client,
+}
+
async fn webcat<S: Stream<Item = Result<Message, WsError>> + Sink<Message>>(
- name: &str,
+ mode: Mode,
stream: S,
receiver: &mut UnboundedReceiver<String>,
) -> Result<()> {
@@ -98,7 +106,12 @@ async fn webcat<S: Stream<Item = Result<Message, WsError>> + Sink<Message>>(
loop {
tokio::select! {
Some(line) = receiver.recv() => {
- if write.send(Message::Text(line)).await.is_err() {
+ if atty::isnt(atty::Stream::Stdin) && mode == Mode::Client && line == SERVER_ACCEPTED_NEW_CONN {
+ // Assuming the MITM setup described in the README, we
+ // disconnect and reconnect the client to prevent the
+ // application client and server from desynchronizing.
+ return Ok(());
+ } else if write.send(Message::Text(line)).await.is_err() {
eprintln!("failed to send message");
}
}
@@ -111,7 +124,10 @@ async fn webcat<S: Stream<Item = Result<Message, WsError>> + Sink<Message>>(
if !atty::is(atty::Stream::Stdout) {
// if stdout is redirected we also print the
// messages to stderr for convenience
- eprintln!("{} {}", name, text);
+ eprintln!("{} {}", match mode {
+ Mode::Server => "server <<<",
+ Mode::Client => "client >>>",
+ }, text);
}
}
other => {