81 lines
2.5 KiB
Go
81 lines
2.5 KiB
Go
package onvif
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const discoveryProbe = `<?xml version="1.0"?><e:Envelope xmlns:e="http://www.w3.org/2003/05/soap-envelope" xmlns:w="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:dn="http://www.onvif.org/ver10/network/wsdl"><e:Header><w:MessageID>uuid:sense-controlled-discovery</w:MessageID><w:To e:mustUnderstand="true">urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To><w:Action e:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action></e:Header><e:Body><d:Probe><d:Types>dn:NetworkVideoTransmitter</d:Types></d:Probe></e:Body></e:Envelope>`
|
|
|
|
func Discover(ctx context.Context, localIP string, timeout time.Duration) ([]string, error) {
|
|
ip := net.ParseIP(localIP)
|
|
if ip == nil {
|
|
return nil, fmt.Errorf("approved discovery interface is not configured")
|
|
}
|
|
approved := false
|
|
interfaces, _ := net.Interfaces()
|
|
for _, iface := range interfaces {
|
|
addresses, _ := iface.Addrs()
|
|
for _, address := range addresses {
|
|
if strings.HasPrefix(address.String(), ip.String()+"/") {
|
|
approved = true
|
|
}
|
|
}
|
|
}
|
|
if !approved {
|
|
return nil, fmt.Errorf("approved discovery IP is not a local interface")
|
|
}
|
|
connection, err := net.ListenUDP("udp4", &net.UDPAddr{IP: ip, Port: 0})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer connection.Close()
|
|
deadline := time.Now().Add(timeout)
|
|
_ = connection.SetDeadline(deadline)
|
|
target := &net.UDPAddr{IP: net.ParseIP("239.255.255.250"), Port: 3702}
|
|
if _, err := connection.WriteToUDP([]byte(discoveryProbe), target); err != nil {
|
|
return nil, err
|
|
}
|
|
found := map[string]bool{}
|
|
buffer := make([]byte, 65535)
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return keys(found), nil
|
|
default:
|
|
}
|
|
n, _, err := connection.ReadFromUDP(buffer)
|
|
if err != nil {
|
|
if networkErr, ok := err.(net.Error); ok && networkErr.Timeout() {
|
|
return keys(found), nil
|
|
}
|
|
return nil, err
|
|
}
|
|
for _, value := range extractXAddrs(string(buffer[:n])) {
|
|
found[value] = true
|
|
}
|
|
}
|
|
}
|
|
func extractXAddrs(value string) []string {
|
|
startTag, endTag := "<d:XAddrs>", "</d:XAddrs>"
|
|
start := strings.Index(value, startTag)
|
|
if start < 0 {
|
|
return nil
|
|
}
|
|
end := strings.Index(value[start:], endTag)
|
|
if end < 0 {
|
|
return nil
|
|
}
|
|
return strings.Fields(value[start+len(startTag) : start+end])
|
|
}
|
|
func keys(values map[string]bool) []string {
|
|
result := make([]string, 0, len(values))
|
|
for value := range values {
|
|
result = append(result, value)
|
|
}
|
|
return result
|
|
}
|