Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: circuit-relay-like module, but for negotiating WebRTC #361

Closed
guysv opened this issue Apr 28, 2019 · 22 comments
Closed

Suggestion: circuit-relay-like module, but for negotiating WebRTC #361

guysv opened this issue Apr 28, 2019 · 22 comments

Comments

@guysv
Copy link
Contributor

guysv commented Apr 28, 2019

So, the best part of circuit-relay, is the fact that it allows to connect nodes where neither can listen for incoming connections.

Unfortunately, running a circuit-relay HOP node is expensive, and by default HOP is turned off.

tl;dr: I hacked js-libp2p-circuit into negotiating WebRTC signals using newly relayed connections, and establish a direct connection between unconnected peers using a helping 3rd party.

Such connection scheme is more considerate of "relay" nodes, because they only need to relay the signaling data, and not the entire connection.
Another plus, is that now browsers should be able to connect to one another directly.

I uploaded the modded js-libp2p-circuit repo and a demo to github:
You can view the diff to the original circuit-relay module in this PR: guysv/js-libp2p-webrtc-poc#1

To run the demo:

  1. npm install (note: I included a custom package-lock.json in the demo repo so that js-libp2p-switch will use my modded circuit module, if you feel extra paranoid, regenerate the package-lock.json and fill in guysv/js-libp2p-webrtc-poc#webrtc-poc
  2. run the hop node DEBUG=libp2p:circuit:* node src/hop.js
  3. run listener node DEBUG=libp2p:circuit:* node src/listener.js
  4. run dialer node DEBUG=libp2p:circuit:* node src/dialer.js
  5. look for those logs I added, which indicate that WebRTC connection was established with listener node
# dialer node
  libp2p:circuit:dialer Writing signal to circuit connection +21ms
  libp2p:circuit:dialer Writing signal to circuit connection +1ms
  libp2p:circuit:dialer Read signal from circuit connection +46ms
  libp2p:circuit:dialer Connected! +21ms
# listener node
  libp2p:circuit:stop Read signal from circuit connection +0ms
  libp2p:circuit:stop Read signal from circuit connection +3ms
  libp2p:circuit:stop Writing signal to circuit connection +11ms
  libp2p:circuit:stop Writing signal to circuit connection +2ms
  libp2p:circuit:stop Connected! +22ms

The connection is not really usable, but those logs indicate that a WebRTC channel was established, good enough for POC :)

Anyhow, That was a cool experiment. I wrote this issue to hear what you guys think about it. I tried to find material about WebRTC in Libp2p but the only modules I found were webrtc-star, which is a centralized solution, and webrtc-direct, which doesn't really offer interesting features that websockets lack. I think this could be a cool addition to the stack!

@jacobheun
Copy link
Contributor

There is a WIP by @mkg20001 to convert webrtc-star to a distributed architecture at libp2p/js-libp2p-webrtc-star#148. It's currently blocked by some needed work on libp2p-crypto libp2p/js-libp2p-crypto#125.

@guysv
Copy link
Contributor Author

guysv commented Apr 30, 2019

