webrtc: improve connectivity mechanism (#2686)

This commit is contained in:
Alessandro Ros 2023-11-12 23:55:28 +01:00 committed by GitHub
parent 4cf8948fe6
commit 687d8685ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 261 additions and 228 deletions

View file

@ -143,20 +143,24 @@ type Conf struct {
HLSDirectory string `json:"hlsDirectory"`
// WebRTC
WebRTC bool `json:"webrtc"`
WebRTCDisable *bool `json:"webrtcDisable,omitempty"` // deprecated
WebRTCAddress string `json:"webrtcAddress"`
WebRTCEncryption bool `json:"webrtcEncryption"`
WebRTCServerKey string `json:"webrtcServerKey"`
WebRTCServerCert string `json:"webrtcServerCert"`
WebRTCAllowOrigin string `json:"webrtcAllowOrigin"`
WebRTCTrustedProxies IPsOrCIDRs `json:"webrtcTrustedProxies"`
WebRTCICEServers *[]string `json:"webrtcICEServers,omitempty"` // deprecated
WebRTCICEServers2 []WebRTCICEServer `json:"webrtcICEServers2"`
WebRTCICEInterfaces []string `json:"webrtcICEInterfaces"`
WebRTCICEHostNAT1To1IPs []string `json:"webrtcICEHostNAT1To1IPs"`
WebRTCICEUDPMuxAddress string `json:"webrtcICEUDPMuxAddress"`
WebRTCICETCPMuxAddress string `json:"webrtcICETCPMuxAddress"`
WebRTC bool `json:"webrtc"`
WebRTCDisable *bool `json:"webrtcDisable,omitempty"` // deprecated
WebRTCAddress string `json:"webrtcAddress"`
WebRTCEncryption bool `json:"webrtcEncryption"`
WebRTCServerKey string `json:"webrtcServerKey"`
WebRTCServerCert string `json:"webrtcServerCert"`
WebRTCAllowOrigin string `json:"webrtcAllowOrigin"`
WebRTCTrustedProxies IPsOrCIDRs `json:"webrtcTrustedProxies"`
WebRTCLocalUDPAddress string `json:"webrtcLocalUDPAddress"`
WebRTCLocalTCPAddress string `json:"webrtcLocalTCPAddress"`
WebRTCIPsFromInterfaces bool `json:"webrtcIPsFromInterfaces"`
WebRTCIPsFromInterfacesList []string `json:"webrtcIPsFromInterfacesList"`
WebRTCAdditionalHosts []string `json:"webrtcAdditionalHosts"`
WebRTCICEServers2 []WebRTCICEServer `json:"webrtcICEServers2"`
WebRTCICEUDPMuxAddress *string `json:"webrtcICEUDPMuxAddress,omitempty"` // deprecated
WebRTCICETCPMuxAddress *string `json:"webrtcICETCPMuxAddress,omitempty"` // deprecated
WebRTCICEHostNAT1To1IPs *[]string `json:"webrtcICEHostNAT1To1IPs,omitempty"` // deprecated
WebRTCICEServers *[]string `json:"webrtcICEServers,omitempty"` // deprecated
// SRT
SRT bool `json:"srt"`
@ -234,9 +238,11 @@ func (conf *Conf) setDefaults() {
conf.WebRTCServerKey = "server.key"
conf.WebRTCServerCert = "server.crt"
conf.WebRTCAllowOrigin = "*"
conf.WebRTCICEServers2 = []WebRTCICEServer{{URL: "stun:stun.l.google.com:19302"}}
conf.WebRTCICEInterfaces = []string{}
conf.WebRTCICEHostNAT1To1IPs = []string{}
conf.WebRTCLocalUDPAddress = ":8189"
conf.WebRTCIPsFromInterfaces = true
conf.WebRTCIPsFromInterfacesList = []string{}
conf.WebRTCAdditionalHosts = []string{}
conf.WebRTCICEServers2 = []WebRTCICEServer{}
// SRT
conf.SRT = true
@ -382,6 +388,15 @@ func (conf *Conf) Check() error {
if conf.WebRTCDisable != nil {
conf.WebRTC = !*conf.WebRTCDisable
}
if conf.WebRTCICEUDPMuxAddress != nil {
conf.WebRTCLocalUDPAddress = *conf.WebRTCICEUDPMuxAddress
}
if conf.WebRTCICETCPMuxAddress != nil {
conf.WebRTCLocalTCPAddress = *conf.WebRTCICETCPMuxAddress
}
if conf.WebRTCICEHostNAT1To1IPs != nil {
conf.WebRTCAdditionalHosts = *conf.WebRTCICEHostNAT1To1IPs
}
if conf.WebRTCICEServers != nil {
for _, server := range *conf.WebRTCICEServers {
parts := strings.Split(server, ":")
@ -405,6 +420,17 @@ func (conf *Conf) Check() error {
return fmt.Errorf("invalid ICE server: '%s'", server.URL)
}
}
if conf.WebRTCLocalUDPAddress == "" &&
conf.WebRTCLocalTCPAddress == "" &&
len(conf.WebRTCICEServers2) == 0 {
return fmt.Errorf("at least one between 'webrtcLocalUDPAddress'," +
" 'webrtcLocalTCPAddress' or 'webrtcICEServers2' must be filled")
}
if conf.WebRTCLocalUDPAddress != "" || conf.WebRTCLocalTCPAddress != "" {
if !conf.WebRTCIPsFromInterfaces && len(conf.WebRTCAdditionalHosts) == 0 {
return fmt.Errorf("at least one between 'webrtcIPsFromInterfaces' or 'webrtcAdditionalHosts' must be filled")
}
}
// Record
if conf.Record != nil {

View file

@ -8,7 +8,7 @@ import (
// Encryption is the encryption parameter.
type Encryption int
// supported encryption policies.
// values.
const (
EncryptionNo Encryption = iota
EncryptionOptional

View file

@ -472,26 +472,29 @@ func (p *Core) createResources(initial bool) error {
if p.conf.WebRTC &&
p.webRTCManager == nil {
p.webRTCManager, err = newWebRTCManager(
p.conf.WebRTCAddress,
p.conf.WebRTCEncryption,
p.conf.WebRTCServerKey,
p.conf.WebRTCServerCert,
p.conf.WebRTCAllowOrigin,
p.conf.WebRTCTrustedProxies,
p.conf.WebRTCICEServers2,
p.conf.ReadTimeout,
p.conf.WriteQueueSize,
p.conf.WebRTCICEInterfaces,
p.conf.WebRTCICEHostNAT1To1IPs,
p.conf.WebRTCICEUDPMuxAddress,
p.conf.WebRTCICETCPMuxAddress,
p.externalCmdPool,
p.pathManager,
p.metrics,
p,
)
p.webRTCManager = &webRTCManager{
Address: p.conf.WebRTCAddress,
Encryption: p.conf.WebRTCEncryption,
ServerKey: p.conf.WebRTCServerKey,
ServerCert: p.conf.WebRTCServerCert,
AllowOrigin: p.conf.WebRTCAllowOrigin,
TrustedProxies: p.conf.WebRTCTrustedProxies,
ReadTimeout: p.conf.ReadTimeout,
WriteQueueSize: p.conf.WriteQueueSize,
LocalUDPAddress: p.conf.WebRTCLocalUDPAddress,
LocalTCPAddress: p.conf.WebRTCLocalTCPAddress,
IPsFromInterfaces: p.conf.WebRTCIPsFromInterfaces,
IPsFromInterfacesList: p.conf.WebRTCIPsFromInterfacesList,
AdditionalHosts: p.conf.WebRTCAdditionalHosts,
ICEServers: p.conf.WebRTCICEServers2,
ExternalCmdPool: p.externalCmdPool,
PathManager: p.pathManager,
Metrics: p.metrics,
Parent: p,
}
err = p.webRTCManager.initialize()
if err != nil {
p.webRTCManager = nil
return err
}
}
@ -689,13 +692,14 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) {
newConf.WebRTCServerCert != p.conf.WebRTCServerCert ||
newConf.WebRTCAllowOrigin != p.conf.WebRTCAllowOrigin ||
!reflect.DeepEqual(newConf.WebRTCTrustedProxies, p.conf.WebRTCTrustedProxies) ||
!reflect.DeepEqual(newConf.WebRTCICEServers2, p.conf.WebRTCICEServers2) ||
newConf.ReadTimeout != p.conf.ReadTimeout ||
newConf.WriteQueueSize != p.conf.WriteQueueSize ||
!reflect.DeepEqual(newConf.WebRTCICEInterfaces, p.conf.WebRTCICEInterfaces) ||
!reflect.DeepEqual(newConf.WebRTCICEHostNAT1To1IPs, p.conf.WebRTCICEHostNAT1To1IPs) ||
newConf.WebRTCICEUDPMuxAddress != p.conf.WebRTCICEUDPMuxAddress ||
newConf.WebRTCICETCPMuxAddress != p.conf.WebRTCICETCPMuxAddress ||
newConf.WebRTCLocalUDPAddress != p.conf.WebRTCLocalUDPAddress ||
newConf.WebRTCLocalTCPAddress != p.conf.WebRTCLocalTCPAddress ||
newConf.WebRTCIPsFromInterfaces != p.conf.WebRTCIPsFromInterfaces ||
!reflect.DeepEqual(newConf.WebRTCIPsFromInterfacesList, p.conf.WebRTCIPsFromInterfacesList) ||
!reflect.DeepEqual(newConf.WebRTCAdditionalHosts, p.conf.WebRTCAdditionalHosts) ||
!reflect.DeepEqual(newConf.WebRTCICEServers2, p.conf.WebRTCICEServers2) ||
closeMetrics ||
closePathManager ||
closeLogger

View file

@ -376,10 +376,6 @@ func (pa *path) runInner() error {
case <-pa.onDemandPublisherCloseTimer.C:
pa.doOnDemandPublisherCloseTimer()
if pa.shouldClose() {
return fmt.Errorf("not in use")
}
case newConf := <-pa.chReloadConf:
pa.doReloadConf(newConf)

View file

@ -15,7 +15,6 @@ import (
"time"
"github.com/google/uuid"
"github.com/pion/ice/v2"
"github.com/pion/logging"
pwebrtc "github.com/pion/webrtc/v3"
@ -165,14 +164,24 @@ type webRTCManagerParent interface {
}
type webRTCManager struct {
allowOrigin string
trustedProxies conf.IPsOrCIDRs
iceServers []conf.WebRTCICEServer
writeQueueSize int
externalCmdPool *externalcmd.Pool
pathManager *pathManager
metrics *metrics
parent webRTCManagerParent
Address string
Encryption bool
ServerKey string
ServerCert string
AllowOrigin string
TrustedProxies conf.IPsOrCIDRs
ReadTimeout conf.StringDuration
WriteQueueSize int
LocalUDPAddress string
LocalTCPAddress string
IPsFromInterfaces bool
IPsFromInterfacesList []string
AdditionalHosts []string
ICEServers []conf.WebRTCICEServer
ExternalCmdPool *externalcmd.Pool
PathManager *pathManager
Metrics *metrics
Parent webRTCManagerParent
ctx context.Context
ctxCancel func()
@ -196,127 +205,97 @@ type webRTCManager struct {
done chan struct{}
}
func newWebRTCManager(
address string,
encryption bool,
serverKey string,
serverCert string,
allowOrigin string,
trustedProxies conf.IPsOrCIDRs,
iceServers []conf.WebRTCICEServer,
readTimeout conf.StringDuration,
writeQueueSize int,
iceInterfaces []string,
iceHostNAT1To1IPs []string,
iceUDPMuxAddress string,
iceTCPMuxAddress string,
externalCmdPool *externalcmd.Pool,
pathManager *pathManager,
metrics *metrics,
parent webRTCManagerParent,
) (*webRTCManager, error) {
func (m *webRTCManager) initialize() error {
ctx, ctxCancel := context.WithCancel(context.Background())
m := &webRTCManager{
allowOrigin: allowOrigin,
trustedProxies: trustedProxies,
iceServers: iceServers,
writeQueueSize: writeQueueSize,
externalCmdPool: externalCmdPool,
pathManager: pathManager,
metrics: metrics,
parent: parent,
ctx: ctx,
ctxCancel: ctxCancel,
sessions: make(map[*webRTCSession]struct{}),
sessionsBySecret: make(map[uuid.UUID]*webRTCSession),
chNewSession: make(chan webRTCNewSessionReq),
chCloseSession: make(chan *webRTCSession),
chAddSessionCandidates: make(chan webRTCAddSessionCandidatesReq),
chDeleteSession: make(chan webRTCDeleteSessionReq),
chAPISessionsList: make(chan webRTCManagerAPISessionsListReq),
chAPISessionsGet: make(chan webRTCManagerAPISessionsGetReq),
chAPIConnsKick: make(chan webRTCManagerAPISessionsKickReq),
done: make(chan struct{}),
}
m.ctx = ctx
m.ctxCancel = ctxCancel
m.sessions = make(map[*webRTCSession]struct{})
m.sessionsBySecret = make(map[uuid.UUID]*webRTCSession)
m.chNewSession = make(chan webRTCNewSessionReq)
m.chCloseSession = make(chan *webRTCSession)
m.chAddSessionCandidates = make(chan webRTCAddSessionCandidatesReq)
m.chDeleteSession = make(chan webRTCDeleteSessionReq)
m.chAPISessionsList = make(chan webRTCManagerAPISessionsListReq)
m.chAPISessionsGet = make(chan webRTCManagerAPISessionsGetReq)
m.chAPIConnsKick = make(chan webRTCManagerAPISessionsKickReq)
m.done = make(chan struct{})
var err error
m.httpServer, err = newWebRTCHTTPServer(
address,
encryption,
serverKey,
serverCert,
allowOrigin,
trustedProxies,
readTimeout,
pathManager,
m.Address,
m.Encryption,
m.ServerKey,
m.ServerCert,
m.AllowOrigin,
m.TrustedProxies,
m.ReadTimeout,
m.PathManager,
m,
)
if err != nil {
ctxCancel()
return nil, err
return err
}
var iceUDPMux ice.UDPMux
apiConf := webrtc.APIConf{
LocalRandomUDP: false,
IPsFromInterfaces: m.IPsFromInterfaces,
IPsFromInterfacesList: m.IPsFromInterfacesList,
AdditionalHosts: m.AdditionalHosts,
}
if iceUDPMuxAddress != "" {
m.udpMuxLn, err = net.ListenPacket(restrictnetwork.Restrict("udp", iceUDPMuxAddress))
if m.LocalUDPAddress != "" {
m.udpMuxLn, err = net.ListenPacket(restrictnetwork.Restrict("udp", m.LocalUDPAddress))
if err != nil {
m.httpServer.close()
ctxCancel()
return nil, err
return err
}
iceUDPMux = pwebrtc.NewICEUDPMux(webrtcNilLogger, m.udpMuxLn)
apiConf.ICEUDPMux = pwebrtc.NewICEUDPMux(webrtcNilLogger, m.udpMuxLn)
}
var iceTCPMux ice.TCPMux
if iceTCPMuxAddress != "" {
m.tcpMuxLn, err = net.Listen(restrictnetwork.Restrict("tcp", iceTCPMuxAddress))
if m.LocalTCPAddress != "" {
m.tcpMuxLn, err = net.Listen(restrictnetwork.Restrict("tcp", m.LocalTCPAddress))
if err != nil {
m.udpMuxLn.Close()
m.httpServer.close()
ctxCancel()
return nil, err
return err
}
iceTCPMux = pwebrtc.NewICETCPMux(webrtcNilLogger, m.tcpMuxLn, 8)
apiConf.ICETCPMux = pwebrtc.NewICETCPMux(webrtcNilLogger, m.tcpMuxLn, 8)
}
m.api, err = webrtc.NewAPI(webrtc.APIConf{
ICEInterfaces: iceInterfaces,
ICEHostNAT1To1IPs: iceHostNAT1To1IPs,
ICEUDPMux: iceUDPMux,
ICETCPMux: iceTCPMux,
})
m.api, err = webrtc.NewAPI(apiConf)
if err != nil {
m.udpMuxLn.Close()
m.tcpMuxLn.Close()
m.httpServer.close()
ctxCancel()
return nil, err
return err
}
str := "listener opened on " + address + " (HTTP)"
str := "listener opened on " + m.Address + " (HTTP)"
if m.udpMuxLn != nil {
str += ", " + iceUDPMuxAddress + " (ICE/UDP)"
str += ", " + m.LocalUDPAddress + " (ICE/UDP)"
}
if m.tcpMuxLn != nil {
str += ", " + iceTCPMuxAddress + " (ICE/TCP)"
str += ", " + m.LocalTCPAddress + " (ICE/TCP)"
}
m.Log(logger.Info, str)
if m.metrics != nil {
m.metrics.webRTCManagerSet(m)
if m.Metrics != nil {
m.Metrics.webRTCManagerSet(m)
}
go m.run()
return m, nil
return nil
}
// Log is the main logging function.
func (m *webRTCManager) Log(level logger.Level, format string, args ...interface{}) {
m.parent.Log(level, "[WebRTC] "+format, args...)
m.Parent.Log(level, "[WebRTC] "+format, args...)
}
func (m *webRTCManager) close() {
@ -336,12 +315,12 @@ outer:
case req := <-m.chNewSession:
sx := newWebRTCSession(
m.ctx,
m.writeQueueSize,
m.WriteQueueSize,
m.api,
req,
&wg,
m.externalCmdPool,
m.pathManager,
m.ExternalCmdPool,
m.PathManager,
m,
)
m.sessions[sx] = struct{}{}
@ -441,9 +420,9 @@ func (m *webRTCManager) findSessionByUUID(uuid uuid.UUID) *webRTCSession {
}
func (m *webRTCManager) generateICEServers() ([]pwebrtc.ICEServer, error) {
ret := make([]pwebrtc.ICEServer, len(m.iceServers))
ret := make([]pwebrtc.ICEServer, len(m.ICEServers))
for i, server := range m.iceServers {
for i, server := range m.ICEServers {
if server.Username == "AUTH_SECRET" {
expireDate := time.Now().Add(webrtcTurnSecretExpiration).Unix()

View file

@ -99,34 +99,44 @@ var audioCodecs = []webrtc.RTPCodecParameters{
// APIConf is the configuration passed to NewAPI().
type APIConf struct {
ICEInterfaces []string
ICEHostNAT1To1IPs []string
ICEUDPMux ice.UDPMux
ICETCPMux ice.TCPMux
ICEUDPMux ice.UDPMux
ICETCPMux ice.TCPMux
LocalRandomUDP bool
IPsFromInterfaces bool
IPsFromInterfacesList []string
AdditionalHosts []string
}
// NewAPI allocates a webrtc API.
func NewAPI(conf APIConf) (*webrtc.API, error) {
func NewAPI(cnf APIConf) (*webrtc.API, error) {
settingsEngine := webrtc.SettingEngine{}
if len(conf.ICEInterfaces) != 0 {
settingsEngine.SetInterfaceFilter(func(iface string) bool {
return stringInSlice(iface, conf.ICEInterfaces)
})
settingsEngine.SetInterfaceFilter(func(iface string) bool {
return cnf.IPsFromInterfaces && (len(cnf.IPsFromInterfacesList) == 0 ||
stringInSlice(iface, cnf.IPsFromInterfacesList))
})
settingsEngine.SetAdditionalHosts(cnf.AdditionalHosts)
var networkTypes []webrtc.NetworkType
// always enable UDP in order to support STUN/TURN
networkTypes = append(networkTypes, webrtc.NetworkTypeUDP4)
if cnf.ICEUDPMux != nil {
settingsEngine.SetICEUDPMux(cnf.ICEUDPMux)
}
if len(conf.ICEHostNAT1To1IPs) != 0 {
settingsEngine.SetNAT1To1IPs(conf.ICEHostNAT1To1IPs, webrtc.ICECandidateTypeHost)
if cnf.ICETCPMux != nil {
settingsEngine.SetICETCPMux(cnf.ICETCPMux)
networkTypes = append(networkTypes, webrtc.NetworkTypeTCP4)
}
if conf.ICEUDPMux != nil {
settingsEngine.SetICEUDPMux(conf.ICEUDPMux)
if cnf.LocalRandomUDP {
settingsEngine.SetICEUDPRandom(true)
}
if conf.ICETCPMux != nil {
settingsEngine.SetICETCPMux(conf.ICETCPMux)
settingsEngine.SetNetworkTypes([]webrtc.NetworkType{webrtc.NetworkTypeTCP4})
}
settingsEngine.SetNetworkTypes(networkTypes)
mediaEngine := &webrtc.MediaEngine{}

View file

@ -33,7 +33,10 @@ func (c *WHIPClient) Publish(
return nil, err
}
api, err := NewAPI(APIConf{})
api, err := NewAPI(APIConf{
LocalRandomUDP: true,
IPsFromInterfaces: true,
})
if err != nil {
return nil, err
}
@ -113,7 +116,10 @@ func (c *WHIPClient) Read(ctx context.Context) ([]*IncomingTrack, error) {
return nil, err
}
api, err := NewAPI(APIConf{})
api, err := NewAPI(APIConf{
LocalRandomUDP: true,
IPsFromInterfaces: true,
})
if err != nil {
return nil, err
}

View file

@ -27,7 +27,10 @@ func whipOffer(body []byte) *pwebrtc.SessionDescription {
}
func TestSource(t *testing.T) {
api, err := webrtc.NewAPI(webrtc.APIConf{})
api, err := webrtc.NewAPI(webrtc.APIConf{
LocalRandomUDP: true,
IPsFromInterfaces: true,
})
require.NoError(t, err)
pc := &webrtc.PeerConnection{