diff --git a/cmd/webrtc/candidates.go b/cmd/webrtc/candidates.go index 6199be2b..ec895845 100644 --- a/cmd/webrtc/candidates.go +++ b/cmd/webrtc/candidates.go @@ -4,39 +4,67 @@ import ( "github.com/AlexxIT/go2rtc/cmd/api" "github.com/AlexxIT/go2rtc/pkg/webrtc" "github.com/pion/sdp/v3" + "strconv" + "strings" ) -var candidates []string -var networks = []string{"udp", "tcp"} +type Address struct { + Host string + Port int +} + +var addresses []Address func AddCandidate(address string) { - candidates = append(candidates, address) + 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 + } + } + + // use default WebRTC port + if port == 0 { + port, _ = strconv.Atoi(Port) + } + + addresses = append(addresses, Address{Host: address, Port: port}) +} + +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 + } + // this is a copy, original host unchanged + address.Host = ip.String() + } + + candidates = append( + candidates, + webrtc.CandidateHostUDP(address.Host, address.Port), + webrtc.CandidateHostTCPPassive(address.Host, address.Port), + ) + } + + return } func asyncCandidates(tr *api.Transport) { - for _, address := range candidates { - address, err := webrtc.LookupIP(address) - if err != nil { - log.Warn().Err(err).Caller().Send() - continue - } - - for _, network := range networks { - cand, err := webrtc.NewCandidate(network, address) - if err != nil { - log.Warn().Err(err).Caller().Send() - continue - } - - log.Trace().Str("candidate", cand).Msg("[webrtc] config") - - tr.Write(&api.Message{Type: "webrtc/candidate", Value: cand}) - } + for _, candidate := range GetCandidates() { + log.Trace().Str("candidate", candidate).Msg("[webrtc] config") + tr.Write(&api.Message{Type: "webrtc/candidate", Value: candidate}) } } func syncCanditates(answer string) (string, error) { - if len(candidates) == 0 { + if len(addresses) == 0 { return answer, nil } @@ -52,23 +80,8 @@ func syncCanditates(answer string) (string, error) { md.Attributes = md.Attributes[:len(md.Attributes)-1] } - for _, address := range candidates { - var err error - address, err = webrtc.LookupIP(address) - if err != nil { - log.Warn().Err(err).Msg("[webrtc] candidate") - continue - } - - for _, network := range networks { - cand, err := webrtc.NewCandidate(network, address) - if err != nil { - log.Warn().Err(err).Msg("[webrtc] candidate") - continue - } - - md.WithPropertyAttribute(cand) - } + for _, candidate := range GetCandidates() { + md.WithPropertyAttribute(candidate) } if end { diff --git a/cmd/webrtc/webrtc.go b/cmd/webrtc/webrtc.go index bd6d60a8..d940f3d8 100644 --- a/cmd/webrtc/webrtc.go +++ b/cmd/webrtc/webrtc.go @@ -58,7 +58,9 @@ func Init() { return pionAPI.NewPeerConnection(pionConf) } - candidates = cfg.Mod.Candidates + for _, candidate := range cfg.Mod.Candidates { + AddCandidate(candidate) + } api.HandleWS("webrtc", asyncHandler) api.HandleWS("webrtc/offer", asyncHandler) diff --git a/pkg/webrtc/helpers.go b/pkg/webrtc/helpers.go index d934e7c1..3caf433b 100644 --- a/pkg/webrtc/helpers.go +++ b/pkg/webrtc/helpers.go @@ -7,6 +7,7 @@ import ( "github.com/pion/ice/v2" "github.com/pion/stun" "github.com/pion/webrtc/v3" + "hash/crc32" "net" "strconv" "strings" @@ -157,3 +158,35 @@ func MimeType(codec *streamer.Codec) string { } panic("not implemented") } + +const PriorityHost = (1 << 24) * uint32(126) +const PriorityLocalUDP = (1 << 8) * uint32(65535) +const PriorityLocalTCPPassive = (1 << 8) * uint32((1<<13)*4+8191) +const PriorityComponentRTP = uint32(256 - ice.ComponentRTP) + +func CandidateHostUDP(host string, port int) string { + foundation := crc32.ChecksumIEEE([]byte("host" + host + "udp4")) + priority := PriorityHost + PriorityLocalUDP + PriorityComponentRTP + + // 1. Foundation + // 2. Component, always 1 because RTP + // 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 %d typ host", + foundation, priority, host, port, + ) +} + +func CandidateHostTCPPassive(address string, port int) string { + foundation := crc32.ChecksumIEEE([]byte("host" + address + "tcp4")) + priority := PriorityHost + PriorityLocalTCPPassive + PriorityComponentRTP + + return fmt.Sprintf( + "candidate:%d 1 tcp %d %s %d typ host tcptype passive", + foundation, priority, address, port, + ) +} diff --git a/pkg/webrtc/webrtc_test.go b/pkg/webrtc/webrtc_test.go index c243d70b..71f194a1 100644 --- a/pkg/webrtc/webrtc_test.go +++ b/pkg/webrtc/webrtc_test.go @@ -5,19 +5,31 @@ import ( "github.com/pion/sdp/v3" "github.com/pion/webrtc/v3" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "testing" ) -func TestName(t *testing.T) { - i, _ := ice.NewCandidateHost(&ice.CandidateHostConfig{ +func TestCandidates(t *testing.T) { + conf := &ice.CandidateHostConfig{ + Network: "udp", + Address: "192.168.1.123", + Port: 8555, + Component: ice.ComponentRTP, + } + cand, err := ice.NewCandidateHost(conf) + require.Nil(t, err) + assert.Equal(t, "candidate:"+cand.Marshal(), CandidateHostUDP(conf.Address, conf.Port)) + + conf = &ice.CandidateHostConfig{ Network: "tcp", Address: "192.168.1.123", Port: 8555, Component: ice.ComponentRTP, TCPType: ice.TCPTypePassive, - }) - a := i.Marshal() - println(a) + } + cand, err = ice.NewCandidateHost(conf) + require.Nil(t, err) + assert.Equal(t, "candidate:"+cand.Marshal(), CandidateHostTCPPassive(conf.Address, conf.Port)) } func TestPublicIP(t *testing.T) {