From b27c363ecfc1ec878b0b5765c50a9fe77e2a4cf9 Mon Sep 17 00:00:00 2001 From: andrew-ld <43882924+andrew-ld@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:39:20 +0100 Subject: [PATCH] (webrtc) added support for ice mux tcp and nat1to1ips (#1323) * add webrtcp static tcp mux port * add ice nat1 host configuration and cleanup * typo * rename config keys * apply codecov suggestions * apply review suggestions * typo * dont use deepequal for WebRTCICETCPMuxAddress * unexport NewPeerConnection() * remove Dockerfile * use an empty list instead of nil value in webrtcICEHostNAT1To1IPs * drop webrtcICETCPMuxEnable and enable TCP mux when webrtcICETCPMuxAddress is filled * run go mod tidy Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com> --- apidocs/openapi.yaml | 6 +++ go.mod | 4 +- internal/conf/conf.go | 18 +++---- internal/core/core.go | 6 ++- internal/core/webrtc_conn.go | 85 +++++++++++++++++++++++++--------- internal/core/webrtc_server.go | 34 ++++++++++++++ rtsp-simple-server.yml | 7 +++ 7 files changed, 128 insertions(+), 32 deletions(-) diff --git a/apidocs/openapi.yaml b/apidocs/openapi.yaml index 3081e64a..dbfe4047 100644 --- a/apidocs/openapi.yaml +++ b/apidocs/openapi.yaml @@ -147,6 +147,12 @@ components: type: array items: type: string + webrtcICEHostNAT1To1IPs: + type: array + items: + type: string + webrtcICETCPMuxAddress: + type: string # paths paths: diff --git a/go.mod b/go.mod index 46b0a12b..3ad7b178 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,8 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/notedit/rtmp v0.0.2 github.com/orcaman/writerseeker v0.0.0 + github.com/pion/ice/v2 v2.2.11 + github.com/pion/interceptor v0.1.11 github.com/pion/rtp v1.7.13 github.com/pion/webrtc/v3 v3.1.47 github.com/stretchr/testify v1.7.1 @@ -42,8 +44,6 @@ require ( github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/pion/datachannel v1.5.2 // indirect github.com/pion/dtls/v2 v2.1.5 // indirect - github.com/pion/ice/v2 v2.2.11 // indirect - github.com/pion/interceptor v0.1.11 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/mdns v0.0.5 // indirect github.com/pion/randutil v0.1.0 // indirect diff --git a/internal/conf/conf.go b/internal/conf/conf.go index 7a3aab02..1674dcde 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -224,14 +224,16 @@ type Conf struct { HLSTrustedProxies IPsOrCIDRs `json:"hlsTrustedProxies"` // WebRTC - WebRTCDisable bool `json:"webrtcDisable"` - 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"` + WebRTCDisable bool `json:"webrtcDisable"` + 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"` + WebRTCICEHostNAT1To1IPs []string `json:"webrtcICEHostNAT1To1IPs"` + WebRTCICETCPMuxAddress string `json:"webrtcICETCPMuxAddress"` // paths Paths map[string]*PathConf `json:"paths"` diff --git a/internal/core/core.go b/internal/core/core.go index 51f3e58b..21dfdd25 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -413,6 +413,8 @@ func (p *Core) createResources(initial bool) error { p.pathManager, p.metrics, p, + p.conf.WebRTCICEHostNAT1To1IPs, + p.conf.WebRTCICETCPMuxAddress, ) if err != nil { return err @@ -574,7 +576,9 @@ func (p *Core) closeResources(newConf *conf.Conf, calledByAPI bool) { !reflect.DeepEqual(newConf.WebRTCICEServers, p.conf.WebRTCICEServers) || newConf.ReadBufferCount != p.conf.ReadBufferCount || closeMetrics || - closePathManager + closePathManager || + !reflect.DeepEqual(newConf.WebRTCICEHostNAT1To1IPs, p.conf.WebRTCICEHostNAT1To1IPs) || + newConf.WebRTCICETCPMuxAddress != p.conf.WebRTCICETCPMuxAddress closeAPI := newConf == nil || newConf.API != p.conf.API || diff --git a/internal/core/webrtc_conn.go b/internal/core/webrtc_conn.go index 8b3a33e1..da55ef06 100644 --- a/internal/core/webrtc_conn.go +++ b/internal/core/webrtc_conn.go @@ -24,6 +24,8 @@ import ( "github.com/aler9/gortsplib/v2/pkg/ringbuffer" "github.com/google/uuid" "github.com/gorilla/websocket" + "github.com/pion/ice/v2" + "github.com/pion/interceptor" "github.com/pion/webrtc/v3" "github.com/aler9/rtsp-simple-server/internal/conf" @@ -34,6 +36,31 @@ const ( handshakeDeadline = 10 * time.Second ) +// newPeerConnection creates a PeerConnection with the default codecs and +// interceptors. See RegisterDefaultCodecs and RegisterDefaultInterceptors. +// +// This function is a copy of webrtc/peerconnection.go +// unlike the original one, allows you to add additional custom options +func newPeerConnection(configuration webrtc.Configuration, + options ...func(*webrtc.API), +) (*webrtc.PeerConnection, error) { + m := &webrtc.MediaEngine{} + if err := m.RegisterDefaultCodecs(); err != nil { + return nil, err + } + + i := &interceptor.Registry{} + if err := webrtc.RegisterDefaultInterceptors(m, i); err != nil { + return nil, err + } + + options = append(options, webrtc.WithMediaEngine(m)) + options = append(options, webrtc.WithInterceptorRegistry(i)) + + api := webrtc.NewAPI(options...) + return api.NewPeerConnection(configuration) +} + type webRTCTrack struct { media *media.Media format format.Format @@ -61,13 +88,15 @@ type webRTCConnParent interface { } type webRTCConn struct { - readBufferCount int - pathName string - wsconn *websocket.Conn - iceServers []string - wg *sync.WaitGroup - pathManager webRTCConnPathManager - parent webRTCConnParent + readBufferCount int + pathName string + wsconn *websocket.Conn + iceServers []string + wg *sync.WaitGroup + pathManager webRTCConnPathManager + parent webRTCConnParent + iceTCPMux ice.TCPMux + iceHostNAT1To1IPs []string ctx context.Context ctxCancel func() @@ -86,21 +115,25 @@ func newWebRTCConn( wg *sync.WaitGroup, pathManager webRTCConnPathManager, parent webRTCConnParent, + iceTCPMux ice.TCPMux, + iceHostNAT1To1IPs []string, ) *webRTCConn { ctx, ctxCancel := context.WithCancel(parentCtx) c := &webRTCConn{ - readBufferCount: readBufferCount, - pathName: pathName, - wsconn: wsconn, - iceServers: iceServers, - wg: wg, - pathManager: pathManager, - parent: parent, - ctx: ctx, - ctxCancel: ctxCancel, - uuid: uuid.New(), - created: time.Now(), + readBufferCount: readBufferCount, + pathName: pathName, + wsconn: wsconn, + iceServers: iceServers, + wg: wg, + pathManager: pathManager, + parent: parent, + ctx: ctx, + ctxCancel: ctxCancel, + uuid: uuid.New(), + created: time.Now(), + iceTCPMux: iceTCPMux, + iceHostNAT1To1IPs: iceHostNAT1To1IPs, } c.log(logger.Info, "opened") @@ -222,9 +255,19 @@ func (c *webRTCConn) runInner(ctx context.Context) error { return err } - pc, err := webrtc.NewPeerConnection(webrtc.Configuration{ - ICEServers: c.genICEServers(), - }) + configuration := webrtc.Configuration{ICEServers: c.genICEServers()} + settingsEngine := webrtc.SettingEngine{} + + if c.iceTCPMux != nil { + settingsEngine.SetICETCPMux(c.iceTCPMux) + settingsEngine.SetNetworkTypes([]webrtc.NetworkType{webrtc.NetworkTypeTCP4}) + } + + if len(c.iceHostNAT1To1IPs) != 0 { + settingsEngine.SetNAT1To1IPs(c.iceHostNAT1To1IPs, webrtc.ICECandidateTypeHost) + } + + pc, err := newPeerConnection(configuration, webrtc.WithSettingEngine(settingsEngine)) if err != nil { return err } diff --git a/internal/core/webrtc_server.go b/internal/core/webrtc_server.go index df9aac76..410c3659 100644 --- a/internal/core/webrtc_server.go +++ b/internal/core/webrtc_server.go @@ -15,6 +15,8 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" + "github.com/pion/ice/v2" + "github.com/pion/webrtc/v3" "github.com/aler9/rtsp-simple-server/internal/conf" "github.com/aler9/rtsp-simple-server/internal/logger" @@ -81,9 +83,13 @@ type webRTCServer struct { ctxCancel func() wg sync.WaitGroup ln net.Listener + tcpMuxLn net.Listener tlsConfig *tls.Config conns map[*webRTCConn]struct{} + iceTCPMux ice.TCPMux + iceHostNAT1To1IPs []string + // in connNew chan webRTCConnNewReq chConnClose chan *webRTCConn @@ -105,6 +111,8 @@ func newWebRTCServer( pathManager *pathManager, metrics *metrics, parent webRTCServerParent, + iceHostNAT1To1IPs []string, + iceTCPMuxAddress string, ) (*webRTCServer, error) { ln, err := net.Listen("tcp", address) if err != nil { @@ -124,6 +132,18 @@ func newWebRTCServer( } } + var iceTCPMux ice.TCPMux + var tcpMuxLn net.Listener + if iceTCPMuxAddress != "" { + tcpMuxLn, err = net.Listen("tcp", iceTCPMuxAddress) + if err != nil { + tcpMuxLn.Close() + return nil, err + } + + iceTCPMux = webrtc.NewICETCPMux(nil, tcpMuxLn, 8) + } + ctx, ctxCancel := context.WithCancel(parentCtx) s := &webRTCServer{ @@ -138,7 +158,10 @@ func newWebRTCServer( ctx: ctx, ctxCancel: ctxCancel, ln: ln, + tcpMuxLn: tcpMuxLn, tlsConfig: tlsConfig, + iceTCPMux: iceTCPMux, + iceHostNAT1To1IPs: iceHostNAT1To1IPs, conns: make(map[*webRTCConn]struct{}), connNew: make(chan webRTCConnNewReq), chConnClose: make(chan *webRTCConn), @@ -148,6 +171,10 @@ func newWebRTCServer( s.log(logger.Info, "listener opened on "+address) + if tcpMuxLn != nil { + s.log(logger.Info, "ice mux tcp listener opened on "+iceTCPMuxAddress) + } + if s.metrics != nil { s.metrics.webRTCServerSet(s) } @@ -206,6 +233,8 @@ outer: &s.wg, s.pathManager, s, + s.iceTCPMux, + s.iceHostNAT1To1IPs, ) s.conns[c] = struct{}{} @@ -253,6 +282,11 @@ outer: s.ctxCancel() hs.Shutdown(context.Background()) + + if s.tcpMuxLn != nil { + s.tcpMuxLn.Close() + } + s.ln.Close() // in case Shutdown() is called before Serve() } diff --git a/rtsp-simple-server.yml b/rtsp-simple-server.yml index aeb9ebbb..c6c9b703 100644 --- a/rtsp-simple-server.yml +++ b/rtsp-simple-server.yml @@ -196,6 +196,13 @@ webrtcTrustedProxies: [] # if user is "AUTH_SECRET", then authentication is secret based. # the secret must be inserted into the pass field. webrtcICEServers: [stun:stun.l.google.com:19302] +# List of public IP addresses that are to be used as a host. +# This is used typically for servers that are behind 1:1 D-NAT. +webrtcICEHostNAT1To1IPs: [] +# Address of a ICE TCP listener in format host:port. +# If filled, ICE traffic will come through a single TCP port, +# allowing the deployment of the server inside a container or behind a NAT. +webrtcICETCPMuxAddress: ############################################### # Path parameters