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")
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
- https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html

View File

@@ -1,58 +1,66 @@
package webrtc
import (
"net"
"github.com/AlexxIT/go2rtc/internal/api/ws"
"github.com/AlexxIT/go2rtc/pkg/webrtc"
"github.com/pion/sdp/v3"
"strconv"
"strings"
)
type Address struct {
Host string
Port int
Port string
Network string
Offset int
}
var addresses []Address
func AddCandidate(address string) {
var port int
// try to get port from address string
if i := strings.LastIndexByte(address, ':'); i > 0 {
if v, _ := strconv.Atoi(address[i+1:]); v != 0 {
address = address[:i]
port = v
func (a *Address) Marshal() string {
host := a.Host
if host == "stun" {
ip, err := webrtc.GetCachedPublicIP()
if err != nil {
return ""
}
host = ip.String()
}
// use default WebRTC port
if port == 0 {
port, _ = strconv.Atoi(Port)
switch a.Network {
case "udp":
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) {
for _, address := range addresses {
// using stun server for receive public IP-address
if address.Host == "stun" {
ip, err := webrtc.GetCachedPublicIP()
if err != nil {
continue
if candidate := address.Marshal(); candidate != "" {
candidates = append(candidates, candidate)
}
// 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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,9 +2,7 @@ package webrtc
import (
"net"
"strings"
"github.com/pion/ice/v2"
"github.com/pion/interceptor"
"github.com/pion/webrtc/v3"
)
@@ -13,7 +11,11 @@ import (
// https://ffmpeg.org/ffmpeg-all.html#Muxer
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`
m := &webrtc.MediaEngine{}
//if err := m.RegisterDefaultCodecs(); err != nil {
@@ -35,34 +37,33 @@ func NewAPI(address string) (*webrtc.API, error) {
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
s.SetDTLSInsecureSkipHelloVerify(true)
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 != "" {
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 ln, err := net.Listen("tcp", address); err == nil {
tcpMux := webrtc.NewICETCPMux(nil, ln, 8)
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(

View File

@@ -10,7 +10,7 @@ import (
)
func TestClient(t *testing.T) {
api, err := NewAPI("")
api, err := NewAPI()
require.Nil(t, err)
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,
// and 0 for relayed candidates.
// We use new priority 120 for Manual Host. It is lower than real Host,
// but more then any other 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)
const PriorityManualHost = (1 << 24) * uint32(120)
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 {
func CandidateManualHostUDP(host, port string, offset int) string {
foundation := crc32.ChecksumIEEE([]byte("host" + host + "udp4"))
priority := PriorityManualHost + PriorityLocalUDP + PriorityComponentRTP
priority := PriorityTypeHostUDP + PriorityLocalUDP + PriorityComponentRTP + offset
// 1. Foundation
// 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
// 6. Port
// 7. typ host
return fmt.Sprintf(
"candidate:%d 1 udp %d %s %d typ host",
foundation, priority, host, port,
)
return fmt.Sprintf("candidate:%d 1 udp %d %s %s typ host", foundation, priority, host, port)
}
func CandidateManualHostTCPPassive(address string, port int) string {
foundation := crc32.ChecksumIEEE([]byte("host" + address + "tcp4"))
priority := PriorityManualHost + PriorityLocalTCPPassive + PriorityComponentRTP
func CandidateManualHostTCPPassive(host, port string, offset int) string {
foundation := crc32.ChecksumIEEE([]byte("host" + host + "tcp4"))
priority := PriorityTypeHostTCP + PriorityLocalTCPPassive + PriorityComponentRTP + offset
return fmt.Sprintf(
"candidate:%d 1 tcp %d %s %d typ host tcptype passive",
foundation, priority, address, port,
"candidate:%d 1 tcp %d %s %s typ host tcptype passive", foundation, priority, host, port,
)
}