2020-04-06 14:45:47 +08:00
|
|
|
package webtorrent
|
2020-04-05 11:55:14 +08:00
|
|
|
|
|
|
|
import (
|
2022-07-12 14:05:19 +08:00
|
|
|
"context"
|
2020-04-20 08:21:31 +08:00
|
|
|
"expvar"
|
2020-04-05 11:55:14 +08:00
|
|
|
"fmt"
|
2022-07-12 14:05:19 +08:00
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
|
|
"go.opentelemetry.io/otel/codes"
|
|
|
|
"go.opentelemetry.io/otel/trace"
|
2020-04-20 08:21:31 +08:00
|
|
|
"io"
|
2020-04-05 11:55:14 +08:00
|
|
|
"sync"
|
|
|
|
|
2022-07-12 14:05:19 +08:00
|
|
|
"github.com/anacrolix/log"
|
2020-04-20 08:21:31 +08:00
|
|
|
"github.com/anacrolix/missinggo/v2/pproffd"
|
2020-04-05 11:55:14 +08:00
|
|
|
"github.com/pion/datachannel"
|
2021-02-22 05:30:34 +08:00
|
|
|
"github.com/pion/webrtc/v3"
|
2022-07-12 14:05:19 +08:00
|
|
|
"go.opentelemetry.io/otel"
|
2020-04-05 11:55:14 +08:00
|
|
|
)
|
|
|
|
|
2020-04-07 10:17:21 +08:00
|
|
|
var (
|
2020-04-20 08:21:31 +08:00
|
|
|
metrics = expvar.NewMap("webtorrent")
|
|
|
|
api = func() *webrtc.API {
|
2020-04-13 12:31:39 +08:00
|
|
|
// Enable the detach API (since it's non-standard but more idiomatic).
|
2020-04-07 10:17:21 +08:00
|
|
|
s.DetachDataChannels()
|
|
|
|
return webrtc.NewAPI(webrtc.WithSettingEngine(s))
|
|
|
|
}()
|
|
|
|
config = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}}
|
|
|
|
newPeerConnectionMu sync.Mutex
|
|
|
|
)
|
|
|
|
|
2020-04-20 08:21:31 +08:00
|
|
|
type wrappedPeerConnection struct {
|
|
|
|
*webrtc.PeerConnection
|
2020-05-03 16:45:12 +08:00
|
|
|
closeMu sync.Mutex
|
2020-04-20 08:21:31 +08:00
|
|
|
pproffd.CloseWrapper
|
2022-07-12 14:05:19 +08:00
|
|
|
span trace.Span
|
|
|
|
ctx context.Context
|
2020-04-07 10:17:21 +08:00
|
|
|
}
|
|
|
|
|
2020-05-03 16:45:12 +08:00
|
|
|
func (me *wrappedPeerConnection) Close() error {
|
|
|
|
me.closeMu.Lock()
|
|
|
|
defer me.closeMu.Unlock()
|
2022-07-12 14:05:19 +08:00
|
|
|
err := me.CloseWrapper.Close()
|
|
|
|
me.span.End()
|
|
|
|
return err
|
2020-04-20 08:21:31 +08:00
|
|
|
}
|
2020-04-05 11:55:14 +08:00
|
|
|
|
2022-07-12 14:05:19 +08:00
|
|
|
func newPeerConnection(logger log.Logger) (*wrappedPeerConnection, error) {
|
2020-04-20 08:21:31 +08:00
|
|
|
newPeerConnectionMu.Lock()
|
|
|
|
defer newPeerConnectionMu.Unlock()
|
2022-07-12 14:05:19 +08:00
|
|
|
ctx, span := otel.Tracer(tracerName).Start(context.Background(), "PeerConnection")
|
2020-04-20 08:21:31 +08:00
|
|
|
pc, err := api.NewPeerConnection(config)
|
|
|
|
if err != nil {
|
2022-07-12 14:05:19 +08:00
|
|
|
span.SetStatus(codes.Error, err.Error())
|
|
|
|
span.RecordError(err)
|
|
|
|
span.End()
|
2020-05-03 16:45:12 +08:00
|
|
|
return nil, err
|
2020-04-20 08:21:31 +08:00
|
|
|
}
|
2022-07-12 14:05:19 +08:00
|
|
|
wpc := &wrappedPeerConnection{
|
2020-05-03 16:45:12 +08:00
|
|
|
PeerConnection: pc,
|
|
|
|
CloseWrapper: pproffd.NewCloseWrapper(pc),
|
2022-07-12 14:05:19 +08:00
|
|
|
ctx: ctx,
|
|
|
|
span: span,
|
|
|
|
}
|
|
|
|
// If the state change handler intends to call Close, it should call it on the wrapper.
|
|
|
|
wpc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
|
|
|
|
logger.Levelf(log.Warning, "webrtc PeerConnection state changed to %v", state)
|
|
|
|
span.AddEvent("connection state changed", trace.WithAttributes(attribute.String("state", state.String())))
|
|
|
|
})
|
|
|
|
return wpc, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func setAndGatherLocalDescription(peerConnection *wrappedPeerConnection, sdp webrtc.SessionDescription) (_ webrtc.SessionDescription, err error) {
|
|
|
|
gatherComplete := webrtc.GatheringCompletePromise(peerConnection.PeerConnection)
|
|
|
|
peerConnection.span.AddEvent("setting local description")
|
|
|
|
err = peerConnection.SetLocalDescription(sdp)
|
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("setting local description: %w", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
<-gatherComplete
|
|
|
|
peerConnection.span.AddEvent("gathering complete")
|
|
|
|
return *peerConnection.LocalDescription(), nil
|
2020-04-05 11:55:14 +08:00
|
|
|
}
|
|
|
|
|
2020-04-20 08:21:31 +08:00
|
|
|
// newOffer creates a transport and returns a WebRTC offer to be announced
|
2022-07-12 14:05:19 +08:00
|
|
|
func newOffer(
|
|
|
|
logger log.Logger,
|
|
|
|
) (
|
2020-05-03 16:45:12 +08:00
|
|
|
peerConnection *wrappedPeerConnection,
|
2020-04-20 08:21:31 +08:00
|
|
|
offer webrtc.SessionDescription,
|
|
|
|
err error,
|
|
|
|
) {
|
2022-07-12 14:05:19 +08:00
|
|
|
peerConnection, err = newPeerConnection(logger)
|
2020-04-05 11:55:14 +08:00
|
|
|
if err != nil {
|
2020-04-20 08:21:31 +08:00
|
|
|
return
|
2020-04-05 11:55:14 +08:00
|
|
|
}
|
2022-07-12 14:05:19 +08:00
|
|
|
|
|
|
|
peerConnection.span.SetAttributes(attribute.String(webrtcConnTypeKey, "offer"))
|
|
|
|
|
2020-04-20 08:21:31 +08:00
|
|
|
offer, err = peerConnection.CreateOffer(nil)
|
2020-04-05 11:55:14 +08:00
|
|
|
if err != nil {
|
2020-04-20 08:21:31 +08:00
|
|
|
peerConnection.Close()
|
|
|
|
return
|
2020-04-05 11:55:14 +08:00
|
|
|
}
|
2021-02-22 05:30:34 +08:00
|
|
|
|
2022-07-12 14:05:19 +08:00
|
|
|
offer, err = setAndGatherLocalDescription(peerConnection, offer)
|
2020-04-05 11:55:14 +08:00
|
|
|
if err != nil {
|
2020-04-20 08:21:31 +08:00
|
|
|
peerConnection.Close()
|
2020-04-05 11:55:14 +08:00
|
|
|
}
|
2020-04-20 08:21:31 +08:00
|
|
|
return
|
2020-04-05 11:55:14 +08:00
|
|
|
}
|
|
|
|
|
2020-04-20 08:21:31 +08:00
|
|
|
func initAnsweringPeerConnection(
|
2020-05-03 16:45:12 +08:00
|
|
|
peerConnection *wrappedPeerConnection,
|
2020-04-20 08:21:31 +08:00
|
|
|
offer webrtc.SessionDescription,
|
|
|
|
) (answer webrtc.SessionDescription, err error) {
|
2022-07-12 14:05:19 +08:00
|
|
|
peerConnection.span.SetAttributes(attribute.String(webrtcConnTypeKey, "answer"))
|
|
|
|
|
2020-04-20 08:21:31 +08:00
|
|
|
err = peerConnection.SetRemoteDescription(offer)
|
2020-04-05 11:55:14 +08:00
|
|
|
if err != nil {
|
2020-04-20 08:21:31 +08:00
|
|
|
return
|
2020-04-05 11:55:14 +08:00
|
|
|
}
|
2020-04-20 08:21:31 +08:00
|
|
|
answer, err = peerConnection.CreateAnswer(nil)
|
2020-04-05 11:55:14 +08:00
|
|
|
if err != nil {
|
2020-04-20 08:21:31 +08:00
|
|
|
return
|
2020-04-05 11:55:14 +08:00
|
|
|
}
|
2021-02-22 05:30:34 +08:00
|
|
|
|
2022-07-12 14:05:19 +08:00
|
|
|
answer, err = setAndGatherLocalDescription(peerConnection, answer)
|
2020-04-20 08:21:31 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-07-12 14:05:19 +08:00
|
|
|
// newAnsweringPeerConnection creates a transport from a WebRTC offer and returns a WebRTC answer to be announced.
|
|
|
|
func newAnsweringPeerConnection(
|
|
|
|
logger log.Logger,
|
|
|
|
offer webrtc.SessionDescription,
|
|
|
|
) (
|
2020-05-03 16:45:12 +08:00
|
|
|
peerConn *wrappedPeerConnection, answer webrtc.SessionDescription, err error,
|
2020-04-20 08:21:31 +08:00
|
|
|
) {
|
2022-07-12 14:05:19 +08:00
|
|
|
peerConn, err = newPeerConnection(logger)
|
2020-04-05 11:55:14 +08:00
|
|
|
if err != nil {
|
2020-04-21 16:08:43 +08:00
|
|
|
err = fmt.Errorf("failed to create new connection: %w", err)
|
2020-04-20 08:21:31 +08:00
|
|
|
return
|
|
|
|
}
|
2020-04-21 16:08:43 +08:00
|
|
|
answer, err = initAnsweringPeerConnection(peerConn, offer)
|
2020-04-20 08:21:31 +08:00
|
|
|
if err != nil {
|
2022-07-12 14:05:19 +08:00
|
|
|
peerConn.span.RecordError(err)
|
2020-04-21 16:08:43 +08:00
|
|
|
peerConn.Close()
|
2020-04-20 08:21:31 +08:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
type datachannelReadWriter interface {
|
|
|
|
datachannel.Reader
|
|
|
|
datachannel.Writer
|
|
|
|
io.Reader
|
|
|
|
io.Writer
|
2020-04-05 11:55:14 +08:00
|
|
|
}
|
|
|
|
|
2020-04-20 08:21:31 +08:00
|
|
|
type ioCloserFunc func() error
|
2020-04-05 11:55:14 +08:00
|
|
|
|
2020-04-20 08:21:31 +08:00
|
|
|
func (me ioCloserFunc) Close() error {
|
|
|
|
return me()
|
2020-04-05 11:55:14 +08:00
|
|
|
}
|
|
|
|
|
2020-04-20 08:21:31 +08:00
|
|
|
func setDataChannelOnOpen(
|
2022-07-12 14:05:19 +08:00
|
|
|
ctx context.Context,
|
2020-04-20 08:21:31 +08:00
|
|
|
dc *webrtc.DataChannel,
|
2020-05-03 16:45:12 +08:00
|
|
|
pc *wrappedPeerConnection,
|
2020-04-20 08:21:31 +08:00
|
|
|
onOpen func(closer datachannel.ReadWriteCloser),
|
|
|
|
) {
|
2020-04-05 11:55:14 +08:00
|
|
|
dc.OnOpen(func() {
|
2022-07-09 15:54:08 +08:00
|
|
|
dataChannelSpan := trace.SpanFromContext(ctx)
|
|
|
|
dataChannelSpan.AddEvent("opened")
|
2020-04-05 11:55:14 +08:00
|
|
|
raw, err := dc.Detach()
|
|
|
|
if err != nil {
|
2020-04-20 08:21:31 +08:00
|
|
|
// This shouldn't happen if the API is configured correctly, and we call from OnOpen.
|
|
|
|
panic(err)
|
2020-04-05 11:55:14 +08:00
|
|
|
}
|
2022-07-12 14:05:19 +08:00
|
|
|
//dc.OnClose()
|
2022-07-09 15:54:08 +08:00
|
|
|
onOpen(hookDataChannelCloser(raw, pc, dataChannelSpan))
|
2020-04-05 11:55:14 +08:00
|
|
|
})
|
|
|
|
}
|
2020-04-20 08:21:31 +08:00
|
|
|
|
2020-05-05 07:00:43 +08:00
|
|
|
// Hooks the datachannel's Close to Close the owning PeerConnection. The datachannel takes ownership
|
|
|
|
// and responsibility for the PeerConnection.
|
2022-07-09 15:54:08 +08:00
|
|
|
func hookDataChannelCloser(dcrwc datachannel.ReadWriteCloser, pc *wrappedPeerConnection, dataChannelSpan trace.Span) datachannel.ReadWriteCloser {
|
2020-04-20 08:21:31 +08:00
|
|
|
return struct {
|
|
|
|
datachannelReadWriter
|
|
|
|
io.Closer
|
|
|
|
}{
|
|
|
|
dcrwc,
|
2022-07-09 15:54:08 +08:00
|
|
|
ioCloserFunc(func() error {
|
|
|
|
dcrwc.Close()
|
|
|
|
pc.Close()
|
|
|
|
dataChannelSpan.End()
|
|
|
|
return nil
|
|
|
|
}),
|
2020-04-20 08:21:31 +08:00
|
|
|
}
|
|
|
|
}
|