Improve WebRTC candidates handling

This commit is contained in:
Alex X
2024-05-10 17:26:52 +03:00
parent f7b98044e6
commit 205018c96a
8 changed files with 288 additions and 118 deletions
+54 -13
View File
@@ -2,6 +2,7 @@ package webrtc
import (
"net"
"slices"
"github.com/pion/interceptor"
"github.com/pion/webrtc/v3"
@@ -15,7 +16,15 @@ func NewAPI() (*webrtc.API, error) {
return NewServerAPI("", "", nil)
}
func NewServerAPI(address, network string, candidateHost []string) (*webrtc.API, error) {
type Filters struct {
Candidates []string `yaml:"candidates"`
Interfaces []string `yaml:"interfaces"`
IPs []string `yaml:"ips"`
Networks []string `yaml:"networks"`
UDPPorts []uint16 `yaml:"udp_ports"`
}
func NewServerAPI(network, address string, filters *Filters) (*webrtc.API, error) {
// for debug logs add to env: `PION_LOG_DEBUG=all`
m := &webrtc.MediaEngine{}
//if err := m.RegisterDefaultCodecs(); err != nil {
@@ -32,23 +41,55 @@ func NewServerAPI(address, network string, candidateHost []string) (*webrtc.API,
s := webrtc.SettingEngine{}
// disable listen on Hassio docker interfaces
s.SetInterfaceFilter(func(name string) bool {
return name != "hassio" && name != "docker0"
})
// fix https://github.com/pion/webrtc/pull/2407
s.SetDTLSInsecureSkipHelloVerify(true)
s.SetReceiveMTU(ReceiveMTU)
if filters != nil && filters.Interfaces != nil {
s.SetIncludeLoopbackCandidate(true)
s.SetInterfaceFilter(func(name string) bool {
return slices.Contains(filters.Interfaces, name)
})
} else {
// disable listen on Hassio docker interfaces
s.SetInterfaceFilter(func(name string) bool {
return name != "hassio" && name != "docker0"
})
}
s.SetNAT1To1IPs(candidateHost, webrtc.ICECandidateTypeHost)
if filters != nil && filters.IPs != nil {
s.SetIncludeLoopbackCandidate(true)
s.SetIPFilter(func(ip net.IP) bool {
return slices.Contains(filters.IPs, ip.String())
})
}
// by default enable IPv4 + IPv6 modes
s.SetNetworkTypes([]webrtc.NetworkType{
webrtc.NetworkTypeUDP4, webrtc.NetworkTypeTCP4,
webrtc.NetworkTypeUDP6, webrtc.NetworkTypeTCP6,
})
if filters != nil && filters.Networks != nil {
var networkTypes []webrtc.NetworkType
for _, s := range filters.Networks {
if networkType, err := webrtc.NewNetworkType(s); err == nil {
networkTypes = append(networkTypes, networkType)
}
}
s.SetNetworkTypes(networkTypes)
} else {
s.SetNetworkTypes([]webrtc.NetworkType{
webrtc.NetworkTypeUDP4, webrtc.NetworkTypeUDP6,
webrtc.NetworkTypeTCP4, webrtc.NetworkTypeTCP6,
})
}
if filters != nil && len(filters.UDPPorts) == 2 {
_ = s.SetEphemeralUDPPortRange(filters.UDPPorts[0], filters.UDPPorts[1])
}
//if len(hosts) != 0 {
// // support only: host, srflx
// if candidateType, err := webrtc.NewICECandidateType(hosts[0]); err == nil {
// s.SetNAT1To1IPs(hosts[1:], candidateType)
// } else {
// s.SetNAT1To1IPs(hosts, 0) // 0 = host
// }
//}
if address != "" {
if network == "" || network == "tcp" {
+27 -24
View File
@@ -273,38 +273,41 @@ func MimeType(codec *core.Codec) string {
panic("not implemented")
}
// 4.1.2.2. Guidelines for Choosing Type and Local Preferences
// The RECOMMENDED values are 126 for host candidates, 100
// for server reflexive candidates, 110 for peer reflexive candidates,
// and 0 for relayed candidates.
const PriorityTypeHostUDP = (1 << 24) * int(126)
const PriorityTypeHostTCP = (1 << 24) * int(126-27)
const PriorityLocalUDP = (1 << 8) * int(65535)
const PriorityLocalTCPPassive = (1 << 8) * int((1<<13)*4+8191)
const PriorityComponentRTP = 1 * int(256-ice.ComponentRTP)
func CandidateManualHostUDP(host, port string, offset int) string {
foundation := crc32.ChecksumIEEE([]byte("host" + host + "udp4"))
priority := PriorityTypeHostUDP + PriorityLocalUDP + PriorityComponentRTP + offset
func CandidateICE(network, host, port string, priority uint32) string {
// 1. Foundation
// 2. Component, always 1 because RTP
// 3. udp or tcp
// 3. "udp" or "tcp"
// 4. Priority
// 5. Host - IP4 or IP6 or domain name
// 6. Port
// 7. typ host
return fmt.Sprintf("candidate:%d 1 udp %d %s %s typ host", foundation, priority, host, port)
// 7. "typ host"
foundation := crc32.ChecksumIEEE([]byte("host" + host + network + "4"))
s := fmt.Sprintf("candidate:%d 1 %s %d %s %s typ host", foundation, network, priority, host, port)
if network == "tcp" {
return s + " tcptype passive"
}
return s
}
func CandidateManualHostTCPPassive(host, port string, offset int) string {
foundation := crc32.ChecksumIEEE([]byte("host" + host + "tcp4"))
priority := PriorityTypeHostTCP + PriorityLocalTCPPassive + PriorityComponentRTP + offset
// Priority = type << 24 + local << 8 + component
// https://www.rfc-editor.org/rfc/rfc8445#section-5.1.2.1
return fmt.Sprintf(
"candidate:%d 1 tcp %d %s %s typ host tcptype passive", foundation, priority, host, port,
)
const PriorityHostUDP uint32 = 0x001F_FFFF |
126<<24 | // udp host
7<<21 // udp
const PriorityHostTCPPassive uint32 = 0x001F_FFFF |
99<<24 | // tcp host
4<<21 // tcp passive
// CandidateHostPriority (lower indexes has a higher priority)
func CandidateHostPriority(network string, index int) uint32 {
switch network {
case "udp":
return PriorityHostUDP - uint32(index)
case "tcp":
return PriorityHostTCPPassive - uint32(index)
}
return 0
}
func UnmarshalICEServers(b []byte) ([]webrtc.ICEServer, error) {
+36 -5
View File
@@ -81,11 +81,42 @@ transeivers:
return c.pc.LocalDescription().SDP, nil
}
func (c *Conn) GetCompleteAnswer() (answer string, err error) {
if _, err = c.GetAnswer(); err != nil {
return
// GetCompleteAnswer - get SDP answer with candidates inside
func (c *Conn) GetCompleteAnswer(candidates []string, filter func(*webrtc.ICECandidate) bool) (string, error) {
var done = make(chan struct{})
c.pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate != nil {
if filter == nil || filter(candidate) {
candidates = append(candidates, candidate.ToJSON().Candidate)
}
} else {
done <- struct{}{}
}
})
answer, err := c.GetAnswer()
if err != nil {
return "", err
}
<-webrtc.GatheringCompletePromise(c.pc)
return c.pc.LocalDescription().SDP, nil
<-done
sd := &sdp.SessionDescription{}
if err = sd.Unmarshal([]byte(answer)); err != nil {
return "", err
}
md := sd.MediaDescriptions[0]
for _, candidate := range candidates {
md.WithPropertyAttribute(candidate)
}
b, err := sd.Marshal()
if err != nil {
return "", err
}
return string(b), nil
}