-
Notifications
You must be signed in to change notification settings - Fork 989
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
feat: Add WebRTC transport #2622
Conversation
I just want to mention that the state of smoldot shouldn't influence what we decide to merge in the rust-libp2p repo. I think that implementing (3) would reuse most of the work that has been done, so there is little drawback in doing (1) first. |
@melekes Thank you very much for this pull request. Exciting work! I won't get to reviewing it this week, though I will make sure I come back to it next week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
transports/webrtc/src/transport.rs
Outdated
// XXX: anything to do here? | ||
self.dial(addr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is required in the case of hole punching. (See below.) Eventually we might want a browser to be able to hole punch to a rust-libp2p server node. I am not yet sure how this concept would then translate into the WebRTC world. Will need to give this more thought.
- Simultaneous Connect. The two nodes follow the steps below in parallel for
every address obtained from theConnect
message:
- For a TCP address:
- Upon receiving the
Sync
,A
immediately dials the address toB
.- Upon expiry of the timer,
B
dials the address toA
.- This will result in a TCP Simultaneous Connect. For the purpose of all
protocols run on top of this TCP connection,A
is assumed to be the
client andB
the server.- For a QUIC address:
- Upon receiving the
Sync
,A
immediately dials the address toB
.- Upon expiry of the timer,
B
starts to send UDP packets filled with
random bytes toA
's address. Packets should be sent repeatedly in
random intervals between 10 and 200 ms.- This will result in a QUIC connection where
A
is the client andB
is
the server.
https://github.com/libp2p/specs/blob/master/relay/DCUtR.md
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did an initial pass, thanks for pushing this forward! :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple more comments.
Most importantly, I think from an overall design perspective, we should try and avoid these long-running tasks that are just spawned into the executor. The rest of rust-libp2p is designed around exposing passive state machines that don't do anything unless they are polled.
Thoughts @mxinden?
Thanks for review @mxinden and @thomaseizinger ❤️ appreciate it! Waiting for #2652 makes sense. I'm still waiting for some PRs to land in webrtc-rs, which is used here. When the spec is finalised, I will fix the remaining comments and update the code to reflect the spec. |
Very much agreed on a general level. I see additional tasks as a performance optimization. Until we can prove that it is worth the performance gain, I would very much favor the simplicity of not spawning additional tasks. Also note that enforcing back-pressure is hard when spawning tasks. E.g. in a simple HTTP example, spawning a task per HTTP request breaks the back-pressure chain unless enforced through an additional mechanism. |
warning: uses my fork of libp2p and webrtc-rs library Impl: libp2p/rust-libp2p#2622 Spec: libp2p/specs#412 Refs paritytech/smoldot#1712
Closes #168 This PR adds `RTCCertificate::from_existing` method, which constructs `RTCCertificate` from an existing DTLS certificate. An existing certificate might be needed in cases like [this](libp2p/rust-libp2p#2622) where you need DTLS identity to be fixed for some period of time (whole duration of the certificate or some part of it). * peer_connection: replace x509_cert with pem field also, make `pem` and `expires` fields private and add `RTCCertificate::from_existing` method, which gives a way to construct a `RTCCertificate` using an already generated certificate (as opposed to generating a new one using `from_params` or `from_key_pair` methods). * make RTCConfiguration clonable otherwise, it's not possible to reuse the same config across N peer connections.
@melekes does it make sense for me to give this another review? Or would you prefer to wait until after this is updated to the latest |
this ^ |
Tried my best at updating to the current master d96b499, but failed. Tests are failing after 60s of not establishing a conn. Kinda wish there was a document with changes that must be made to the transport. |
This pull request has merge conflicts. Could you please resolve them @melekes? 🙏 |
@mxinden @thomaseizinger I think this is ready for final review & merge |
Wonderful. Thanks for the update and the upstream changes. Adding the |
@melekes can you address the CI failures unrelated to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@melekes can you address the CI failures unrelated to
semver-check
?
I've marked them as inline comments.
Assumption correct! I slightly updated the pull request description to make a better commit message. |
and fix link
use libp2p::core::{identity, muxing::StreamMuxerBox, upgrade, Transport as _}; | ||
use libp2p::request_response::{ | ||
ProtocolName, ProtocolSupport, RequestResponse, RequestResponseCodec, RequestResponseConfig, | ||
RequestResponseEvent, RequestResponseMessage, | ||
}; | ||
use libp2p::swarm::{Swarm, SwarmEvent}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great if we could rewrite these tests to not depend on Swarm
. See #3023.
However, I don't think it is a blocker. We can resolve that also in the above linked PR.
listen_addr: self | ||
.listen_multiaddress(ip, self.config.id_keys.public().to_peer_id()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for raising this after it has been merged, but I think that all the other transports do not include the /p2p/
in the multiaddress they report, but this one does.
It's an important detail, as these addresses aren't just for logging purposes but get sent over the network to other peers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like it was done on purpose. @thomaseizinger @mxinden can you confirm that it's okay to include /p2p/
in the reported multiaddress? (other transports don't seem to do that)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I remember that. Does this break something? IMO, peers should always use /p2p
when possible upon dialing another peer. We have access to the PeerId
in the Transport
so why not append it? Esp. because the webrtc spec mandates that you have to dial with /p2p
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These addresses get sent over the network through the identify
protocol. They are then most likely propagated to kademlia
to put in the k-buckets. Either all addresses have a /p2p/
at the end (and then the receiver removes these /p2p/
), or none, but the in-between makes no sense.
We have access to the PeerId in the Transport so why not append it?
We already know the PeerId
everywhere, so why should we append it to the addresses as well? These addresses are always in every single scenario already associated with a specific PeerId. Putting the PeerId again in the address itself is completely redundant.
I really dislike this /p2p/
multiaddress specifier with a passion because it's purely an API trick. It exists solely so that you can call functions with foo(concat(address, "/p2p/", peer_id))
(and then foo
de-concats them) instead of foo(address, peer_id)
. I don't understand why this /p2p/
exists at all. And it makes all our usages of Multiaddr
completely ambiguous because you never know whether there's a /p2p/
at the end or not.
Esp. because the webrtc spec mandates that you have to dial with /p2p.
Sorry but if the spec says that then I must say that this is completely out of scope of the spec. This is purely an API problem, not a wire problem. The spec might say that you should know the peerid of the remote ahead of time, and that's true, but that isn't equivalent to "you should pass as parameter to your dial function a multiaddress that contains a /p2p/ component".
On the other hand, the identify
spec if it was written properly should mention what the multiaddresses should look like, and if so would probably say that they shouldn't have a /p2p/
at the end given that not only the remote is supposed to know our PeerId but we already send the PeerId separately through another field in the identify
protocol.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that this is an API problem and would like to present a (small) counter-weight: having this syntax available is very useful so that people (in my case: IT admins) can effect a dial to a specific peer at a specific transport address by just copying&pasting a single string.
If we were to agree that that is a worthy cause for the existence of /p2p
syntax then it would be surprising to see it rejected in API calls. DialOpts
is already a bit weird in that sense because it requires the calling code to figure out whether the PeerId is to be required or not.
Small rant: there is quite a bit of code in ipfs-embed
to canonicalise multiaddrs in this regard, so that the address book can present a consistent view that is useful for UIs (→ human consumption).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it is very useful to be able to copy-paste a string that contains a /p2p/
and pass both the address and peerid at the same time, however that is still not a proper justification for the existence of /p2p/
in multiaddresses.
If you want your API to accept a multiaddress and peerid, then just use a type named MultiaddrWithPeerId
and then properly split it into a (Multiaddr, PeerId)
tuple before passing them around. Similarly, when you show them to the user, just reconstruct the MultiaddrWithPeerId
. Not everything has to be shoved into a single Multiaddr
type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. A more sophisticated admin might want to express a relayed connection through a specific peer, in which case this separation would be difficult — in particular since multiple such hops could potentially be used (not sure whether it is supported today, but it might be!).
I’m also not sure whether I agree with the underlying premise that the PeerId is not genuinely part of the “address”. The routing example above demonstrates that an address may well depend on the identities of the nodes found at certain transport hops. This would imply that a Multiaddr
may contain a MultiaddrWithPeerId
, at which point we’re back at square one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our conversation went off-topic I guess because I said that I didn't understand why /p2p/
existed.
Let's recenter on the pragmatic problem here, which is that the WebRTC transport returns addresses with /p2p/
while all the other transports don't.
The reason why I'd like to change that is consistency, and to avoid passing clearly redundant information when sending these addresses through identify (it also avoids problems such as "what if there's a mismatch in the peerid?"), and to avoid issues such as "remotes treat our address as invalid because it didn't expect the /p2p at the end". I'm not trying to push an ideology about /p2p
itself here.
I think we all agree that it is good to precisely document the contexts in which a multiaddr ends with /p2p
, the contexts in which it might, and the contexts in which it doesn't. We'd rather avoid having to put if multiaddr.last().is_p2p() { multiaddr.pop() }
everywhere "just in case".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
emphatic yes!
since libp2p/rust-libp2p#2622 was merged
Refs paritytech/smoldot#1712 Impl libp2p/rust-libp2p#2622 - WebRTC transport is enabled for non-validators and developers by default. - The transport will generate and store the certificate, which is required for WebRTC identity, in base dir. In the future, when a new version of `ring` library is released, the certificate will be deterministically derived from the node's peer ID.
Refs paritytech/smoldot#1712 Impl libp2p/rust-libp2p#2622 - WebRTC transport is enabled for non-validators and developers by default. - The transport will generate and store the certificate, which is required for WebRTC identity, in base dir. In the future, when a new version of `ring` library is released, the certificate will be deterministically derived from the node's peer ID.
to align with all other transports, which don't include `/p2p/..` in their reported addresses. Refs libp2p#2622 (comment)
This aligns with what other transports do. Related: #2622 (comment).
Refs paritytech/smoldot#1712 Impl libp2p/rust-libp2p#2622 - WebRTC transport is enabled for non-validators and developers by default. - The transport will generate and store the certificate, which is required for WebRTC identity, in base dir. In the future, when a new version of `ring` library is released, the certificate will be deterministically derived from the node's peer ID.
- Added in libp2p#2622 - Interoperability tests added in libp2p/test-plans#100
- Added in #2622 - Interoperability tests added in libp2p/test-plans#100
Description
Hey 👋 This is a WebRTC transport implemented in accordance w/ the spec. It's based on the webrtc-rs library.
Resolves: #1066.
Links to any relevant issues
Change checklist