diff --git a/wsnet/dial.go b/wsnet/dial.go index 1e7dd87a..c8f0f7e3 100644 --- a/wsnet/dial.go +++ b/wsnet/dial.go @@ -113,17 +113,16 @@ func (d *Dialer) negotiate() (err error) { go func() { defer close(errCh) - err := waitForDataChannelOpen(context.Background(), d.ctrl) + err := waitForConnectionOpen(context.Background(), d.rtc) if err != nil { _ = d.conn.Close() errCh <- err return } - d.ctrlrw, err = d.ctrl.Detach() - if err != nil { - errCh <- err - } - _ = d.conn.Close() + go func() { + // Closing this connection took 30ms+. + _ = d.conn.Close() + }() }() for { @@ -179,7 +178,20 @@ func (d *Dialer) Close() error { // Ping sends a ping through the control channel. func (d *Dialer) Ping(ctx context.Context) error { - _, err := d.ctrlrw.Write([]byte{'a'}) + // Since we control the client and server we could open this + // data channel with `Negotiated` true to reduce traffic being + // sent when the RTC connection is opened. + err := waitForDataChannelOpen(context.Background(), d.ctrl) + if err != nil { + return err + } + if d.ctrlrw == nil { + d.ctrlrw, err = d.ctrl.Detach() + if err != nil { + return err + } + } + _, err = d.ctrlrw.Write([]byte{'a'}) if err != nil { return fmt.Errorf("write: %w", err) } diff --git a/wsnet/listen.go b/wsnet/listen.go index 9a9a30db..5a159e52 100644 --- a/wsnet/listen.go +++ b/wsnet/listen.go @@ -103,7 +103,7 @@ func (l *listener) dial(ctx context.Context) (<-chan error, error) { // Negotiates the handshake protocol over the connection provided. // This functions control-flow is important to readability, // so the cognitive overload linter has been disabled. -// nolint:gocognit +// nolint:gocognit,nestif func (l *listener) negotiate(conn net.Conn) { var ( err error @@ -172,11 +172,17 @@ func (l *listener) negotiate(conn net.Conn) { closeError(err) return } + rtc.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) { + if pcs == webrtc.PeerConnectionStateConnecting { + return + } + _ = conn.Close() + }) + flushCandidates := proxyICECandidates(rtc, conn) l.connClosersMut.Lock() l.connClosers = append(l.connClosers, rtc) l.connClosersMut.Unlock() rtc.OnDataChannel(l.handle) - flushCandidates := proxyICECandidates(rtc, conn) err = rtc.SetRemoteDescription(*msg.Offer) if err != nil { closeError(fmt.Errorf("apply offer: %w", err)) diff --git a/wsnet/rtc.go b/wsnet/rtc.go index bd08baf0..9c07663d 100644 --- a/wsnet/rtc.go +++ b/wsnet/rtc.go @@ -155,6 +155,8 @@ func dialICEURL(server webrtc.ICEServer, rawURL string, options *DialICEOptions) // Generalizes creating a new peer connection with consistent options. func newPeerConnection(servers []webrtc.ICEServer) (*webrtc.PeerConnection, error) { se := webrtc.SettingEngine{} + se.SetNetworkTypes([]webrtc.NetworkType{webrtc.NetworkTypeUDP4}) + se.SetSrflxAcceptanceMinWait(0) se.DetachDataChannels() se.SetICETimeouts(time.Second*5, time.Second*5, time.Second*2) @@ -165,6 +167,7 @@ func newPeerConnection(servers []webrtc.ICEServer) (*webrtc.PeerConnection, erro if server.Credential != nil && len(server.URLs) == 1 { url, err := ice.ParseURL(server.URLs[0]) if err == nil && url.Proto == ice.ProtoTypeTCP { + se.SetNetworkTypes([]webrtc.NetworkType{webrtc.NetworkTypeTCP4, webrtc.NetworkTypeTCP6}) se.SetRelayAcceptanceMinWait(0) } } @@ -213,6 +216,25 @@ func proxyICECandidates(conn *webrtc.PeerConnection, w io.Writer) func() { } } +// Waits for a PeerConnection to hit the open state. +func waitForConnectionOpen(ctx context.Context, conn *webrtc.PeerConnection) error { + if conn.ConnectionState() == webrtc.PeerConnectionStateConnected { + return nil + } + ctx, cancelFunc := context.WithTimeout(ctx, time.Second*15) + defer cancelFunc() + conn.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) { + if pcs == webrtc.PeerConnectionStateConnected { + cancelFunc() + } + }) + <-ctx.Done() + if ctx.Err() == context.DeadlineExceeded { + return ctx.Err() + } + return nil +} + // Waits for a DataChannel to hit the open state. func waitForDataChannelOpen(ctx context.Context, channel *webrtc.DataChannel) error { if channel.ReadyState() == webrtc.DataChannelStateOpen {