Skip to content

Commit 1b864df

Browse files
committed
conn.go: Include selected candidate pair to Addr
1 parent 9b4af1d commit 1b864df

File tree

3 files changed

+91
-74
lines changed

3 files changed

+91
-74
lines changed

Diff for: conn.go

+57-54
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ func (*Conn) SetWriteDeadline(time.Time) error {
143143
func (c *Conn) LocalAddr() net.Addr {
144144
addr := c.local
145145
addr.ConnectionID = c.id
146+
pair, _ := c.ice.GetSelectedCandidatePair()
147+
if pair != nil {
148+
addr.SelectedCandidate = pair.Local
149+
}
146150
return &addr
147151
}
148152

@@ -154,6 +158,11 @@ func (c *Conn) RemoteAddr() net.Addr {
154158
c.candidatesMu.Lock()
155159
addr.Candidates = slices.Clone(c.candidates)
156160
c.candidatesMu.Unlock()
161+
162+
pair, _ := c.ice.GetSelectedCandidatePair()
163+
if pair != nil {
164+
addr.SelectedCandidate = pair.Remote
165+
}
157166
return addr
158167
}
159168

@@ -176,9 +185,15 @@ func (c *Conn) Close() (err error) {
176185

177186
c.negotiator.handleClose(c)
178187

188+
if c.reliable != nil {
189+
err = c.reliable.Close()
190+
}
191+
if c.unreliable != nil {
192+
err = errors.Join(err, c.unreliable.Close())
193+
}
194+
179195
err = errors.Join(
180-
c.reliable.Close(),
181-
c.unreliable.Close(),
196+
err,
182197
c.sctp.Stop(),
183198
c.dtls.Stop(),
184199
c.ice.Stop(),
@@ -254,7 +269,6 @@ func (c *Conn) handleSignal(signal *Signal) error {
254269
Typ: webrtc.ICECandidateType(candidate.Type()),
255270
TCPType: candidate.TCPType().String(),
256271
}
257-
258272
if r := candidate.RelatedAddress(); r != nil {
259273
i.RelatedAddress, i.RelatedPort = r.Address, uint16(r.Port)
260274
}
@@ -323,9 +337,9 @@ func parseDescription(d *sdp.SessionDescription) (*description, error) {
323337
}
324338
var role webrtc.DTLSRole
325339
switch attr {
326-
case "active":
340+
case sdp.ConnectionRoleActive.String():
327341
role = webrtc.DTLSRoleClient
328-
case "actpass":
342+
case sdp.ConnectionRoleActpass.String():
329343
role = webrtc.DTLSRoleServer
330344
default:
331345
return nil, fmt.Errorf("invalid setup attribute: %s", attr)
@@ -360,14 +374,6 @@ func parseDescription(d *sdp.SessionDescription) (*description, error) {
360374
}, nil
361375
}
362376

363-
// description contains parameters for calling the Start method of ICE, DTLS and SCTP transport.
364-
//
365-
// A description may be parsed by a negotiator (Listener or Dialer) using parseDescription
366-
// with a [sdp.SessionDescription] decoded from a Signal of SignalTypeOffer or SignalTypeAnswer.
367-
//
368-
// A description may be filled in by a negotiator (Listener or Dialer) to encode
369-
// a local description of a Conn.
370-
371377
// description contains parameters necessary for starting ICE, DTLS, and SCTP transport within a Conn.
372378
//
373379
// It may be created by parsing a [sdp.SessionDescription] signaled from a remote connection or filed
@@ -386,7 +392,7 @@ type description struct {
386392
// parameters of each transport within a Conn.
387393
func (desc description) encode() ([]byte, error) {
388394
d := &sdp.SessionDescription{
389-
Version: 0x2,
395+
Version: 0x0,
390396
Origin: sdp.Origin{
391397
Username: "-",
392398
SessionID: rand.Uint64(),
@@ -400,53 +406,50 @@ func (desc description) encode() ([]byte, error) {
400406
{},
401407
},
402408
Attributes: []sdp.Attribute{
403-
{Key: "group", Value: "BUNDLE 0"},
404-
{Key: "extmap-allow-mixed", Value: ""},
405-
{Key: "msid-semantic", Value: " WMS"},
409+
{Key: sdp.AttrKeyGroup, Value: "BUNDLE 0"},
410+
sdp.NewPropertyAttribute(sdp.AttrKeyExtMapAllowMixed),
411+
{Key: sdp.AttrKeyMsidSemantic, Value: " WMS"},
406412
},
407-
MediaDescriptions: []*sdp.MediaDescription{
408-
{
409-
MediaName: sdp.MediaName{
410-
Media: "application",
411-
Port: sdp.RangedPort{Value: 9},
412-
Protos: []string{"UDP", "DTLS", "SCTP"},
413-
Formats: []string{"webrtc-datachannel"},
414-
},
415-
ConnectionInformation: &sdp.ConnectionInformation{
416-
NetworkType: "IN",
417-
AddressType: "IP4",
418-
Address: &sdp.Address{Address: "0.0.0.0"},
419-
},
420-
Attributes: []sdp.Attribute{
421-
{Key: "ice-ufrag", Value: desc.ice.UsernameFragment},
422-
{Key: "ice-pwd", Value: desc.ice.Password},
423-
{Key: "ice-options", Value: "trickle"},
424-
{Key: "fingerprint", Value: fmt.Sprintf("%s %s",
425-
desc.dtls.Fingerprints[0].Algorithm,
426-
desc.dtls.Fingerprints[0].Value,
427-
)},
428-
desc.setupAttribute(),
429-
{Key: "mid", Value: "0"},
430-
{Key: "sctp-port", Value: "5000"},
431-
{Key: "max-message-size", Value: strconv.FormatUint(uint64(desc.sctp.MaxMessageSize), 10)},
432-
},
413+
}
414+
415+
media := &sdp.MediaDescription{
416+
MediaName: sdp.MediaName{
417+
Media: "application",
418+
Port: sdp.RangedPort{Value: 9},
419+
Protos: []string{"UDP", "DTLS", "SCTP"},
420+
Formats: []string{"webrtc-datachannel"},
421+
},
422+
ConnectionInformation: &sdp.ConnectionInformation{
423+
NetworkType: "IN",
424+
AddressType: "IP4",
425+
Address: &sdp.Address{
426+
Address: "0.0.0.0",
433427
},
434428
},
435429
}
436-
return d.Marshal()
430+
media.WithICECredentials(desc.ice.UsernameFragment, desc.ice.Password)
431+
media.WithValueAttribute("ice-options", "trickle")
432+
for _, fingerprint := range desc.dtls.Fingerprints {
433+
media.WithFingerprint(fingerprint.Algorithm, fingerprint.Value)
434+
}
435+
media.WithValueAttribute(sdp.AttrKeyConnectionSetup, desc.connectionRole(desc.dtls.Role).String())
436+
media.WithValueAttribute(sdp.AttrKeyMID, "0")
437+
media.WithValueAttribute("sctp-port", "5000")
438+
media.WithValueAttribute("max-message-size", strconv.FormatUint(uint64(desc.sctp.MaxMessageSize), 10))
439+
440+
return d.WithMedia(media).Marshal()
437441
}
438442

439-
// setupAttribute returns a [sdp.Attribute] with the key 'setup' indicating the local
440-
// DTLS role as either 'active' or 'actpass'. It is called by encode to include the role
441-
// in the media description of the local [sdp.SessionDescription].
442-
func (desc description) setupAttribute() sdp.Attribute {
443-
attr := sdp.Attribute{Key: "setup"}
444-
if desc.dtls.Role == webrtc.DTLSRoleServer {
445-
attr.Value = "actpass"
446-
} else {
447-
attr.Value = "active"
443+
// connectionRole returns a [sdp.ConnectionRole] indicating the local DTLS role. It is called
444+
// by encode to include the role into the media description of local [sdp.SessionDescription]
445+
// as a [sdp.Attribute] of 'setup'.
446+
func (desc description) connectionRole(role webrtc.DTLSRole) sdp.ConnectionRole {
447+
switch role {
448+
case webrtc.DTLSRoleServer:
449+
return sdp.ConnectionRoleActpass
450+
default:
451+
return sdp.ConnectionRoleActive
448452
}
449-
return attr
450453
}
451454

452455
// newConn creates a Conn from the ICE, DTLS and SCTP transport associated with the IDs.

Diff for: dial.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ type Dialer struct {
2525
// has been negotiated by Dialer.
2626
Log *slog.Logger
2727

28-
// API specifies custom configuration for WebRTC transports, and data channels. If left as nil, a new [webrtc.API]
29-
// will be set from [webrtc.NewAPI]. The webrtc.SettingEngine of the API should not allow detaching data channels
30-
// (by calling [webrtc.SettingEngine.DetachDataChannels]) as it requires additional steps on the Conn.
31-
3228
// API specifies custom configuration for WebRTC transports and data channels. If nil, a new [webrtc.API] will be
3329
// set from [webrtc.NewAPI]. The [webrtc.SettingEngine] of the API should not allow detaching data channels, as it requires
3430
// additional steps on the Conn (which cannot be determined by the Conn).
@@ -248,17 +244,21 @@ func (d Dialer) startTransports(ctx context.Context, conn *Conn, desc *descripti
248244
if err := withContext(ctx, func() error {
249245
var err error
250246
conn.reliable, err = d.API.NewDataChannel(conn.sctp, &webrtc.DataChannelParameters{
251-
Label: "ReliableDataChannel",
247+
Label: "ReliableDataChannel",
248+
Ordered: true,
252249
})
253250
return err
254251
}); err != nil {
255252
return fmt.Errorf("create ReliableDataChannel: %w", err)
256253
}
257254
if err := withContext(ctx, func() error {
258-
var err error
255+
var (
256+
err error
257+
maxRetransmits uint16 = 0
258+
)
259259
conn.unreliable, err = d.API.NewDataChannel(conn.sctp, &webrtc.DataChannelParameters{
260-
Label: "UnreliableDataChannel",
261-
Ordered: false,
260+
Label: "UnreliableDataChannel",
261+
MaxRetransmits: &maxRetransmits,
262262
})
263263
return err
264264
}); err != nil {

Diff for: listener.go

+26-12
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ type Listener struct {
7777

7878
stop func()
7979
closed chan struct{}
80-
once sync.Once
8180
}
8281

8382
// Accept waits for and returns the next [Conn] to the Listener. An error may be
@@ -113,6 +112,12 @@ type Addr struct {
113112
// signaled from a remote connection. ICE candidates are used to determine the UDP/TCP addresses
114113
// for establishing ICE transport and can be used to determine the network address of the connection.
115114
Candidates []webrtc.ICECandidate
115+
116+
// SelectedCandidate is the candidate selected to connect with the ICE transport within a Conn.
117+
// An ICE candidate may be used to determine the UDP/TCP address of the connection. It may be nil
118+
// if the Conn has been closed, or if the Conn has encountered an error when obtaining the selected
119+
// ICE candidate pair.
120+
SelectedCandidate *webrtc.ICECandidate
116121
}
117122

118123
// String formats the Addr as a string.
@@ -125,6 +130,12 @@ func (addr *Addr) String() string {
125130
b.WriteString(strconv.FormatUint(addr.ConnectionID, 10))
126131
b.WriteByte(')')
127132
}
133+
if addr.SelectedCandidate != nil {
134+
b.WriteByte(' ')
135+
b.WriteByte('(')
136+
b.WriteString(addr.SelectedCandidate.String())
137+
b.WriteByte(')')
138+
}
128139
return b.String()
129140
}
130141

@@ -332,7 +343,7 @@ func (l *Listener) handleConn(conn *Conn, d *description) {
332343
var err error
333344
defer func() {
334345
if err != nil {
335-
l.connections.Delete(conn.remoteAddr().String()) // Stop notifying for the Conn.
346+
_ = conn.Close() // Stop notifying for the Conn.
336347

337348
if errors.Is(err, context.DeadlineExceeded) {
338349
if err := l.signaling.Signal(&Signal{
@@ -368,6 +379,9 @@ func (l *Listener) handleConn(conn *Conn, d *description) {
368379
select {
369380
case <-ctx.Done():
370381
err = ctx.Err()
382+
case <-l.closed:
383+
case <-conn.closed:
384+
return
371385
case <-conn.candidateReceived:
372386
conn.log.Debug("received first candidate")
373387
if err = l.startTransports(ctx, conn, d); err != nil {
@@ -398,21 +412,18 @@ func (l *Listener) startTransports(ctx context.Context, conn *Conn, d *descripti
398412
}
399413

400414
conn.log.Debug("starting SCTP transport")
401-
var (
402-
once = new(sync.Once)
403-
opened = make(chan struct{}, 1)
404-
)
415+
opened := make(chan struct{}, 1)
405416
conn.sctp.OnDataChannelOpened(func(channel *webrtc.DataChannel) {
406417
switch channel.Label() {
407418
case "ReliableDataChannel":
408419
conn.reliable = channel
409420
case "UnreliableDataChannel":
410421
conn.unreliable = channel
422+
default:
423+
return
411424
}
412425
if conn.reliable != nil && conn.unreliable != nil {
413-
once.Do(func() {
414-
close(opened)
415-
})
426+
close(opened)
416427
}
417428
})
418429
if err := withContext(ctx, func() error {
@@ -449,12 +460,15 @@ func withContext(ctx context.Context, f func() error) error {
449460

450461
// Close closes the Listener, ensuring that any blocking methods will return [net.ErrClosed] as an error.
451462
func (l *Listener) Close() error {
452-
l.once.Do(func() {
463+
select {
464+
case <-l.closed:
465+
return nil
466+
default:
453467
close(l.closed)
454468
close(l.incoming)
455469
l.stop()
456-
})
457-
return nil
470+
return nil
471+
}
458472
}
459473

460474
// A signalError may be returned by the methods of Listener to handle incoming Signals signaled from the

0 commit comments

Comments
 (0)