97 lines
2.8 KiB
Go
97 lines
2.8 KiB
Go
package onvif
|
|
|
|
import (
|
|
"context"
|
|
"encoding/xml"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
ErrDiscoveryNotConfigured = errors.New("未配置获准的发现网卡")
|
|
ErrDiscoveryInterface = errors.New("配置的发现地址不是本机网卡")
|
|
)
|
|
|
|
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, policy Policy) ([]string, error) {
|
|
ip := net.ParseIP(strings.TrimSpace(localIP))
|
|
if ip == nil {
|
|
return nil, ErrDiscoveryNotConfigured
|
|
}
|
|
approved := false
|
|
interfaces, _ := net.Interfaces()
|
|
for _, iface := range interfaces {
|
|
addresses, _ := iface.Addrs()
|
|
for _, address := range addresses {
|
|
host, _, _ := net.ParseCIDR(address.String())
|
|
if host != nil && host.Equal(ip) {
|
|
approved = true
|
|
}
|
|
}
|
|
}
|
|
if !approved {
|
|
return nil, ErrDiscoveryInterface
|
|
}
|
|
connection, err := net.ListenUDP("udp4", &net.UDPAddr{IP: ip})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer connection.Close()
|
|
if timeout <= 0 {
|
|
timeout = 3 * time.Second
|
|
}
|
|
_ = connection.SetDeadline(time.Now().Add(timeout))
|
|
if _, err = connection.WriteToUDP([]byte(discoveryProbe), &net.UDPAddr{IP: net.ParseIP("239.255.255.250"), Port: 3702}); err != nil {
|
|
return nil, err
|
|
}
|
|
found := map[string]bool{}
|
|
buffer := make([]byte, 65535)
|
|
for {
|
|
n, _, readErr := connection.ReadFromUDP(buffer)
|
|
if readErr != nil {
|
|
if e, ok := readErr.(net.Error); ok && e.Timeout() {
|
|
break
|
|
}
|
|
return nil, readErr
|
|
}
|
|
for _, candidate := range extractXAddrs(string(buffer[:n])) {
|
|
if _, _, validErr := policy.ValidateURL(ctx, candidate, "http", "https"); validErr == nil {
|
|
found[candidate] = true
|
|
}
|
|
}
|
|
}
|
|
result := make([]string, 0, len(found))
|
|
for value := range found {
|
|
result = append(result, value)
|
|
}
|
|
sort.Strings(result)
|
|
return result, nil
|
|
}
|
|
|
|
func extractXAddrs(value string) []string {
|
|
decoder := xml.NewDecoder(strings.NewReader(value))
|
|
for {
|
|
token, err := decoder.Token()
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
start, ok := token.(xml.StartElement)
|
|
if !ok || start.Name.Local != "XAddrs" {
|
|
continue
|
|
}
|
|
var addresses string
|
|
if decoder.DecodeElement(&addresses, &start) == nil {
|
|
return strings.Fields(addresses)
|
|
}
|
|
}
|
|
return nil
|
|
}
|