Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion dtlstransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package webrtc

import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
Expand Down Expand Up @@ -301,6 +302,20 @@ func (t *DTLSTransport) role() DTLSRole {

// Start DTLS transport negotiation with the parameters of the remote DTLS transport.
func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error {
ctx := context.Background()
if t.api.settingEngine.dtls.connectContextMaker != nil {
var cancel func()
ctx, cancel = t.api.settingEngine.dtls.connectContextMaker()
defer cancel()
}

return t.StartContext(ctx, remoteParameters)
}

// StartContext starts DTLS transport negotiation with the parameters of the remote DTLS
// transport. If the context is canceled before the DTLS handshake is complete, the handshake
// is interrupted and an error is returned.
func (t *DTLSTransport) StartContext(ctx context.Context, remoteParameters DTLSParameters) error {
role, certificate, err := t.prepareStart(remoteParameters)
if err != nil {
return err
Expand All @@ -319,7 +334,7 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error {
return t.failStart(err)
}

if err = t.handshakeDTLS(dtlsConn); err != nil {
if err = dtlsConn.HandshakeContext(ctx); err != nil {
dtlsEndpoint.SetOnClose(nil)
_ = dtlsConn.Close()

Expand Down
2 changes: 1 addition & 1 deletion dtlstransport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func (c *failingPacketConn) SetReadDeadline(time.Time) error { return nil }
func (c *failingPacketConn) SetWriteDeadline(time.Time) error { return nil }

func TestDTLSTransport_Start_ErrICEConnectionNotStarted(t *testing.T) {
transport := &DTLSTransport{state: DTLSTransportStateNew}
transport := &DTLSTransport{api: NewAPI(), state: DTLSTransportStateNew}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm sorry can we revert this and check if t.api is null? calls with t.api null shouldn't panic.


err := transport.Start(DTLSParameters{Role: DTLSRoleServer})
assert.ErrorIs(t, err, errICEConnectionNotStarted)
Expand Down
28 changes: 26 additions & 2 deletions icetransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,20 @@ func NewICETransport(gatherer *ICEGatherer, loggerFactory logging.LoggerFactory)
}

// Start incoming connectivity checks based on its configured role.
func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *ICERole) error { //nolint:cyclop
func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *ICERole) error {
return t.StartContext(context.Background(), gatherer, params, role)
}

// StartContext incoming connectivity checks based on its configured role.
// If the context is canceled, the ICE transport will stop.
//
//nolint:cyclop
func (t *ICETransport) StartContext(
ctx context.Context,
gatherer *ICEGatherer,
params ICEParameters,
role *ICERole,
) error {
t.lock.Lock()
defer t.lock.Unlock()

Expand Down Expand Up @@ -134,7 +147,7 @@ func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *
}
t.role = *role

ctx, ctxCancel := context.WithCancel(context.Background())
ctx, ctxCancel := context.WithCancel(ctx)
t.ctxCancel = ctxCancel

Comment on lines 98 to 152

@JoTurk JoTurk May 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a problem in the current ICE design, but it affects this PR, currently https://github.com/pion/ice/blob/main/transport.go ICE just returns an error if the context is cancelled after the connection checks started, and it doesn't call agent's close by itself (we should change in the next major version). But for this PR we should also call close when the context get cancelled.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Should be good now

// Drop the lock here to allow ICE candidates to be
Expand All @@ -161,6 +174,17 @@ func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *
// Reacquire the lock to set the connection/mux
t.lock.Lock()
if err != nil {
if ctxErr := ctx.Err(); ctxErr != nil {
t.lock.Unlock()
_ = t.Stop()
t.lock.Lock()

return ctxErr
}
Comment on lines +177 to +183

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use context.Cause(ctx) instead of ctx.Err()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@sirzooro I don't think we would want a Pion api to return aribitary erros espically if this is used with third party created contexts, this will be kinda unpredicatable. and also sounds like a policy or API change across all of pion in a new major version and not just a single API and keep the rest.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would be useful when used with complex context trees. Cancellation cause set few levels up the tree is propagated down when context is cancelled. This allows to improve logs, it is immediately obvious that connection was terminated due to something what happened elsewhere. Generic error "context cancelled" does not carry this information.

@JoTurk JoTurk Jun 5, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see the point, but it's outside of the scope of this change, we shouldn't introduce a new pattern or error handling randomly in a single new API and keep the rest. Because having unpredictable APIs and maintaining different behaviors is way worse than losing some context, and If we would do it, we should do it across all of pion and we should use a linter to force it, and it should be in a new release.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@sirzooro I made an issue #3446 does this look good for you?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, I am ok with this.


ctxCancel()
t.ctxCancel = nil

return err
}
Comment thread
HashimTheArab marked this conversation as resolved.

Expand Down
29 changes: 29 additions & 0 deletions icetransport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package webrtc

import (
"context"
"sync"
"sync/atomic"
"testing"
Expand All @@ -15,6 +16,34 @@ import (
"github.com/stretchr/testify/assert"
)

func TestICETransport_StartContextClosesOnCancel(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()

api := NewAPI()
gatherer, err := api.NewICEGatherer(ICEGatherOptions{})
assert.NoError(t, err)

remoteGatherer, err := api.NewICEGatherer(ICEGatherOptions{})
assert.NoError(t, err)

params, err := remoteGatherer.GetLocalParameters()
assert.NoError(t, err)
defer func() {
assert.NoError(t, remoteGatherer.Close())
}()

transport := api.NewICETransport(gatherer)
ctx, cancel := context.WithCancel(context.Background())
cancel()

controlling := ICERoleControlling
err = transport.StartContext(ctx, nil, params, &controlling)
assert.ErrorIs(t, err, context.Canceled)
assert.Equal(t, ICEGathererStateClosed, gatherer.State())
assert.Nil(t, gatherer.getAgent())
}

func TestICETransport_OnConnectionStateChange(t *testing.T) {
report := test.CheckRoutines(t)
defer report()
Expand Down