@mkg20001 , I see you are already one year deep in this.
I crawled through your work and slides, but I did not figure out what extra benefits do we get from randevouz-exchange if we can tunnel direct-exchange over libp2p-circuit as seen in slide 6. (and demo'ed by my PoC, sort of).

@mkg20001
Copy link
Member

mkg20001 commented May 1, 2019

@guysv The extra benefit isn't really for upgrading but for esablisihng webrtc connections between browsers. If two browsers are connected to the same bootstrap node that also runs the rendezvous-exchange, then they can connect with each other over webrtc using this method. It intents to be a better replacement for the current socket.io based signaling server, since server selection and discovery is fully dynamic.

@guysv
Copy link
Contributor Author

guysv commented May 1, 2019

@mkg20001 But we already have a way to communicate over mutual peer, circuit relay. Why not reuse it instead? Especially concidering the fact the the implementation of a request-response API as you are trying to create with randevouz-exchange is facing a blocking issue involving crypto incompetability?

Regarding WebRTC integration, the only benefit I see randevouz-exchange has over circuit relay is that randevouz-exchange offers an API for a limited data exchange, where circuit-relay as it is now is unlimited and hence prone to abuse.

The unlimited nature of circuit relay is solvable, of course. Whatever the solution, it won't be as generic as having a data exchange API, but it is sufficient for WebRTC.

@mkg20001
Copy link
Member

mkg20001 commented May 1, 2019

@guysv

But we already have a way to communicate over mutual peer, circuit relay. Why not reuse it instead?

Establishing a p2p-circuit connection takes resources and isn't of much use considering we are going to toss that circuit connection once we connect via webrtc anyways.
Rendezvous exchange prevents high resource usage by not establishing a new connection but instead using the PeerIDs to securely pass data back and forth.
Basically it takes the destination peers public key (it can ask for it at the exchange server if it doesn't have it) to encrypt the request, the exchange forwards it and then the other side sends back an encrypted ACK/NACK with additional data that the other side can use to finalize the connection.

Edit: I understand that this (simple asymmetric crypto) isn't the worlds most secure way of request->response exchanging (in comparison to SECIO), but the other side's identity is going to be verified using SECIO anyways. Most attacks would be of no use (except for the usual "send invalid data, mess with memory, and cause a RCE" kind of vulnerability which would apply to the other, current, webrtc transport as well)

@guysv
Copy link
Contributor Author

guysv commented May 1, 2019

Establishing a p2p-circuit connection takes resources

If the connection has low traffic, why aren't the needed resources low too?

@mkg20001
Copy link
Member

mkg20001 commented May 1, 2019

If the connection has low traffic, why aren't the needed resources low too?

SECIO takes a few network roundtrips to establish a connection, and also it seems that the browser uses a pretty inefficient way of encrypting the stream (the WebCrypto api has no way to encrypt a stream directly, every buffer must be encrypted separately)

Rendezvous exchange reuses the existing connection with the bootstrap server and sends just a single request and a single response packet (unless the discovery mechanism doesn't provide the peer's public key alongside the peers id)

@guysv
Copy link
Contributor Author

guysv commented May 1, 2019

I see your point, thats indeed a better idea. I saw that you plan on using native JS crypto, how does that compare to WebCrypto in terms of performence?

@mkg20001
Copy link
Member

mkg20001 commented May 2, 2019

@guysv Well, I sort of "had no other choice", as webcrypto PKCS-v1 encryption support seems broken, so I had to go with node-forge, though only for the encryption part. Feel free, though, to contribute here libp2p/js-libp2p-crypto#125 if you have a good solution in mind

@guysv
Copy link
Contributor Author

guysv commented May 2, 2019

You know, I gave my solution another thought
We could modify circuit into transporting arbitrary data prior to SECIO handshake, and hence being more lightweight. We then use that to move around WebRTC signal, and cut on the said double connection handshake.

Which brings me to the other question, you could cut the crypto, and release randevouz-exchange in no time, this will be sufficient for WebRTC. Why shouldn't you?

@mkg20001
Copy link
Member

mkg20001 commented May 2, 2019

Why shouldn't you?

Modularity. My solution focused on having both an rendezvous exchange transport and a direct exchange transport, with the ability to swap them for one another or pick the best dynamically.

In the roadmap (see slide) support for a tiny "router" module that would pick the "best" based on path cost (direct is faster then rendezvous) was mentioned.

@guysv
Copy link
Contributor Author

guysv commented May 3, 2019

Well i get that polymorphic approach, but I think that if you ran into troubles doing the crypto, why not postponing to a future iteration? (For the record, I tried to dive in, but I don't see myself qualified to take crypto decisions) Decentralized WebRTC transport is a game changer for libp2p web apps and should be ready ASAP.

On another topic, On the roadmap you mentioned:

  • Support passing the swarm as an argument to a transport

Is there an effort underway?

@mkg20001
Copy link
Member

mkg20001 commented May 3, 2019

Well i get that polymorphic approach, but I think that if you ran into troubles doing the crypto, why not postponing to a future iteration?

Because I thought I could solve it. Then, when I realized I couldn't, I asked for some help. But no one took on the task libp2p/js-libp2p-crypto#125. Also when I asked for a re-review after giving some clarifications on the PR, nobody looked at it. So I deemed the whole endeavor unnecessary. Well, until now libp2p/js-libp2p-webrtc-star#148 (comment). So here we are... And I just happen to no longer have time for that, because I'm currently trying to move out and "get a life", like people always said, while also having an internship with a 99% chance of becoming an apprenticeship. So, sorry but no demos and refactoring for now, unless I somehow get some time freed up in my scheudle.

Is there an effort underway?

There is always this "libp2p-thing needs swarm, but swarm needs libp2p-thing initialized" discussion that keeps popping up and then gets "solved" for a specific part of the swarm. Example would be wsStar: It required a peerID for crypto, so discovery modules now have access to just the peerID.
AFAIK, no. Or not something I'd consider a proper solution to those problems.
What we really need is a separation of low-level stuff (TCP, UDP, Multicast, things that DON'T depend on swarm) and high-level stuff (wsStar, wrtcStar, stardust, things that utilize low-level swarm modules to do stuff like dialing, etc). But it should be done properly

@guysv
Copy link
Contributor Author

guysv commented May 7, 2019

@jacobheun @mkg20001 So in conclusion, libp2p/js-libp2p-webrtc-star#148 is waiting for the integration of mkg20001/interface-data-exchange into libp2p, but that won't happen until libp2p/js-libp2p-crypto#125 is resolved even though libp2p/js-libp2p-webrtc-star#148 does not need mkg20001/interface-data-exchange to be secure? Bummer.

@guysv
Copy link
Contributor Author

guysv commented May 7, 2019

Just found out i'm not even the first one to try my approach: dryajov/js-libp2p-webrtc-circuit I really think we should work out interface-data-exchange integration into libp2p before figuring out how to secure it.

@jacobheun
Copy link
Contributor

@guysv would you have time to work on that? I'll work on getting crypto unblocked, but it's likely I won't get time to work on that myself until next quarter. If we get the majority of the work complete, we could add in the crypto update before doing a release into something like js-ipfs.

@guysv
Copy link
Contributor Author

guysv commented May 10, 2019

@jacobheun Sure!

There is still an open question about how to include the new WebRTC transport into the libp2p bundle.
Because the WebRTC transport will rely on already connected peers we are required to have the swarm object around. Right now libp2p/js-libp2p-webrtc-star#148 delegates the problem to whoever creates the WebRTC transport.

libp2p-circuit solved this by not being part of the libp2p bundle. Instead, it is burried somewhere inside libp2p-switch object, with its own API. Unless there is some rational reason I missed, this is a hack. If we hit a 2nd transport and needs the same treatment, its time to work out a generic API.

I'd suggest passing all transports the swarm obj, but that will break backwards competability.

The middle grounds are to create a new "3rd-party-assisted" transports field in the libp2p bundle, where all transports must be passed as classes and are instatiated with the swarm obj, and then concat it to the other transports array. Plus, relayed transports usually have a complex configuration, so this way the transports can be configured easily via the root libp2p config.

Thoughts?

@jacobheun
Copy link
Contributor

jacobheun commented May 10, 2019

I think something we can do as an intermediary step is to pass libp2p itself to the options of the transport, as this wouldn't break compatability. interface-transport specifies that a transport should take options in its constructor, so doing new T({ libp2p: libp2p }) should be fine. We could just pass the switch, but I think there is value in passing all of libp2p, as this method could also be used by non transport services.

For the webrtc star update, I think the challenge is going to be providing the exchange, which needs the switch. We could potentially solve this by providing a transport factory instead of the constructor, and passing it libp2p:

const webrtcStarFactory = (libp2p) => {
  const exchange = new Exchange({ libp2p })
  return new WebRTCStar({ libp2p, exchange })
}
new Libp2p({
  modules: {
    transport: [ webrtcStarFactory ]
  }
})

Longer term there's been discussion of using DI in the past, #130. The big advantage there would really be making the libp2p config simpler, and allow a lot more flexibility for modules, but that potentially moves some complexity back to the modules.

@guysv
Copy link
Contributor Author

guysv commented May 12, 2019

I got it, looks something like this:

function WebRTCStarFactory (options) {
  const WebRTCStarProxy = {
    construct(target, args) {
      options.exchange = new RendezvousExchange(args[0].libp2p.switch, {enableServer: true})
      options.exchange.start()
      return new target(Object.assign(args[0], options))
    }
  }

  return new Proxy(WebRTCStarClass, WebRTCStarProxy)
}

module.exports = WebRTCStarFactory

@guysv
Copy link
Contributor Author

guysv commented May 12, 2019

@jacobheun hey, got a working demo, found here https://github.com/guysv/js-libp2p-webrtc-star-demo
I patched some repos to get it to work, you can follow the package.json

there's quite some work left, but it looks good so far.

@jacobheun
Copy link
Contributor

Added a note, guysv/js-libp2p-webrtc-star-demo@48c274b#r33546307, about an issue with the demo I saw and a potential improvement for the demo as work gets finished up.

@p-shahi
Copy link
Member

p-shahi commented May 30, 2023

WebRTC signaling is specified here https://github.com/libp2p/specs/blob/master/webrtc/webrtc.md and implemented in https://github.com/libp2p/js-libp2p-webrtc

@p-shahi p-shahi closed this as completed May 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants