Ping over WebSockets

Hello libp2p folks. I’m trying to setup a connection between two nodes, and I’m following the tutorial and using the Ping Behaviour (libp2p-basic-example.rs). Two nodes are able to connect and ping each other if I’m using raw TCP, but when I use WebSockets, nothing happens.

I’m using the development_transport, that has websocket transport defined, and also when I connect to one of the nodes using websocat, I get an info that a connection was established. However I’m not able to get the nodes to talk to each other using Ping over WS.

Console output from node 2

$ cargo run --example basic -- /ip4/127.0.0.1/tcp/35637
    Finished dev [unoptimized + debuginfo] target(s) in 0.10s
     Running `target/debug/examples/basic /ip4/127.0.0.1/tcp/35637`
local peer id: 12D3KooWB2j1S3cGvBFZif8J1ZYs7Q39zMjCFRRwFACqYhumw3Ee
dialed /ip4/127.0.0.1/tcp/35637
swarm event: NewListenAddr { listener_id: ListenerId(1), address: "/ip4/127.0.0.1/tcp/45767" }
swarm event: NewListenAddr { listener_id: ListenerId(2), address: "/ip4/127.0.0.1/tcp/46283/ws" }
swarm event: NewListenAddr { listener_id: ListenerId(1), address: "/ip4/192.168.0.199/tcp/45767" }
swarm event: NewListenAddr { listener_id: ListenerId(2), address: "/ip4/192.168.0.199/tcp/46283/ws" }
swarm event: NewListenAddr { listener_id: ListenerId(1), address: "/ip4/172.17.0.1/tcp/45767" }
swarm event: NewListenAddr { listener_id: ListenerId(2), address: "/ip4/172.17.0.1/tcp/46283/ws" }
swarm event: NewListenAddr { listener_id: ListenerId(1), address: "/ip4/172.18.0.1/tcp/45767" }
swarm event: NewListenAddr { listener_id: ListenerId(2), address: "/ip4/172.18.0.1/tcp/46283/ws" }
swarm event: ConnectionEstablished { peer_id: PeerId("12D3KooWM9KT1ioBhWYktvnYpYJChAyLqN3ANkKdGjAjcU8npiUr"), endpoint: Dialer { address: "/ip4/127.0.0.1/tcp/35637" }, num_established: 1, concurrent_dial_errors: Some([]) }
swarm event: Behaviour(Event { peer: PeerId("12D3KooWM9KT1ioBhWYktvnYpYJChAyLqN3ANkKdGjAjcU8npiUr"), result: Ok(Pong) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWM9KT1ioBhWYktvnYpYJChAyLqN3ANkKdGjAjcU8npiUr"), result: Ok(Ping { rtt: 681.662µs }) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWM9KT1ioBhWYktvnYpYJChAyLqN3ANkKdGjAjcU8npiUr"), result: Ok(Pong) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWM9KT1ioBhWYktvnYpYJChAyLqN3ANkKdGjAjcU8npiUr"), result: Ok(Ping { rtt: 1.380899ms }) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWM9KT1ioBhWYktvnYpYJChAyLqN3ANkKdGjAjcU8npiUr"), result: Ok(Pong) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWM9KT1ioBhWYktvnYpYJChAyLqN3ANkKdGjAjcU8npiUr"), result: Ok(Ping { rtt: 1.278549ms }) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWM9KT1ioBhWYktvnYpYJChAyLqN3ANkKdGjAjcU8npiUr"), result: Ok(Pong) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWM9KT1ioBhWYktvnYpYJChAyLqN3ANkKdGjAjcU8npiUr"), result: Ok(Ping { rtt: 1.294483ms }) })
^C
$ cargo run --example basic -- /ip4/192.168.0.199/tcp/36839/ws
    Finished dev [unoptimized + debuginfo] target(s) in 0.10s
     Running `target/debug/examples/basic /ip4/192.168.0.199/tcp/36839/ws`
