diff --git a/sdp/helpers.go b/sdp/helpers.go index dc00686..197166b 100644 --- a/sdp/helpers.go +++ b/sdp/helpers.go @@ -38,15 +38,21 @@ func GetAudioDest(s *sdp.SessionDescription, audio *sdp.MediaDescription) (netip return netip.AddrPort{}, errors.New("no audio in sdp") } - // pick media-level c=; if absent, fall back to session-level c= - ci := audio.ConnectionInformation - if ci == nil { - ci = s.ConnectionInformation + // A c= line without an address parses to a non-nil ConnectionInformation + // with a nil Address, so it has to be checked separately. + connAddr := func(ci *sdp.ConnectionInformation) string { + if ci == nil || ci.NetworkType != "IN" || ci.Address == nil { + return "" + } + return ci.Address.Address + } + + // pick media-level c=, then session-level c=, then the o= address + addr := connAddr(audio.ConnectionInformation) + if addr == "" { + addr = connAddr(s.ConnectionInformation) } - var addr string - if ci != nil && ci.NetworkType == "IN" { - addr = ci.Address.Address - } else if s.Origin.NetworkType == "IN" { + if addr == "" && s.Origin.NetworkType == "IN" { addr = s.Origin.UnicastAddress } if addr == "" { diff --git a/sdp/helpers_test.go b/sdp/helpers_test.go index 1a96cbd..7a05e71 100644 --- a/sdp/helpers_test.go +++ b/sdp/helpers_test.go @@ -80,6 +80,87 @@ func TestGetAudioDest(t *testing.T) { }, expected: netip.MustParseAddrPort("1.2.3.4:1234"), }, + { + name: "media level connection info without address", + session: &sdp.SessionDescription{ + ConnectionInformation: &sdp.ConnectionInformation{ + NetworkType: "IN", + AddressType: "IP4", + Address: &sdp.Address{Address: "1.2.3.4"}, + }, + }, + audio: &sdp.MediaDescription{ + MediaName: sdp.MediaName{ + Media: "audio", + Port: sdp.RangedPort{Value: 1234}, + }, + ConnectionInformation: &sdp.ConnectionInformation{ + NetworkType: "IN", + AddressType: "IP4", + }, + }, + expected: netip.MustParseAddrPort("1.2.3.4:1234"), + }, + { + name: "connection info without address falls back to origin", + session: &sdp.SessionDescription{ + Origin: sdp.Origin{ + NetworkType: "IN", + AddressType: "IP4", + UnicastAddress: "5.6.7.8", + }, + ConnectionInformation: &sdp.ConnectionInformation{ + NetworkType: "IN", + AddressType: "IP4", + }, + }, + audio: &sdp.MediaDescription{ + MediaName: sdp.MediaName{ + Media: "audio", + Port: sdp.RangedPort{Value: 1234}, + }, + }, + expected: netip.MustParseAddrPort("5.6.7.8:1234"), + }, + { + name: "non-IN connection info falls back to session level", + session: &sdp.SessionDescription{ + ConnectionInformation: &sdp.ConnectionInformation{ + NetworkType: "IN", + AddressType: "IP4", + Address: &sdp.Address{Address: "1.2.3.4"}, + }, + }, + audio: &sdp.MediaDescription{ + MediaName: sdp.MediaName{ + Media: "audio", + Port: sdp.RangedPort{Value: 1234}, + }, + ConnectionInformation: &sdp.ConnectionInformation{ + NetworkType: "FOO", + AddressType: "IP4", + Address: &sdp.Address{Address: "9.9.9.9"}, + }, + }, + expected: netip.MustParseAddrPort("1.2.3.4:1234"), + }, + { + name: "no usable address anywhere", + session: &sdp.SessionDescription{ + ConnectionInformation: &sdp.ConnectionInformation{ + NetworkType: "IN", + AddressType: "IP4", + }, + }, + audio: &sdp.MediaDescription{ + MediaName: sdp.MediaName{ + Media: "audio", + Port: sdp.RangedPort{Value: 1234}, + }, + }, + expected: netip.AddrPort{}, + error: true, + }, { name: "nil session", session: nil, diff --git a/sdp/offer_test.go b/sdp/offer_test.go index 45e5e32..efebf9a 100644 --- a/sdp/offer_test.go +++ b/sdp/offer_test.go @@ -529,6 +529,55 @@ a=sendrecv `, wantErr: true, }, + { + // The media-level c= has no address, so the session-level one is used. + name: "media level c= without address", + sdp: `v=0 +o=- 1234567890 1234567890 IN IP4 1.2.3.4 +s=LiveKit +c=IN IP4 1.2.3.4 +t=0 0 +m=audio 1234 RTP/AVP 0 101 +c=IN IP4 +a=rtpmap:0 PCMU/8000 +a=rtpmap:101 telephone-event/8000 +a=ptime:20 +a=sendrecv +`, + wantErr: false, + }, + { + name: "session level c= without address falls back to o=", + sdp: `v=0 +o=- 1234567890 1234567890 IN IP4 1.2.3.4 +s=LiveKit +c=IN IP4 +t=0 0 +m=audio 1234 RTP/AVP 0 101 +a=rtpmap:0 PCMU/8000 +a=rtpmap:101 telephone-event/8000 +a=ptime:20 +a=sendrecv +`, + wantErr: false, + }, + { + // Neither c= nor o= carries an address. pion defaults the o= address + // to 0.0.0.0, so this parses rather than failing. + name: "no address in c= or o=", + sdp: `v=0 +o=- 1234567890 1234567890 IN IP4 +s=LiveKit +c=IN IP4 +t=0 0 +m=audio 1234 RTP/AVP 0 101 +a=rtpmap:0 PCMU/8000 +a=rtpmap:101 telephone-event/8000 +a=ptime:20 +a=sendrecv +`, + wantErr: false, + }, } for _, test := range tests { @@ -543,6 +592,29 @@ a=sendrecv } } +// An unusable media-level c= must fall through to the session-level c=, not all +// the way to o=. +func TestParseOfferConnectionAddressFallback(t *testing.T) { + g := media.GlobalCodecs() + + const sdpData = `v=0 +o=- 1234567890 1234567890 IN IP4 5.6.7.8 +s=LiveKit +c=IN IP4 1.2.3.4 +t=0 0 +m=audio 1234 RTP/AVP 0 101 +c=IN IP4 +a=rtpmap:0 PCMU/8000 +a=rtpmap:101 telephone-event/8000 +a=ptime:20 +a=sendrecv +` + + offer, err := ParseOfferWith(g, []byte(sdpData)) + require.NoError(t, err) + require.Equal(t, netip.MustParseAddrPort("1.2.3.4:1234"), offer.Addr) +} + func TestParseOfferSRTP(t *testing.T) { g := media.GlobalCodecs()