housleyjk/ws-rs

ws-rs client use wss can not connect

Opened this issue · 12 comments

sbant commented

with:
cargo run --features ssl --example cli ws://echo.websocket.org
ws-rs can success connect.

but with:
cargo run --features ssl --example cli wss://echo.websocket.org
display:
Connecting to wss://echo.websocket.org
and nothing else.

p.s
ws-rs server tls is ok, I can connect the server with the websocket bulit in javascript

Can you establish a connection with any other non-browser client? I think that websocket.org is dropping the connection because it doesn't like the origin of the request. Just a guess. Looking at the logs, it's clear that websocket.org drops the tcp connection the moment that the handshake request is sent, so I think it means simply that they won't take connections from unknown agents or other origins than their own.

I've checked and wss support is definitely working. We just can't force websocket.org to accept the connection. Here are my results from connecting to wss.websocketstest.com/service:

cargo run --features ssl --example cli wss://wss.websocketstest.com/service                                                                                                      
   Compiling ws v0.5.0 (file:///home/housl/workspaces/develop/ws-rs)
     Running `target/debug/examples/cli wss://wss.websocketstest.com/service`
Connecting to wss://wss.websocketstest.com/service
Type /close [code] [reason] to close the connection.
Type /help to show these instructions.
Other input will be sent as messages.

<<< connected,
?> ,timer
>>> ,timer
<<< time,2016/6/23 01:02:18
<<< time,2016/6/23 01:02:19
<<< time,2016/6/23 01:02:20
?> ^C
sbant commented

I have run your test, but can‘t connect...

cargo run --features ssl --example cli wss://wss.websocketstest.com/service
Compiling ws v0.5.0 (file:///C:/Users/sbant/Downloads/ws-rs-stable/ws-rs-stable)
Running target\debug\examples\cli.exe wss://wss.websocketstest.com/service
Connecting to wss://wss.websocketstest.com/service

I'm use windows with openssl.

The client code create with ws-rs can't connect to ssl server create with ws-rs and outer ssl websocket site.

The "fn on_open(&mut self ..." doesn't even been called.

cargo run --features ssl --example cli wss://127.0.0.1:3012 (this is a server create with ws-rs, and it can successful connected with ssl websocket created in javascript)
Running target\debug\examples\cli.exe wss://127.0.0.1:3012
Connecting to wss://127.0.0.1:3012

but client still can't connect.

I haven't forgotten about this issue, but I need to be able to replicate it on Windows to figure out the problem. Sadly, I haven't been able to get around to getting a Working openssl setup on Windows just yet.

sbant commented

I use precompiled openssl from here http://www.npcglib.org/~stathis/blog/precompiled-openssl/
(p.s http://slproweb.com/products/Win32OpenSSL.html can also work ok, but only compiled with vs2013)

Copy libeay32MD.dll ssleay32MD.dll to the program folder and copy each file again as eay32.dll ssl32.dll because the need of linker.

cargo.toml :
openssl = { version = "*", features = ["tlsv1_2"] }

main.rs
extern crate openssl;

that's all...

What's the status on this issue? I've replicated it with this bit of code, if it helps:

extern crate env_logger;
extern crate ws;

fn main() {
	env_logger::init();

	ws::connect("wss://echo.websocket.org", |out| {
		out.send("Testing").unwrap();
		move |msg| {
			println!("{}", msg);
			Ok(())
		}
	}).unwrap();
}

Hi all! I'm a bit new to Rust but cant make it connect to wss stream.

[dependencies]
env_logger = "*"
openssl-sys = "*"
openssl = { version = "*" }
[dependencies.ws]
version = "*"
features = ["ssl"]
extern crate env_logger;
extern crate ws;
extern crate openssl;

use ws::{connect, Handler, Sender, Handshake, Result, Message, CloseCode};

// Our Handler struct.
// Here we explicity indicate that the Client needs a Sender,
// whereas a closure captures the Sender for us automatically.
struct Client {
    out: Sender,
}

// We implement the Handler trait for Client so that we can get more
// fine-grained control of the connection.
impl Handler for Client {

    // `on_open` will be called only after the WebSocket handshake is successful
    // so at this point we know that the connection is ready to send/receive messages.
    // We ignore the `Handshake` for now, but you could also use this method to setup
    // Handler state or reject the connection based on the details of the Request
    // or Response, such as by checking cookies or Auth headers.
    fn on_open(&mut self, _: Handshake) -> Result<()> {
        // Now we don't need to call unwrap since `on_open` returns a `Result<()>`.
        // If this call fails, it will only result in this connection disconnecting.
        self.out.send("Hello WebSocket")
    }

    // `on_message` is roughly equivalent to the Handler closure. It takes a `Message`
    // and returns a `Result<()>`.
    fn on_message(&mut self, msg: Message) -> Result<()> {
        // Close the connection when we get a response from the server
        println!("Got message: {}", msg);
        self.out.close(CloseCode::Normal)
    }
}

fn main() {
    env_logger::init();
    // Now, instead of a closure, the Factory returns a new instance of our Handler.
    connect("wss://api-pub.bitfinex.com/ws/v2", |out| Client { out: out } ).unwrap()
}

[2019-01-21T10:49:27Z ERROR ws::handler] WS Error <Io(Custom { kind: NotConnected, error: StringError("None") })>
What am i doing wrong?

C:\dev\ws-rs>cargo run --features ssl --example cli wss://echo.websocket.org
Finished dev [unoptimized + debuginfo] target(s) in 0.35s
Running target\debug\examples\cli.exe wss://echo.websocket.org
Connecting to wss://echo.websocket.org
thread '' panicked at 'called Result::unwrap() on an Err value: CursorDestinationInvalid', src\libcore\result.rs:1009:5
note: Run with RUST_BACKTRACE=1 for a backtrace.
thread 'main' panicked at 'called Result::unwrap() on an Err value: Any', src\libcore\result.rs:1009:5
error: process didn't exit successfully: target\debug\examples\cli.exe wss://echo.websocket.org (exit code: 101)

I'm able to repro this aswell.
Code:

pub fn main() {
  env_logger::init();
  connect("wss://websocketstest.com/service", |out| {
      move |msg| {
          println!("Got message: {}", msg);
          out.close(CloseCode::Normal)
      }
  }).unwrap()
}

And I get this error: [2019-04-13T09:17:11Z ERROR ws::handler] WS Error <Io(Custom { kind: NotConnected, error: StringError("None") })>

Some further context:

[2019-06-17T01:10:42Z TRACE ws::handler] Handler is building request to wss://websocketstest.com/service.
[2019-06-17T01:10:42Z DEBUG ws::handshake] Built request from URL:
GET /service HTTP/1.1
Connection: Upgrade
Host: websocketstest.com:443
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: yxHXAyRJpNxtsoY1g0Hc4g==
Upgrade: websocket


[2019-06-17T01:10:42Z TRACE mio::sys::windows::tcp] cancelling active TCP read
[2019-06-17T01:10:42Z TRACE mio::poll] registering with poller
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] register Token(0) Writable
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] set readiness to Writable
[2019-06-17T01:10:42Z TRACE mio::poll] registering with poller
[2019-06-17T01:10:42Z TRACE ws::io] Active connections 1
[2019-06-17T01:10:42Z TRACE ws::io] Waiting for event
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] select; timeout=Some(0ns)
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] polling IOCP
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] returning
[2019-06-17T01:10:42Z TRACE ws::io] Processing 1 events
[2019-06-17T01:10:42Z TRACE ws::connection] Ready to write handshake to UNKNOWN.
[2019-06-17T01:10:42Z TRACE ws::stream] Attempting to write ssl handshake.
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] set readiness to (empty)
[2019-06-17T01:10:42Z TRACE mio::sys::windows::tcp] scheduling a write of 517 bytes
[2019-06-17T01:10:42Z TRACE mio::sys::windows::tcp] write error: A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied. (os error 10057)
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] set readiness to Writable
[2019-06-17T01:10:42Z TRACE ws::io] Scheduling connection to UNKNOWN as Readable
[2019-06-17T01:10:42Z TRACE mio::poll] registering with poller
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] reregister Token(0) Readable
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] set readiness to Writable
[2019-06-17T01:10:42Z TRACE mio::sys::windows::tcp] scheduling a read
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] set readiness to Readable | Writable
[2019-06-17T01:10:42Z TRACE ws::io] Active connections 1
[2019-06-17T01:10:42Z TRACE ws::io] Waiting for event
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] select; timeout=Some(0ns)
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] polling IOCP
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] returning
[2019-06-17T01:10:42Z TRACE ws::io] Processing 1 events
[2019-06-17T01:10:42Z TRACE ws::connection] Performing TLS negotiation on UNKNOWN.
[2019-06-17T01:10:42Z TRACE ws::connection] Ready to write handshake to UNKNOWN.
[2019-06-17T01:10:42Z TRACE ws::stream] Attempting to write ssl handshake.
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] set readiness to Writable
[2019-06-17T01:10:42Z TRACE mio::sys::windows::tcp] scheduling a read
[2019-06-17T01:10:42Z TRACE mio::sys::windows::selector] set readiness to Readable | Writable