local peer id: 12D3KooWRzSG5dFSgsJj7wTfqf9rPRpDEzWbNHwyoTJS61eAVkiM
dialed /ip4/192.168.0.199/tcp/36839/ws
swarm event: OutgoingConnectionError { peer_id: None, error: Transport([("/ip4/192.168.0.199/tcp/36839/ws", Other(Custom { kind: Other, error: Other(A(A(A(MultiaddrNotSupported("/ip4/192.168.0.199/tcp/36839/ws"))))) }))]) }
swarm event: NewListenAddr { listener_id: ListenerId(2), address: "/ip4/127.0.0.1/tcp/34029/ws" }
swarm event: NewListenAddr { listener_id: ListenerId(1), address: "/ip4/127.0.0.1/tcp/35629" }
swarm event: NewListenAddr { listener_id: ListenerId(2), address: "/ip4/192.168.0.199/tcp/34029/ws" }
swarm event: NewListenAddr { listener_id: ListenerId(1), address: "/ip4/192.168.0.199/tcp/35629" }
swarm event: NewListenAddr { listener_id: ListenerId(2), address: "/ip4/172.17.0.1/tcp/34029/ws" }
swarm event: NewListenAddr { listener_id: ListenerId(1), address: "/ip4/172.17.0.1/tcp/35629" }
swarm event: NewListenAddr { listener_id: ListenerId(2), address: "/ip4/172.18.0.1/tcp/34029/ws" }
swarm event: NewListenAddr { listener_id: ListenerId(1), address: "/ip4/172.18.0.1/tcp/35629" }

(nothing)

console output from node 1:

$ cargo run --example basic
WARN rustc_metadata::locator no metadata found: incompatible metadata version found: '/home/karim/dev/solcial/copia/target/debug/deps/libprost_derive-cb21d5e1c33265fb.so'
    Finished dev [unoptimized + debuginfo] target(s) in 0.10s
     Running `target/debug/examples/basic`
local peer id: 12D3KooWM9KT1ioBhWYktvnYpYJChAyLqN3ANkKdGjAjcU8npiUr
swarm event: NewListenAddr { listener_id: ListenerId(2), address: "/ip4/127.0.0.1/tcp/36839/ws" }
swarm event: NewListenAddr { listener_id: ListenerId(1), address: "/ip4/127.0.0.1/tcp/35637" }
swarm event: NewListenAddr { listener_id: ListenerId(2), address: "/ip4/192.168.0.199/tcp/36839/ws" }
swarm event: NewListenAddr { listener_id: ListenerId(1), address: "/ip4/192.168.0.199/tcp/35637" }
swarm event: NewListenAddr { listener_id: ListenerId(2), address: "/ip4/172.17.0.1/tcp/36839/ws" }
swarm event: NewListenAddr { listener_id: ListenerId(1), address: "/ip4/172.17.0.1/tcp/35637" }
swarm event: NewListenAddr { listener_id: ListenerId(2), address: "/ip4/172.18.0.1/tcp/36839/ws" }
swarm event: NewListenAddr { listener_id: ListenerId(1), address: "/ip4/172.18.0.1/tcp/35637" }

(... after connecting to /ip4/127.0.0.1/tcp/35637 by another instance....)

swarm event: IncomingConnection { local_addr: "/ip4/127.0.0.1/tcp/35637", send_back_addr: "/ip4/127.0.0.1/tcp/50626" }
swarm event: ConnectionEstablished { peer_id: PeerId("12D3KooWB2j1S3cGvBFZif8J1ZYs7Q39zMjCFRRwFACqYhumw3Ee"), endpoint: Listener { local_addr: "/ip4/127.0.0.1/tcp/35637", send_back_addr: "/ip4/127.0.0.1/tcp/50626" }, num_established: 1, concurrent_dial_errors: None }
swarm event: Behaviour(Event { peer: PeerId("12D3KooWB2j1S3cGvBFZif8J1ZYs7Q39zMjCFRRwFACqYhumw3Ee"), result: Ok(Pong) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWB2j1S3cGvBFZif8J1ZYs7Q39zMjCFRRwFACqYhumw3Ee"), result: Ok(Ping { rtt: 507.696µs }) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWB2j1S3cGvBFZif8J1ZYs7Q39zMjCFRRwFACqYhumw3Ee"), result: Ok(Pong) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWB2j1S3cGvBFZif8J1ZYs7Q39zMjCFRRwFACqYhumw3Ee"), result: Ok(Ping { rtt: 1.373089ms }) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWB2j1S3cGvBFZif8J1ZYs7Q39zMjCFRRwFACqYhumw3Ee"), result: Ok(Pong) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWB2j1S3cGvBFZif8J1ZYs7Q39zMjCFRRwFACqYhumw3Ee"), result: Ok(Ping { rtt: 1.25048ms }) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWB2j1S3cGvBFZif8J1ZYs7Q39zMjCFRRwFACqYhumw3Ee"), result: Ok(Pong) })
swarm event: Behaviour(Event { peer: PeerId("12D3KooWB2j1S3cGvBFZif8J1ZYs7Q39zMjCFRRwFACqYhumw3Ee"), result: Ok(Ping { rtt: 1.247696ms }) })
swarm event: ConnectionClosed { peer_id: PeerId("12D3KooWB2j1S3cGvBFZif8J1ZYs7Q39zMjCFRRwFACqYhumw3Ee"), endpoint: Listener { local_addr: "/ip4/127.0.0.1/tcp/35637", send_back_addr: "/ip4/127.0.0.1/tcp/50626" }, num_established: 0, cause: Some(IO(Custom { kind: Other, error: Closed })) }

(... no more output for /ip4/192.168.0.199/tcp/36839/ws ...)

Rust code:

use anyhow::Result;
use futures::{future, StreamExt};
use libp2p::{
  identity,
  ping::{Ping, PingConfig},
  swarm::Swarm,
  PeerId,
};
use std::{task::Poll, time::Duration};

#[tokio::main]
async fn main() -> Result<()> {
  let local_key = identity::Keypair::generate_ed25519();
  let local_peer_id = PeerId::from(local_key.public());
  println!("local peer id: {}", &local_peer_id);

  let transport = libp2p::development_transport(local_key).await?;
  let behaviour = Ping::new(
    PingConfig::new()
      .with_keep_alive(true)
      .with_interval(Duration::from_secs(1)),
  );
  let mut swarm = Swarm::new(transport, behaviour, local_peer_id);
  swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
  swarm.listen_on("/ip4/0.0.0.0/tcp/0/ws".parse()?)?;

  if let Some(addr) = std::env::args().nth(1) {
    let remote = addr.parse()?;
    swarm.dial_addr(remote)?;
    println!("dialed {}", addr);
  }

  future::poll_fn(move |cx| loop {
    match swarm.poll_next_unpin(cx) {
      Poll::Ready(Some(event)) => {
        println!("swarm event: {:?}", &event);
      }
      Poll::Ready(None) => return Poll::Ready(()),
      Poll::Pending => return Poll::Pending,
    }
  })
  .await;

  #[allow(unreachable_code)]
  Ok(())
}

also, for your convenience, the source of the development_transport adds WebSockets:

pub async fn development_transport(
    keypair: identity::Keypair,
) -> std::io::Result<core::transport::Boxed<(PeerId, core::muxing::StreamMuxerBox)>> {
    let transport = {
        let tcp = tcp::TcpConfig::new().nodelay(true);
        let dns_tcp = dns::DnsConfig::system(tcp).await?;
        let ws_dns_tcp = websocket::WsConfig::new(dns_tcp.clone());
        dns_tcp.or_transport(ws_dns_tcp)
    };

    let noise_keys = noise::Keypair::<noise::X25519Spec>::new()
        .into_authentic(&keypair)
        .expect("Signing libp2p-noise static DH keypair failed.");

    Ok(transport
        .upgrade(core::upgrade::Version::V1)
        .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated())
        .multiplex(core::upgrade::SelectUpgrade::new(
            yamux::YamuxConfig::default(),
            mplex::MplexConfig::default(),
        ))
        .timeout(std::time::Duration::from_secs(20))
        .boxed())
}

also when I connect with websocat, it does recognize a connection, so it does kind of accept WS:

machine 1:

swarm event: IncomingConnection { local_addr: "/ip4/192.168.0.199/tcp/34363/ws", send_back_addr: "/ip4/192.168.0.199/tcp/45178/ws" }
swarm event: IncomingConnectionError { local_addr: "/ip4/192.168.0.199/tcp/34363/ws", 

machine 2:

$ websocat ws://192.168.0.199:34363

Solved it, the solution is to modify the transport from:

let transport = {
        let tcp = tcp::TcpConfig::new().nodelay(true);
        let dns_tcp = dns::DnsConfig::system(tcp).await?;
        let ws_dns_tcp = websocket::WsConfig::new(dns_tcp.clone());
        dns_tcp.or_transport(ws_dns_tcp)
    };

to

let transport = {
        let tcp = tcp::TcpConfig::new().nodelay(true);
        let dns_tcp = dns::DnsConfig::system(tcp).await?;
        websocket::WsConfig::new(dns_tcp.clone())
    };