-
Notifications
You must be signed in to change notification settings - Fork 582
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Create broker for negotiating connections (#14)
* feat: Create broker for negotiating connections WebRTC require an exchange of encryption keys and network hops to connect. This package pipes the exchange over gRPC. This will be used in all connecting clients and agents. * Regenerate protobuf definition * Cache Go build and test * Fix gRPC language with dRPC Co-authored-by: Bryan <bryan@coder.com> Co-authored-by: Bryan <bryan@coder.com>
- Loading branch information
1 parent
7c260f8
commit 53cfa8a
Showing
14 changed files
with
1,258 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
# Generated files | ||
peerbroker/proto/*.go linguist-generated=true | ||
provisionersdk/proto/*.go linguist-generated=true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,4 +21,5 @@ coverage: | |
|
||
ignore: | ||
# This is generated code. | ||
- peerbroker/proto | ||
- provisionersdk/proto |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package peerbroker | ||
|
||
import ( | ||
"reflect" | ||
|
||
"github.com/pion/webrtc/v3" | ||
"golang.org/x/xerrors" | ||
|
||
"github.com/coder/coder/peer" | ||
"github.com/coder/coder/peerbroker/proto" | ||
) | ||
|
||
// Dial consumes the PeerBroker gRPC connection negotiation stream to produce a WebRTC peered connection. | ||
func Dial(stream proto.DRPCPeerBroker_NegotiateConnectionClient, iceServers []webrtc.ICEServer, opts *peer.ConnOpts) (*peer.Conn, error) { | ||
// Convert WebRTC ICE servers to the protobuf type. | ||
protoIceServers := make([]*proto.WebRTCICEServer, 0, len(iceServers)) | ||
for _, iceServer := range iceServers { | ||
var credentialString string | ||
if value, ok := iceServer.Credential.(string); ok { | ||
credentialString = value | ||
} | ||
protoIceServers = append(protoIceServers, &proto.WebRTCICEServer{ | ||
Urls: iceServer.URLs, | ||
Username: iceServer.Username, | ||
Credential: credentialString, | ||
CredentialType: int32(iceServer.CredentialType), | ||
}) | ||
} | ||
if len(protoIceServers) > 0 { | ||
// Send ICE servers to connect with. | ||
// Client sends ICE servers so clients can determine the node | ||
// servers will meet at. eg. us-west1.coder.com could be a TURN server. | ||
err := stream.Send(&proto.NegotiateConnection_ClientToServer{ | ||
Message: &proto.NegotiateConnection_ClientToServer_Servers{ | ||
Servers: &proto.WebRTCICEServers{ | ||
Servers: protoIceServers, | ||
}, | ||
}, | ||
}) | ||
if err != nil { | ||
return nil, xerrors.Errorf("write ice servers: %w", err) | ||
} | ||
} | ||
|
||
peerConn, err := peer.Client(iceServers, opts) | ||
if err != nil { | ||
return nil, xerrors.Errorf("create peer connection: %w", err) | ||
} | ||
go func() { | ||
defer stream.Close() | ||
// Exchanging messages from the peer connection to negotiate a connection. | ||
for { | ||
select { | ||
case <-peerConn.Closed(): | ||
return | ||
case sessionDescription := <-peerConn.LocalSessionDescription(): | ||
err = stream.Send(&proto.NegotiateConnection_ClientToServer{ | ||
Message: &proto.NegotiateConnection_ClientToServer_Offer{ | ||
Offer: &proto.WebRTCSessionDescription{ | ||
SdpType: int32(sessionDescription.Type), | ||
Sdp: sessionDescription.SDP, | ||
}, | ||
}, | ||
}) | ||
if err != nil { | ||
_ = peerConn.CloseWithError(xerrors.Errorf("send local session description: %w", err)) | ||
return | ||
} | ||
case iceCandidate := <-peerConn.LocalCandidate(): | ||
err = stream.Send(&proto.NegotiateConnection_ClientToServer{ | ||
Message: &proto.NegotiateConnection_ClientToServer_IceCandidate{ | ||
IceCandidate: iceCandidate.Candidate, | ||
}, | ||
}) | ||
if err != nil { | ||
_ = peerConn.CloseWithError(xerrors.Errorf("send local candidate: %w", err)) | ||
return | ||
} | ||
} | ||
} | ||
}() | ||
go func() { | ||
// Exchanging messages from the server to negotiate a connection. | ||
for { | ||
serverToClientMessage, err := stream.Recv() | ||
if err != nil { | ||
_ = peerConn.CloseWithError(err) | ||
return | ||
} | ||
|
||
switch { | ||
case serverToClientMessage.GetAnswer() != nil: | ||
peerConn.SetRemoteSessionDescription(webrtc.SessionDescription{ | ||
Type: webrtc.SDPType(serverToClientMessage.GetAnswer().SdpType), | ||
SDP: serverToClientMessage.GetAnswer().Sdp, | ||
}) | ||
case serverToClientMessage.GetIceCandidate() != "": | ||
err = peerConn.AddRemoteCandidate(webrtc.ICECandidateInit{ | ||
Candidate: serverToClientMessage.GetIceCandidate(), | ||
}) | ||
if err != nil { | ||
_ = peerConn.CloseWithError(xerrors.Errorf("add remote candidate: %w", err)) | ||
return | ||
} | ||
default: | ||
_ = peerConn.CloseWithError(xerrors.Errorf("unhandled message: %s", reflect.TypeOf(serverToClientMessage).String())) | ||
return | ||
} | ||
} | ||
}() | ||
|
||
return peerConn, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package peerbroker_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/coder/coder/peerbroker" | ||
"github.com/coder/coder/peerbroker/proto" | ||
"github.com/coder/coder/provisionersdk" | ||
"github.com/pion/webrtc/v3" | ||
"github.com/stretchr/testify/require" | ||
"go.uber.org/goleak" | ||
"storj.io/drpc/drpcconn" | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
goleak.VerifyTestMain(m) | ||
} | ||
|
||
func TestDial(t *testing.T) { | ||
t.Run("Connect", func(t *testing.T) { | ||
ctx := context.Background() | ||
client, server := provisionersdk.TransportPipe() | ||
defer client.Close() | ||
defer server.Close() | ||
|
||
listener, err := peerbroker.Listen(server, nil) | ||
require.NoError(t, err) | ||
|
||
api := proto.NewDRPCPeerBrokerClient(drpcconn.New(client)) | ||
stream, err := api.NegotiateConnection(ctx) | ||
require.NoError(t, err) | ||
clientConn, err := peerbroker.Dial(stream, []webrtc.ICEServer{{ | ||
URLs: []string{"stun:stun.l.google.com:19302"}, | ||
}}, nil) | ||
require.NoError(t, err) | ||
defer clientConn.Close() | ||
|
||
serverConn, err := listener.Accept() | ||
require.NoError(t, err) | ||
defer serverConn.Close() | ||
_, err = serverConn.Ping() | ||
require.NoError(t, err) | ||
|
||
_, err = clientConn.Ping() | ||
require.NoError(t, err) | ||
}) | ||
} |
Oops, something went wrong.