peer_addr seems to be returning UNKNOWN, and there's an OS write error (apparently not waiting until the socket is ready) causing a problem.

I've spent the last day debugging this issue and I'd like to get some feedback on next possible steps.

But first, let's talk about the root cause of the issue.
I have confirmed that the issue lies in the ws-rs project and specifically in how it sets up a TLS socket.
This repo has a reproduction of the issue.

Basically, whenever a WebSocket connection is started, ws-rs creates a corresponding TCP socket. Instead of using standard library primitives for TCP, ws-rs uses another library called mio which implements similar APIs directly on top of OS primitives (Totally fine except that it is more difficult to get correct).

One particular feature of mio version 0.6 is that creating a socket doesn't neccessarily connect it to the OS, and on Windows in particular mio waits to do that until the stream is registered with a poll. (Note that in Mio 0.7 the entire Windows implementation was rewritten to make the behavior more consistent)

However, instead of setting up the TLS connection after the socket is added to the event loop, ws-rs attempts to set it up immediately after creating the socket. This is done by cloning the original socket and replacing the original with the cloned version. The tricky part is that since mio delays the connection on Windows platforms, the cloned socket doesn't include a socket address to connect to when it is registered. So when ws-rs registers the socket with the poll, it is registering the cloned version which will skip the connection logic due to the assumption that it is already connected. As a result, the socket is never connected and all attempts to read/write to the socket will fail.

