Update WebRTC candidates logic

This commit is contained in:
Alex X
2023-11-02 15:37:44 +03:00
parent 6b29c37433
commit 0f1dc73d55
12 changed files with 112 additions and 89 deletions

View File

@@ -50,7 +50,7 @@ func Init() {
log.Info().Str("addr", address).Msg("[ngrok] add external candidate for WebRTC") log.Info().Str("addr", address).Msg("[ngrok] add external candidate for WebRTC")
webrtc.AddCandidate(address) webrtc.AddCandidate(address, "tcp")
} }
} }
}) })

View File

@@ -1,3 +1,17 @@
## Config
- supported networks: IPv4 (default), IPv6, both
- supported TCP: fixed port (default), disabled
- supported UDP: random port (default), fixed port
| Config examples | IPv4 | IPv6 | TCP | UDP |
|------------------------------|------|------|-------|--------|
| `listen: "0.0.0.0:8555/tcp"` | yes | no | fixed | random |
| `listen: "0.0.0.0:8555/udp"` | yes | no | no | fixed |
| `listen: "[::]:8555/tcp"` | no | yes | fixed | random |
| `listen: ":8555"` | yes | yes | fixed | fixed |
| `listen: ""` | yes | yes | no | random |
## Userful links ## Userful links
- https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html - https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html

View File

@@ -1,58 +1,66 @@
package webrtc package webrtc
import ( import (
"net"
"github.com/AlexxIT/go2rtc/internal/api/ws" "github.com/AlexxIT/go2rtc/internal/api/ws"
"github.com/AlexxIT/go2rtc/pkg/webrtc" "github.com/AlexxIT/go2rtc/pkg/webrtc"
"github.com/pion/sdp/v3" "github.com/pion/sdp/v3"
"strconv"
"strings"
) )
type Address struct { type Address struct {
Host string Host string
Port int Port string
Network string
Offset int
} }
var addresses []Address func (a *Address) Marshal() string {
host := a.Host
func AddCandidate(address string) { if host == "stun" {
var port int ip, err := webrtc.GetCachedPublicIP()
if err != nil {
// try to get port from address string return ""
if i := strings.LastIndexByte(address, ':'); i > 0 {
if v, _ := strconv.Atoi(address[i+1:]); v != 0 {
address = address[:i]
port = v
} }
host = ip.String()
} }
// use default WebRTC port switch a.Network {
if port == 0 { case "udp":
port, _ = strconv.Atoi(Port) return webrtc.CandidateManualHostUDP(host, a.Port, a.Offset)
case "tcp":
return webrtc.CandidateManualHostTCPPassive(host, a.Port, a.Offset)
} }
addresses = append(addresses, Address{Host: address, Port: port}) return ""
}
var addresses []*Address
func AddCandidate(address, network string) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return
}
offset := -1 - len(addresses) // every next candidate will have a lower priority
switch network {
case "tcp", "udp":
addresses = append(addresses, &Address{host, port, network, offset})
default:
addresses = append(
addresses, &Address{host, port, "udp", offset}, &Address{host, port, "tcp", offset},
)
}
} }
func GetCandidates() (candidates []string) { func GetCandidates() (candidates []string) {
for _, address := range addresses { for _, address := range addresses {
// using stun server for receive public IP-address if candidate := address.Marshal(); candidate != "" {
if address.Host == "stun" { candidates = append(candidates, candidate)
ip, err := webrtc.GetCachedPublicIP()
if err != nil {
continue
}
// this is a copy, original host unchanged
address.Host = ip.String()
} }
candidates = append(
candidates,
webrtc.CandidateManualHostUDP(address.Host, address.Port),
webrtc.CandidateManualHostTCPPassive(address.Host, address.Port),
)
} }
return return
} }

View File

