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

webrtcprivate: add transport #2576

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open

webrtcprivate: add transport #2576

wants to merge 22 commits into from

Conversation

sukunrt
Copy link
Member

@sukunrt sukunrt commented Sep 20, 2023

Part of: #2009

Depends on: #2586

@marten-seemann
Copy link
Contributor

#2586 was merged. @sukunrt Can you rebase please?

Copy link
Contributor

@marten-seemann marten-seemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of the non-transport parts of this PR.

@@ -65,6 +67,8 @@ type Security struct {
Constructor interface{}
}

type ICEServer = webrtc.ICEServer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to redeclare this? Is it a requirement for Fx?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. Just so that we redefine it a different way later if we want. But I think it's fine to remove this and just take the pion specific object. We can make a breaking change if this needs to be changed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s a breaking change anyway if we change the underlying type. That should be fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove it?

@@ -597,3 +597,11 @@ func SwarmOpts(opts ...swarm.Option) Option {
return nil
}
}

func EnableWebRTCPrivate(stunServers []config.ICEServer) Option {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a separate option? We already have libp2p.Transport to configure transports. I had hoped that we don't need to introduce a separate knob here.

@@ -808,3 +815,14 @@ func (rf *relayFinder) resetMetrics() {
rf.metricsTracer.RelayAddressCount(0)
rf.metricsTracer.ScheduledWorkUpdated(&scheduledWorkTimes{})
}

var browserProtocols []int = []int{ma.P_WEBTRANSPORT, ma.P_WEBRTC_DIRECT, ma.P_WS, ma.P_WSS}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about /ws? Is this supposed to catch the /ws/tls case?

Suggested change
var browserProtocols []int = []int{ma.P_WEBTRANSPORT, ma.P_WEBRTC_DIRECT, ma.P_WS, ma.P_WSS}
var browserProtocols = []int{ma.P_WEBTRANSPORT, ma.P_WEBRTC_DIRECT, ma.P_WS, ma.P_WSS}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed ma.P_WS. That shouldn't be here.

for _, addr := range addrs {
pub := addr.Encapsulate(circuit)
raddrs = append(raddrs, pub)
if rf.conf.webRTCSupport && isBrowserDialableAddr(addr) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed for WebRTC and not for WebTransport?

As a general design principle, it would be nice if AutoRelay didn't need to contain any transport-specific logic.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. This was a poor choice. I've moved this to host.Addrs. Even this is not great but I'm not sure what else to do without an address pipeline. The flow now is:

BasicHost.AllAddrs => RelayFinder.Addrs => BasicHost.AddrFactory => BasicHost.Addrs

@@ -27,7 +27,10 @@ func (s *Swarm) TransportForDialing(a ma.Multiaddr) transport.Transport {
}
return nil
}
if isRelayAddr(a) {
if isProtocolAddr(a, ma.P_WEBRTC) {
return s.transports.m[ma.P_WEBRTC]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this require extra logic here? Is it because of the certhash? Why doesn't it apply to WebTransport then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The peer address is a circuitv2 address. So we need to check it before the circuitv2 check but the check is the same as the circuit v2 check.

The address is of the form
/ip4/1.2.3.4/udp/1/quic-v1/p2p/<relay-id>/p2p-circuit/webrtc
So we need to ensure that we don't use the quic-v1 or the p2p-circuit transport for dialing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't isRelayAddr already do that? Or phrased differently, the same problem would have occurred for a non-WebRTC address, why do we need special logic for WebRTC now?

Copy link
Member Author

@sukunrt sukunrt Oct 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but we don't want the circuit-v2 transport to handle dialing to the webrtc address.
The previous code was

if isRelayAddr => Use circuitv2 transport. 

But now we need

if isWebRTCAddr => Use webrtcprivate transport.
else if isRelayAddr => Use circuitv2 transport. 

This is because the webrtcprivate address has a p2p-circuit component but we want to use webrtcprivate transport. I could have written this as:

if isRelayAddr(addr):
	if isWebRTCAddr(addr):
		use webrtcprivate transport
	else
		use circuitv2 transport

The flow I've implemented is:
peerAddr: <relay-addr>/p2p-circuit/webrtc

  1. webrtcprivate.Transport.Dial is called with peerAddr
  2. webrtcprivate.Transport.Dial adds the relay address inferred from the webrtcprivate address inferred from the peerstore. Then makes a host.Connect call to establish a relay connection with the peer.
  3. Once relay connection is established it opens a signaling stream and establishes a webrtc connection.

I've chosen this because it is similar to circuitv2 transport which first establishes a direct connection with the relay node and then a circuitv2 connection to the peer over the relayed connection. This also has the nice property that Transport.Dial can connect to the peer over the address no matter the state of the host when Transport.Dial is called. In this case, it means if no circuit-v2 connection exists, it'll make a circuit-v2 connection first.

for _, addr := range s.host.Peerstore().Addrs(p) {
if _, err := addr.ValueForProtocol(ma.P_WEBRTC); err == nil {
ctx := network.WithForceDirectDial(s.ctx, "webrtc holepunch")
s.host.Connect(ctx, peer.AddrInfo{ID: p}) // address is already in peerstore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will start multiple Connect calls if there are multiple WebRTC addresses.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It returns immediately after this line, so this should be fine, no?

return
}
p := conn.RemotePeer()
// Peer supports DCUtR, let it trigger holepunch
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the peer supports DCUtR, but there are only WebRTC addresses?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That case is handled here: https://github.com/libp2p/go-libp2p/blob/webrtcprivate/transport/p2p/protocol/holepunch/holepuncher.go#L142

In this case the peer(DCUtR initiator) is best suited to handle the call so I've kept it on that side.

Copy link
Contributor

@marten-seemann marten-seemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move the shared parts to a separate package that's imported by both webrtc and webrtcprivate?

@@ -27,7 +27,10 @@ func (s *Swarm) TransportForDialing(a ma.Multiaddr) transport.Transport {
}
return nil
}
if isRelayAddr(a) {
if isProtocolAddr(a, ma.P_WEBRTC) {
return s.transports.m[ma.P_WEBRTC]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't isRelayAddr already do that? Or phrased differently, the same problem would have occurred for a non-WebRTC address, why do we need special logic for WebRTC now?

stream datachannel.ReadWriteCloser
channel *webrtc.DataChannel
}

type connection struct {
pc *webrtc.PeerConnection
transport *WebRTCTransport
transport tpt.Transport
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

var _ net.Addr = NetAddr{}

func (n NetAddr) Network() string {
return "libp2p-webrtc"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is how it's intended in the net.Addr. The doc says:

name of the network (for example, "tcp", "udp")

}

if l.transport.gater != nil && !l.transport.gater.InterceptSecured(network.DirInbound, s.Conn().RemotePeer(), conn) {
conn.Close()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to close the scope here?

err = fmt.Errorf("failed to read offer: %w", err)
return nil, err
}
if msg.Type == nil || *msg.Type != pb.Message_SDP_OFFER {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if msg.Type == nil || *msg.Type != pb.Message_SDP_OFFER {
if msg.GetType() != pb.Message_SDP_OFFER {

return
}

if msg.Type == nil || *msg.Type != pb.Message_ICE_CANDIDATE {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if msg.Type == nil || *msg.Type != pb.Message_ICE_CANDIDATE {
if msg.GetType() != pb.Message_ICE_CANDIDATE {

// Ignore without Debuging on empty message.
// Pion has a case where OnCandidate callback may be called with a nil
// candidate
if msg.Data == nil || *msg.Data == "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if msg.Data == nil || *msg.Data == "" {
if len(msg.GetData()) == 0 {

}

var init webrtc.ICECandidateInit
if err := json.Unmarshal([]byte(*msg.Data), &init); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if err := json.Unmarshal([]byte(*msg.Data), &init); err != nil {
if err := json.Unmarshal([]byte(msg.GetData()), &init); err != nil {


var _ tpt.Transport = &transport{}

func AddTransport(h host.Host, gater connmgr.ConnectionGater, stunServers []webrtc.ICEServer) (*transport, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exported function returning unexported type.

Why is this method needed? Why don't we have such a method for other transports?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this method for circuit-v2 transport. We need this method for the same reason for webrtc private transport.

The peer address is <relay-addr>/p2p-circuit/webrtc. This transport will first make a relayed connection with the peer, which is why it needs access to the host. After this point it establishes the connection over the signaling stream.

@@ -25,14 +25,14 @@ import (
"go.uber.org/zap/zapcore"
)

type connMultiaddrs struct {
local, remote ma.Multiaddr
type ConnMultiaddrs struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Don't make this public. Redefine if necessary.

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

Successfully merging this pull request may close these issues.

webrtc: private/browser-to-private/server
2 participants