discovery.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. package discovery
  2. import (
  3. "errors"
  4. "net"
  5. "regexp"
  6. "strings"
  7. "time"
  8. "github.com/clbanning/mxj"
  9. uuid "github.com/satori/go.uuid"
  10. )
  11. var errWrongDiscoveryResponse = errors.New("Response is not related to discovery request")
  12. // Device contains data of ONVIF camera
  13. type Device struct {
  14. ID string
  15. Name string
  16. XAddr string
  17. Type string
  18. }
  19. // StartDiscovery send a WS-Discovery message and wait for all matching device to respond
  20. func StartDiscovery(duration time.Duration) ([]Device, error) {
  21. // Get list of interface address
  22. addrs, err := net.InterfaceAddrs()
  23. if err != nil {
  24. return []Device{}, err
  25. }
  26. // Fetch IPv4 address
  27. ipAddrs := []string{}
  28. for _, addr := range addrs {
  29. ipAddr, ok := addr.(*net.IPNet)
  30. if ok && !ipAddr.IP.IsLoopback() && ipAddr.IP.To4() != nil {
  31. ipAddrs = append(ipAddrs, ipAddr.IP.String())
  32. }
  33. }
  34. // Create initial discovery results
  35. discoveryResults := []Device{}
  36. // Discover device on each interface's network
  37. for _, ipAddr := range ipAddrs {
  38. devices, err := discoverDevices(ipAddr, duration)
  39. if err != nil {
  40. // return []Device{}, err
  41. continue
  42. }
  43. for _, dd := range devices {
  44. var found bool
  45. for _, d := range discoveryResults {
  46. if d.ID == dd.ID {
  47. found = true
  48. break
  49. }
  50. }
  51. if !found {
  52. discoveryResults = append(discoveryResults, dd)
  53. }
  54. }
  55. }
  56. if len(discoveryResults) == 0 && err != nil {
  57. return []Device{}, err
  58. }
  59. return discoveryResults, nil
  60. }
  61. func discoverDevices(ipAddr string, duration time.Duration) ([]Device, error) {
  62. // Create WS-Discovery request
  63. requestID := "uuid:" + uuid.NewV4().String()
  64. request := `
  65. <?xml version="1.0" encoding="UTF-8"?>
  66. <e:Envelope
  67. xmlns:e="http://www.w3.org/2003/05/soap-envelope"
  68. xmlns:w="http://schemas.xmlsoap.org/ws/2004/08/addressing"
  69. xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"
  70. xmlns:dn="http://www.onvif.org/ver10/network/wsdl">
  71. <e:Header>
  72. <w:MessageID>` + requestID + `</w:MessageID>
  73. <w:To e:mustUnderstand="true">urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To>
  74. <w:Action a:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe
  75. </w:Action>
  76. </e:Header>
  77. <e:Body>
  78. <d:Probe>
  79. <d:Types>dn:NetworkVideoTransmitter</d:Types>
  80. </d:Probe>
  81. </e:Body>
  82. </e:Envelope>`
  83. // Clean WS-Discovery message
  84. request = regexp.MustCompile(`\>\s+\<`).ReplaceAllString(request, "><")
  85. request = regexp.MustCompile(`\s+`).ReplaceAllString(request, " ")
  86. // Create UDP address for local and multicast address
  87. localAddress, err := net.ResolveUDPAddr("udp4", ipAddr+":0")
  88. if err != nil {
  89. return []Device{}, err
  90. }
  91. multicastAddress, err := net.ResolveUDPAddr("udp4", "239.255.255.250:3702")
  92. if err != nil {
  93. return []Device{}, err
  94. }
  95. // Create UDP connection to listen for respond from matching device
  96. conn, err := net.ListenUDP("udp", localAddress)
  97. if err != nil {
  98. return []Device{}, err
  99. }
  100. defer conn.Close()
  101. // Set connection's timeout
  102. err = conn.SetDeadline(time.Now().Add(duration))
  103. if err != nil {
  104. return []Device{}, err
  105. }
  106. // Send WS-Discovery request to multicast address
  107. _, err = conn.WriteToUDP([]byte(request), multicastAddress)
  108. if err != nil {
  109. return []Device{}, err
  110. }
  111. // Create initial discovery results
  112. discoveryResults := []Device{}
  113. // Keep reading UDP message until timeout
  114. for {
  115. // Create buffer and receive UDP response
  116. buffer := make([]byte, 10*1024)
  117. _, _, err = conn.ReadFromUDP(buffer)
  118. // Check if connection timeout
  119. if err != nil {
  120. if udpErr, ok := err.(net.Error); ok && udpErr.Timeout() {
  121. break
  122. } else {
  123. return discoveryResults, err
  124. }
  125. }
  126. // Read and parse WS-Discovery response
  127. device, err := readDiscoveryResponse(requestID, buffer)
  128. if err != nil && err != errWrongDiscoveryResponse {
  129. return discoveryResults, err
  130. }
  131. // Push device to results
  132. discoveryResults = append(discoveryResults, device)
  133. }
  134. return discoveryResults, nil
  135. }
  136. // readDiscoveryResponse reads and parses WS-Discovery response
  137. func readDiscoveryResponse(messageID string, buffer []byte) (Device, error) {
  138. // Inital result
  139. result := Device{}
  140. // Parse XML to map
  141. mapXML, err := mxj.NewMapXml(buffer)
  142. if err != nil {
  143. return result, err
  144. }
  145. // Check if this response is for our request
  146. responseMessageID, _ := mapXML.ValueForPathString("Envelope.Header.RelatesTo")
  147. if responseMessageID != messageID {
  148. return result, errWrongDiscoveryResponse
  149. }
  150. // Get device's ID and clean it
  151. deviceID, _ := mapXML.ValueForPathString("Envelope.Body.ProbeMatches.ProbeMatch.EndpointReference.Address")
  152. deviceID = strings.Replace(deviceID, "urn:uuid:", "", 1)
  153. // Get device's name
  154. deviceName := ""
  155. scopes, _ := mapXML.ValueForPathString("Envelope.Body.ProbeMatches.ProbeMatch.Scopes")
  156. for _, scope := range strings.Split(scopes, " ") {
  157. if strings.HasPrefix(scope, "onvif://www.onvif.org/name/") {
  158. deviceName = strings.Replace(scope, "onvif://www.onvif.org/name/", "", 1)
  159. deviceName = strings.Replace(deviceName, "_", " ", -1)
  160. break
  161. }
  162. }
  163. // Get device's xAddrs
  164. xAddrs, _ := mapXML.ValueForPathString("Envelope.Body.ProbeMatches.ProbeMatch.XAddrs")
  165. listXAddr := strings.Split(xAddrs, " ")
  166. if len(listXAddr) == 0 {
  167. return result, errors.New("Device does not have any xAddr")
  168. }
  169. // Get device's Types
  170. Types, _ := mapXML.ValueForPathString("Envelope.Body.ProbeMatches.ProbeMatch.Types")
  171. listTypes := strings.Split(Types, " ")
  172. if len(listTypes) == 0 {
  173. return result, errors.New("Device does not have any Types")
  174. } else {
  175. if list := strings.Split(listTypes[0], ":"); len(list) == 2 {
  176. result.Type = list[1]
  177. } else {
  178. result.Type = listTypes[0]
  179. }
  180. }
  181. // Finalize result
  182. result.ID = deviceID
  183. result.Name = deviceName
  184. result.XAddr = listXAddr[0]
  185. return result, nil
  186. }