package wsdiscovery /******************************************************* * Copyright (C) 2018 Palanjyan Zhorzhik * * This file is part of ws-discovery project. * * ws-discovery can be copied and/or distributed without the express * permission of Palanjyan Zhorzhik *******************************************************/ // Copyright (C) 2022 Intel Corporation // SPDX-License-Identifier: Apache-2.0 import ( "errors" "fmt" "net" "net/http" "net/url" "os" "strings" "time" "github.com/beevik/etree" "github.com/google/uuid" "github.com/kerberos-io/onvif" "golang.org/x/net/ipv4" ) const ( bufSize = 8192 ) // GetAvailableDevicesAtSpecificEthernetInterface sends a ws-discovery Probe Message via // UDP multicast to Discover NVT type Devices func GetAvailableDevicesAtSpecificEthernetInterface(interfaceName string) ([]onvif.Device, error) { types := []string{"dn:NetworkVideoTransmitter"} namespaces := map[string]string{"dn": "http://www.onvif.org/ver10/network/wsdl", "ds": "http://www.onvif.org/ver10/device/wsdl"} probeResponses, err := SendProbe(interfaceName, nil, types, namespaces) if err != nil { return nil, fmt.Errorf("failed to probe: %w", err) } nvtDevices, err := DevicesFromProbeResponses(probeResponses) if err != nil { return nil, fmt.Errorf("failed to discover Onvif devices: %w", err) } return nvtDevices, nil } func DevicesFromProbeResponses(probeResponses []string) ([]onvif.Device, error) { nvtDevices := make([]onvif.Device, 0) xaddrSet := make(map[string]struct{}) for _, j := range probeResponses { doc := etree.NewDocument() if err := doc.ReadFromString(j); err != nil { return nil, err } probeMatches := doc.Root().FindElements("./Body/ProbeMatches/ProbeMatch") for _, probeMatch := range probeMatches { var xaddr string if address := probeMatch.FindElement("./XAddrs"); address != nil { u, err := url.Parse(address.Text()) if err != nil { // TODO: Add logger for fmt.Printf("Invalid XAddrs: %s\n", address.Text()) continue } xaddr = u.Host } if _, dupe := xaddrSet[xaddr]; dupe { // TODO: Add logger for fmt.Printf("Skipping duplicate XAddr: %s\n", xaddr) continue } var endpointRefAddress string if ref := probeMatch.FindElement("./EndpointReference/Address"); ref != nil { uuidElements := strings.Split(ref.Text(), ":") endpointRefAddress = uuidElements[len(uuidElements)-1] } dev, err := onvif.NewDevice(onvif.DeviceParams{ Xaddr: xaddr, EndpointRefAddress: endpointRefAddress, HttpClient: &http.Client{ Timeout: 2 * time.Second, }, }) if err != nil { // TODO: Add logger for fmt.Printf("Failed to connect to camera at %s: %s\n", xaddr, err.Error()) continue } var scopes []string ref := probeMatch.FindElement("./Scopes") if ref != nil { scopes = strings.Split(ref.Text(), " ") } dev.SetDeviceInfoFromScopes(scopes) xaddrSet[xaddr] = struct{}{} nvtDevices = append(nvtDevices, *dev) // TODO: Add logger for fmt.Printf("Onvif WS-Discovery: Find Xaddr: %-25s EndpointRefAddress: %s\n", xaddr, string(endpointRefAddress)) } } return nvtDevices, nil } // SendProbe to device func SendProbe(interfaceName string, scopes, types []string, namespaces map[string]string) ([]string, error) { probeSOAP := BuildProbeMessage(uuid.NewString(), scopes, types, namespaces) return SendUDPMulticast(probeSOAP.String(), interfaceName) } func SendUDPMulticast(msg string, interfaceName string) ([]string, error) { var responses []string data := []byte(msg) c, err := net.ListenPacket("udp4", "0.0.0.0:0") if err != nil { return nil, err } defer c.Close() p := ipv4.NewPacketConn(c) // 239.255.255.250 port 3702 is the multicast address and port used by ws-discovery group := net.IPv4(239, 255, 255, 250) dest := &net.UDPAddr{IP: group, Port: 3702} var iface *net.Interface if interfaceName == "" { iface = nil } else { iface, err = net.InterfaceByName(interfaceName) if err != nil { return nil, fmt.Errorf("failed to call InterfaceByName for interface %q: %w", interfaceName, err) } } if err = p.JoinGroup(iface, &net.UDPAddr{IP: group}); err != nil { return nil, fmt.Errorf("failed to JoinGroup for ws-discovery: %w", err) } if iface != nil { if err = p.SetMulticastInterface(iface); err != nil { return nil, fmt.Errorf("failed to SetMulticastInterface for interface %q: %w", interfaceName, err) } if err = p.SetMulticastTTL(2); err != nil { return nil, fmt.Errorf("failed to SetMulticastTTL: %w", err) } } if _, err = p.WriteTo(data, nil, dest); err != nil { return nil, fmt.Errorf("failed to write to ws-discovery multicast address %s: %w", dest.String(), err) } if err = p.SetReadDeadline(time.Now().Add(time.Second * 1)); err != nil { return nil, fmt.Errorf("failed to set read deadline: %w", err) } b := make([]byte, bufSize) // keep reading from the PacketConn until the read deadline expires or an error occurs for { n, _, _, err := p.ReadFrom(b) if err != nil { // ErrDeadlineExceeded is expected once the read timeout is expired if !errors.Is(err, os.ErrDeadlineExceeded) { return nil, fmt.Errorf("unexpected error occurred while reading ws-discovery responses: %w", err) } break } responses = append(responses, string(b[0:n])) } return responses, nil }