From here, the errors work their way up due to the logic in ws-rs.

Now let's look at a couple solutions.

From my perspective, there exist at least a couple of possible solutions, in no particular order:

  1. Rework connect() in io.rs to properly use the mio API.
    • From what I can tell, this may be a fairly large effort since moving the encrypt() call is non-trivial due to the whole replace the socket thing.
    • Also note that ws-rs shares the same connection logic between client and server roles so drastic changes are likely to cause more work in the end.
    • On the other hand, this will likely make the control flow easier to understand since fewer errors will be hit during normal initialization. (on Windows at least)
  2. Update to mio 0.7.
    • This may or may not require doing the work in 1 since the entire Windows implementation was rewritten in mio 0.7.
  3. Do nothing and encourage users to migrate to another library.
    • Not an ideal option but might be the most realistic considering this repo hasn't seen an update in nearly 2 years.

In any case, it would be good to setup test runners for both Windows and MacOS in addition to a Linux based OS to ensure that all core features are functional on all operating systems. (Unfortunately, it looks like Rust is not supported on MacOS on Travis CI yet - maybe setting up GitHub Actions would be worthwile?)

thanks @KallynGowdy, I also spent quite some time on this...

In my case the wss domain is resolved into multiple IPv6 / IPv4, and if I try to connect to them with std::net::TcpStream::connect(), i end up with a "Network is unreachable" error for all IPv6 (it works for IPv4 IPs).

I think the root cause of my problem is similar to what you described.
rs-ws is collecting IPs and process all IPv6 first, unfortunately mio::net::tcp::TcpStream is able to produce a stream for them even if they are unreachable, and later, when the SSL handshake fails, ws-rs is not trying the following IPs...

I agreed with your conclusion, seems time to switch to tungstenite-rs...

just ran into this. after banging my head against the wall for a few hours, that is..

I've been doing some work to try to fix this. Re: @KallynGowdy I took a look your suggestions.

It seems that with the MIO update to 0.7, the entire interfaces are changed. eg, the entire poll and poll.register is different, and mio extras channels are not compatible with 0.7+. the readiness abstraction was removed, and subscriptions done with N "Interests". oh and event itself is no longer a high level abstraction, it just calls the oa. there's more, it's a very long list

from what I see, the options are

  1. rewrite the entire encryption flow to properly use MIO 0.6 for windows. I don't know how much work this is, but yeah it seems like the control flow for winblows would need rewriting, at least.

  2. rewrite the socket/connection/io files such that the package works with 0.7+, aka find a replacement for channels, redo the connections and registrations and read loop... not trivial either.

  3. specific to me, but I am having this issue bc of a package that depends on ws-rs. This would mean rewriting the package such that it worked with tungstenite, which looks like a futures based. this would be a lot of work unless I created my own abstract eventloop, I think?

I am currently going down path #2, since I would like to use the new and improved windows API in 0.7.

l post an update if I ever am successful with the fork