ssdp.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package ssdp
  2. import (
  3. "errors"
  4. "log"
  5. "net"
  6. "net/http"
  7. "os/exec"
  8. "runtime"
  9. "strconv"
  10. "strings"
  11. "time"
  12. ssdp "github.com/koron/go-ssdp"
  13. "imuslab.com/arozos/mod/utils"
  14. )
  15. type SSDPOption struct {
  16. URLBase string
  17. Hostname string
  18. Vendor string
  19. VendorURL string
  20. ModelName string
  21. ModelDesc string
  22. Serial string
  23. UUID string
  24. }
  25. type SSDPHost struct {
  26. ADV *ssdp.Advertiser
  27. advStarted bool
  28. SSDPTemplateFile string
  29. Option *SSDPOption
  30. quit chan bool
  31. }
  32. func NewSSDPHost(outboundIP string, port int, templateFile string, option SSDPOption) (*SSDPHost, error) {
  33. if runtime.GOOS == "linux" {
  34. //In case there are more than 1 network interface connect to the same LAN, choose the first one by default
  35. interfaceName, err := getFirstNetworkInterfaceName()
  36. if err != nil {
  37. //Ignore the interface binding
  38. log.Println("[WARN] No connected network interface. Starting SSDP anyway.")
  39. } else {
  40. mainNIC, err := net.InterfaceByName(interfaceName)
  41. if err != nil {
  42. log.Println("[WARN] Unable to get interface by name: " + interfaceName + ". Starting SSDP on all IPv4 interfaces.")
  43. } else {
  44. ssdp.Interfaces = []net.Interface{*mainNIC}
  45. }
  46. }
  47. }
  48. ad, err := ssdp.Advertise(
  49. "upnp:rootdevice", // send as "ST"
  50. "uuid:"+option.UUID, // send as "USN"
  51. "http://"+outboundIP+":"+strconv.Itoa(port)+"/ssdp.xml", // send as "LOCATION"
  52. "arozos/"+outboundIP, // send as "SERVER"
  53. 30) // send as "maxAge" in "CACHE-CONTROL"
  54. if err != nil {
  55. return &SSDPHost{}, err
  56. }
  57. return &SSDPHost{
  58. ADV: ad,
  59. advStarted: false,
  60. SSDPTemplateFile: templateFile,
  61. Option: &option,
  62. }, nil
  63. }
  64. func (a *SSDPHost) Start() {
  65. //Advertise ssdp
  66. http.HandleFunc("/ssdp.xml", a.handleSSDP)
  67. log.Println("Starting SSDP Discovery Service: " + a.Option.URLBase)
  68. var aliveTick <-chan time.Time
  69. aliveTick = time.Tick(time.Duration(5) * time.Second)
  70. quit := make(chan bool)
  71. a.quit = quit
  72. a.advStarted = true
  73. go func(ad *ssdp.Advertiser) {
  74. for {
  75. select {
  76. case <-aliveTick:
  77. if ad != nil {
  78. ad.Alive()
  79. }
  80. case <-quit:
  81. ad.Bye()
  82. ad.Close()
  83. break
  84. }
  85. }
  86. }(a.ADV)
  87. }
  88. func (a *SSDPHost) Close() {
  89. if a != nil {
  90. if a.advStarted {
  91. a.quit <- true
  92. }
  93. }
  94. }
  95. // Serve the xml file with the given properties
  96. func (a *SSDPHost) handleSSDP(w http.ResponseWriter, r *http.Request) {
  97. //Load the ssdp xml from file
  98. template, err := utils.Templateload(a.SSDPTemplateFile, map[string]string{
  99. "urlbase": a.Option.URLBase,
  100. "hostname": a.Option.Hostname,
  101. "vendor": a.Option.Vendor,
  102. "vendorurl": a.Option.VendorURL,
  103. "modeldesc": a.Option.ModelDesc,
  104. "modelname": a.Option.ModelName,
  105. "uuid": a.Option.UUID,
  106. "serial": a.Option.Serial,
  107. })
  108. if err != nil {
  109. w.Write([]byte("SSDP.XML NOT FOUND"))
  110. return
  111. }
  112. w.Write([]byte(template))
  113. }
  114. // Helper functions
  115. func getFirstNetworkInterfaceName() (string, error) {
  116. if runtime.GOOS == "linux" {
  117. if pkg_exists("ip") {
  118. //Use the fast method
  119. cmd := exec.Command("bash", "-c", `ip route | grep default | sed -e "s/^.*dev.//" -e "s/.proto.*//"`)
  120. out, _ := cmd.CombinedOutput()
  121. if strings.TrimSpace(string(out)) == "" {
  122. //No interface found.
  123. return "", errors.New("No interface found")
  124. } else {
  125. return strings.Split(strings.TrimSpace(string(out)), "\n")[0], nil
  126. }
  127. } else if pkg_exists("ifconfig") {
  128. //Guess it from ifconfig list
  129. cmd := exec.Command("bash", "-c", `ifconfig -a | sed -E 's/[[:space:]:].*//;/^$/d'`)
  130. out, _ := cmd.CombinedOutput()
  131. if strings.TrimSpace(string(out)) == "" {
  132. //No interface found.
  133. return "", errors.New("No interface found")
  134. } else {
  135. return strings.Split(strings.TrimSpace(string(out)), "\n")[0], nil
  136. }
  137. }
  138. }
  139. return "", errors.New("Not supported platform or missing package")
  140. }
  141. func pkg_exists(pkgname string) bool {
  142. cmd := exec.Command("which", pkgname)
  143. out, _ := cmd.CombinedOutput()
  144. if len(string(out)) > 1 {
  145. return true
  146. } else {
  147. return false
  148. }
  149. }