@@ -55,7 +55,7 @@ func kinesisClient(rawURL string, query url.Values, desc string) (core.Producer,
defer conn.Close() defer conn.Close()
// 3. Create Peer Connection // 3. Create Peer Connection
api, err := webrtc.NewAPI("") api, err := webrtc.NewAPI()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -33,7 +33,7 @@ func openIPCClient(rawURL string, query url.Values) (core.Producer, error) {
defer conn.Close() defer conn.Close()
// 3. Create Peer Connection // 3. Create Peer Connection
api, err := webrtc.NewAPI("") api, err := webrtc.NewAPI()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -2,7 +2,7 @@ package webrtc
import ( import (
"errors" "errors"
"net" "strings"
"github.com/AlexxIT/go2rtc/internal/api" "github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/api/ws" "github.com/AlexxIT/go2rtc/internal/api/ws"
@@ -32,10 +32,20 @@ func Init() {
log = app.GetLogger("webrtc") log = app.GetLogger("webrtc")
address := cfg.Mod.Listen address, network, _ := strings.Cut(cfg.Mod.Listen, "/")
var candidateHost []string
for _, candidate := range cfg.Mod.Candidates {
if strings.HasPrefix(candidate, "host:") {
candidateHost = append(candidateHost, candidate[5:])
continue
}
AddCandidate(candidate, network)
}
// create pionAPI with custom codecs list and custom network settings // create pionAPI with custom codecs list and custom network settings
serverAPI, err := webrtc.NewAPI(address) serverAPI, err := webrtc.NewServerAPI(address, network, candidateHost)
if err != nil { if err != nil {
log.Error().Err(err).Caller().Send() log.Error().Err(err).Caller().Send()
return return
@@ -46,9 +56,8 @@ func Init() {
if address != "" { if address != "" {
log.Info().Str("addr", address).Msg("[webrtc] listen") log.Info().Str("addr", address).Msg("[webrtc] listen")
_, Port, _ = net.SplitHostPort(address)
clientAPI, _ = webrtc.NewAPI("") clientAPI, _ = webrtc.NewAPI()
} }
pionConf := pion.Configuration{ pionConf := pion.Configuration{
@@ -65,10 +74,6 @@ func Init() {
} }
} }
for _, candidate := range cfg.Mod.Candidates {
AddCandidate(candidate)
}
// async WebRTC server (two API versions) // async WebRTC server (two API versions)
ws.HandleFunc("webrtc", asyncHandler) ws.HandleFunc("webrtc", asyncHandler)
ws.HandleFunc("webrtc/offer", asyncHandler) ws.HandleFunc("webrtc/offer", asyncHandler)
@@ -81,7 +86,6 @@ func Init() {
streams.HandleFunc("webrtc", streamsHandler) streams.HandleFunc("webrtc", streamsHandler)
} }
var Port string
var log zerolog.Logger var log zerolog.Logger
var PeerConnection func(active bool) (*pion.PeerConnection, error) var PeerConnection func(active bool) (*pion.PeerConnection, error)

View File

@@ -2,10 +2,11 @@ package hass
import ( import (
"errors" "errors"
"net/url"
"github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/webrtc" "github.com/AlexxIT/go2rtc/pkg/webrtc"
pion "github.com/pion/webrtc/v3" pion "github.com/pion/webrtc/v3"
"net/url"
) )
type Client struct { type Client struct {
@@ -48,7 +49,7 @@ func NewClient(rawURL string) (*Client, error) {
defer hassAPI.Close() defer hassAPI.Close()
// 2. Create WebRTC client // 2. Create WebRTC client
rtcAPI, err := webrtc.NewAPI("") rtcAPI, err := webrtc.NewAPI()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -2,10 +2,11 @@ package nest
import ( import (
"errors" "errors"
"net/url"
"github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/webrtc" "github.com/AlexxIT/go2rtc/pkg/webrtc"
pion "github.com/pion/webrtc/v3" pion "github.com/pion/webrtc/v3"
"net/url"
) )
type Client struct { type Client struct {
@@ -34,7 +35,7 @@ func NewClient(rawURL string) (*Client, error) {
return nil, err return nil, err
} }
rtcAPI, err := webrtc.NewAPI("") rtcAPI, err := webrtc.NewAPI()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -90,7 +90,7 @@ func (c *Client) Connect() error {
} }
// 4. Create Peer Connection // 4. Create Peer Connection
api, err := webrtc.NewAPI("") api, err := webrtc.NewAPI()
if err != nil { if err != nil {
return err return err
} }

View File

@@ -2,9 +2,7 @@ package webrtc
import ( import (
"net" "net"
"strings"
"github.com/pion/ice/v2"
"github.com/pion/interceptor" "github.com/pion/interceptor"
"github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3"
) )
@@ -13,7 +11,11 @@ import (
// https://ffmpeg.org/ffmpeg-all.html#Muxer // https://ffmpeg.org/ffmpeg-all.html#Muxer
const ReceiveMTU = 1472 const ReceiveMTU = 1472
func NewAPI(address string) (*webrtc.API, error) { func NewAPI() (*webrtc.API, error) {
return NewServerAPI("", "", nil)
}
func NewServerAPI(address, network string, candidateHost []string) (*webrtc.API, error) {
// for debug logs add to env: `PION_LOG_DEBUG=all` // for debug logs add to env: `PION_LOG_DEBUG=all`
m := &webrtc.MediaEngine{} m := &webrtc.MediaEngine{}
//if err := m.RegisterDefaultCodecs(); err != nil { //if err := m.RegisterDefaultCodecs(); err != nil {
@@ -35,34 +37,33 @@ func NewAPI(address string) (*webrtc.API, error) {
return name != "hassio" && name != "docker0" return name != "hassio" && name != "docker0"
}) })
// disable mDNS listener
s.SetICEMulticastDNSMode(ice.MulticastDNSModeDisabled)
// UDP6 may have problems with DNS resolving for STUN servers
s.SetNetworkTypes([]webrtc.NetworkType{
webrtc.NetworkTypeUDP4, webrtc.NetworkTypeTCP4,
webrtc.NetworkTypeUDP6, webrtc.NetworkTypeTCP6,
})
// fix https://github.com/pion/webrtc/pull/2407 // fix https://github.com/pion/webrtc/pull/2407
s.SetDTLSInsecureSkipHelloVerify(true) s.SetDTLSInsecureSkipHelloVerify(true)
s.SetReceiveMTU(ReceiveMTU) s.SetReceiveMTU(ReceiveMTU)
s.SetNAT1To1IPs(candidateHost, webrtc.ICECandidateTypeHost)
// by default enable IPv4 + IPv6 modes
s.SetNetworkTypes([]webrtc.NetworkType{
webrtc.NetworkTypeUDP4, webrtc.NetworkTypeTCP4,
webrtc.NetworkTypeUDP6, webrtc.NetworkTypeTCP6,
})
if address != "" { if address != "" {
address, network, _ := strings.Cut(address, "/")
if network == "" || network == "udp" {
if ln, err := net.ListenPacket("udp", address); err == nil {
udpMux := webrtc.NewICEUDPMux(nil, ln)
s.SetICEUDPMux(udpMux)
}
}
if network == "" || network == "tcp" { if network == "" || network == "tcp" {
if ln, err := net.Listen("tcp", address); err == nil { if ln, err := net.Listen("tcp", address); err == nil {
tcpMux := webrtc.NewICETCPMux(nil, ln, 8) tcpMux := webrtc.NewICETCPMux(nil, ln, 8)
s.SetICETCPMux(tcpMux) s.SetICETCPMux(tcpMux)
} }
} }
if network == "" || network == "udp" {
if ln, err := net.ListenPacket("udp", address); err == nil {
udpMux := webrtc.NewICEUDPMux(nil, ln)
s.SetICEUDPMux(udpMux)
}
}
} }
return webrtc.NewAPI( return webrtc.NewAPI(

View File

@@ -10,7 +10,7 @@ import (
) )
func TestClient(t *testing.T) { func TestClient(t *testing.T) {
api, err := NewAPI("") api, err := NewAPI()
require.Nil(t, err) require.Nil(t, err)
pc, err := api.NewPeerConnection(webrtc.Configuration{}) pc, err := api.NewPeerConnection(webrtc.Configuration{})

View File

@@ -260,17 +260,15 @@ func MimeType(codec *core.Codec) string {
// for server reflexive candidates, 110 for peer reflexive candidates, // for server reflexive candidates, 110 for peer reflexive candidates,
// and 0 for relayed candidates. // and 0 for relayed candidates.
// We use new priority 120 for Manual Host. It is lower than real Host, const PriorityTypeHostUDP = (1 << 24) * int(126)
// but more then any other candidates. 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)
const PriorityManualHost = (1 << 24) * uint32(120) func CandidateManualHostUDP(host, port string, offset int) string {
const PriorityLocalUDP = (1 << 8) * uint32(65535)
const PriorityLocalTCPPassive = (1 << 8) * uint32((1<<13)*4+8191)
const PriorityComponentRTP = uint32(256 - ice.ComponentRTP)
func CandidateManualHostUDP(host string, port int) string {
foundation := crc32.ChecksumIEEE([]byte("host" + host + "udp4")) foundation := crc32.ChecksumIEEE([]byte("host" + host + "udp4"))
priority := PriorityManualHost + PriorityLocalUDP + PriorityComponentRTP priority := PriorityTypeHostUDP + PriorityLocalUDP + PriorityComponentRTP + offset
// 1. Foundation // 1. Foundation
// 2. Component, always 1 because RTP // 2. Component, always 1 because RTP
@@ -279,19 +277,15 @@ func CandidateManualHostUDP(host string, port int) string {
// 5. Host - IP4 or IP6 or domain name // 5. Host - IP4 or IP6 or domain name
// 6. Port // 6. Port
// 7. typ host // 7. typ host
return fmt.Sprintf( return fmt.Sprintf("candidate:%d 1 udp %d %s %s typ host", foundation, priority, host, port)
"candidate:%d 1 udp %d %s %d typ host",
foundation, priority, host, port,
)
} }
func CandidateManualHostTCPPassive(address string, port int) string { func CandidateManualHostTCPPassive(host, port string, offset int) string {
foundation := crc32.ChecksumIEEE([]byte("host" + address + "tcp4")) foundation := crc32.ChecksumIEEE([]byte("host" + host + "tcp4"))
priority := PriorityManualHost + PriorityLocalTCPPassive + PriorityComponentRTP priority := PriorityTypeHostTCP + PriorityLocalTCPPassive + PriorityComponentRTP + offset
return fmt.Sprintf( return fmt.Sprintf(
"candidate:%d 1 tcp %d %s %d typ host tcptype passive", "candidate:%d 1 tcp %d %s %s typ host tcptype passive", foundation, priority, host, port,
foundation, priority, address, port,
) )
} }