package onvif import ( "github.com/AlexxIT/go2rtc/pkg/core" "net" "net/url" "regexp" "strconv" "strings" "time" ) const ( PathDevice = "/onvif/device_service" PathMedia = "/onvif/media_service" ) func FindTagValue(b []byte, tag string) string { re := regexp.MustCompile(tag + `[^>]*>([^<]+)`) m := re.FindSubmatch(b) if len(m) != 2 { return "" } return string(m[1]) } // UUID - generate something like 44302cbf-0d18-4feb-79b3-33b575263da3 func UUID() string { s := core.RandString(32, 16) return s[:8] + "-" + s[8:12] + "-" + s[12:16] + "-" + s[16:20] + "-" + s[20:] } func DiscoveryStreamingHosts() ([]string, error) { conn, err := net.ListenUDP("udp4", nil) if err != nil { return nil, err } // https://www.onvif.org/wp-content/uploads/2016/12/ONVIF_Feature_Discovery_Specification_16.07.pdf // 5.3 Discovery Procedure: msg := ` http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe urn:uuid:` + UUID() + ` urn:schemas-xmlsoap-org:ws:2005:04:discovery ` addr := &net.UDPAddr{ IP: net.IP{239, 255, 255, 250}, Port: 3702, } if _, err = conn.WriteTo([]byte(msg), addr); err != nil { return nil, err } if err = conn.SetReadDeadline(time.Now().Add(time.Second * 3)); err != nil { return nil, err } var hosts []string b := make([]byte, 8192) for { n, addr, err := conn.ReadFromUDP(b) if err != nil { break } // ignore printers, etc if !strings.Contains(string(b[:n]), "onvif") { continue } //log.Printf("[onvif] discovery response:\n%s", b[:n]) rawURL := FindTagValue(b[:n], "XAddrs") if rawURL == "" { continue } u, err := url.Parse(rawURL) if err != nil { continue } if u.Scheme != "http" { continue } // fix some buggy cameras // http://0.0.0.0:8080/onvif/device_service if strings.HasPrefix(u.Host, "0.0.0.0") { u.Host = addr.IP.String() + u.Host[7:] } hosts = append(hosts, u.Host) } return hosts, nil } func atoi(s string) int { if s == "" { return 0 } i, err := strconv.Atoi(s) if err != nil { return -1 } return i }