PageRenderTime 1921ms CodeModel.GetById 40ms RepoModel.GetById 0ms app.codeStats 0ms

/internal/upnp/upnp.go

https://gitlab.com/steamdriven80/syncthing
Go | 613 lines | 459 code | 112 blank | 42 comment | 118 complexity | 8aa116af1e136fd88cee64fa9311f247 MD5 | raw file
Possible License(s): LGPL-3.0, MPL-2.0-no-copyleft-exception, BSD-3-Clause, JSON
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at http://mozilla.org/MPL/2.0/.
  6. // Adapted from https://github.com/jackpal/Taipei-Torrent/blob/dd88a8bfac6431c01d959ce3c745e74b8a911793/IGD.go
  7. // Copyright (c) 2010 Jack Palevich (https://github.com/jackpal/Taipei-Torrent/blob/dd88a8bfac6431c01d959ce3c745e74b8a911793/LICENSE)
  8. // Package upnp implements UPnP InternetGatewayDevice discovery, querying, and port mapping.
  9. package upnp
  10. import (
  11. "bufio"
  12. "bytes"
  13. "encoding/xml"
  14. "errors"
  15. "fmt"
  16. "io/ioutil"
  17. "net"
  18. "net/http"
  19. "net/url"
  20. "regexp"
  21. "strings"
  22. "sync"
  23. "time"
  24. )
  25. // A container for relevant properties of a UPnP InternetGatewayDevice.
  26. type IGD struct {
  27. uuid string
  28. friendlyName string
  29. services []IGDService
  30. url *url.URL
  31. localIPAddress string
  32. }
  33. // The InternetGatewayDevice's UUID.
  34. func (n *IGD) UUID() string {
  35. return n.uuid
  36. }
  37. // The InternetGatewayDevice's friendly name.
  38. func (n *IGD) FriendlyName() string {
  39. return n.friendlyName
  40. }
  41. // The InternetGatewayDevice's friendly identifier (friendly name + IP address).
  42. func (n *IGD) FriendlyIdentifier() string {
  43. return "'" + n.FriendlyName() + "' (" + strings.Split(n.URL().Host, ":")[0] + ")"
  44. }
  45. // The URL of the InternetGatewayDevice's root device description.
  46. func (n *IGD) URL() *url.URL {
  47. return n.url
  48. }
  49. // A container for relevant properties of a UPnP service of an IGD.
  50. type IGDService struct {
  51. serviceID string
  52. serviceURL string
  53. serviceURN string
  54. }
  55. func (s *IGDService) ID() string {
  56. return s.serviceID
  57. }
  58. type Protocol string
  59. const (
  60. TCP Protocol = "TCP"
  61. UDP = "UDP"
  62. )
  63. type upnpService struct {
  64. ServiceID string `xml:"serviceId"`
  65. ServiceType string `xml:"serviceType"`
  66. ControlURL string `xml:"controlURL"`
  67. }
  68. type upnpDevice struct {
  69. DeviceType string `xml:"deviceType"`
  70. FriendlyName string `xml:"friendlyName"`
  71. Devices []upnpDevice `xml:"deviceList>device"`
  72. Services []upnpService `xml:"serviceList>service"`
  73. }
  74. type upnpRoot struct {
  75. Device upnpDevice `xml:"device"`
  76. }
  77. // Discover discovers UPnP InternetGatewayDevices.
  78. // The order in which the devices appear in the result list is not deterministic.
  79. func Discover() []IGD {
  80. var result []IGD
  81. l.Infoln("Starting UPnP discovery...")
  82. timeout := 3
  83. // Search for InternetGatewayDevice:2 devices
  84. result = append(result, discover("urn:schemas-upnp-org:device:InternetGatewayDevice:2", timeout, result)...)
  85. // Search for InternetGatewayDevice:1 devices
  86. // InternetGatewayDevice:2 devices that correctly respond to the IGD:1 request as well will not be re-added to the result list
  87. result = append(result, discover("urn:schemas-upnp-org:device:InternetGatewayDevice:1", timeout, result)...)
  88. if len(result) > 0 && debug {
  89. l.Debugln("UPnP discovery result:")
  90. for _, resultDevice := range result {
  91. l.Debugln("[" + resultDevice.uuid + "]")
  92. for _, resultService := range resultDevice.services {
  93. l.Debugln("* [" + resultService.serviceID + "] " + resultService.serviceURL)
  94. }
  95. }
  96. }
  97. suffix := "devices"
  98. if len(result) == 1 {
  99. suffix = "device"
  100. }
  101. l.Infof("UPnP discovery complete (found %d %s).", len(result), suffix)
  102. return result
  103. }
  104. // Search for UPnP InternetGatewayDevices for <timeout> seconds, ignoring responses from any devices listed in knownDevices.
  105. // The order in which the devices appear in the result list is not deterministic
  106. func discover(deviceType string, timeout int, knownDevices []IGD) []IGD {
  107. ssdp := &net.UDPAddr{IP: []byte{239, 255, 255, 250}, Port: 1900}
  108. tpl := `M-SEARCH * HTTP/1.1
  109. Host: 239.255.255.250:1900
  110. St: %s
  111. Man: "ssdp:discover"
  112. Mx: %d
  113. `
  114. searchStr := fmt.Sprintf(tpl, deviceType, timeout)
  115. search := []byte(strings.Replace(searchStr, "\n", "\r\n", -1))
  116. if debug {
  117. l.Debugln("Starting discovery of device type " + deviceType + "...")
  118. }
  119. var results []IGD
  120. resultChannel := make(chan IGD, 8)
  121. socket, err := net.ListenMulticastUDP("udp4", nil, &net.UDPAddr{IP: ssdp.IP})
  122. if err != nil {
  123. l.Infoln(err)
  124. return results
  125. }
  126. defer socket.Close() // Make sure our socket gets closed
  127. err = socket.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
  128. if err != nil {
  129. l.Infoln(err)
  130. return results
  131. }
  132. if debug {
  133. l.Debugln("Sending search request for device type " + deviceType + "...")
  134. }
  135. var resultWaitGroup sync.WaitGroup
  136. _, err = socket.WriteTo(search, ssdp)
  137. if err != nil {
  138. l.Infoln(err)
  139. return results
  140. }
  141. if debug {
  142. l.Debugln("Listening for UPnP response for device type " + deviceType + "...")
  143. }
  144. // Listen for responses until a timeout is reached
  145. for {
  146. resp := make([]byte, 1500)
  147. n, _, err := socket.ReadFrom(resp)
  148. if err != nil {
  149. if e, ok := err.(net.Error); !ok || !e.Timeout() {
  150. l.Infoln(err) //legitimate error, not a timeout.
  151. }
  152. break
  153. } else {
  154. // Process results in a separate go routine so we can immediately return to listening for more responses
  155. resultWaitGroup.Add(1)
  156. go handleSearchResponse(deviceType, knownDevices, resp, n, resultChannel, &resultWaitGroup)
  157. }
  158. }
  159. // Wait for all result handlers to finish processing, then close result channel
  160. resultWaitGroup.Wait()
  161. close(resultChannel)
  162. // Collect our results from the result handlers using the result channel
  163. for result := range resultChannel {
  164. // Check for existing results (some routers send multiple response packets)
  165. for _, existingResult := range results {
  166. if existingResult.uuid == result.uuid {
  167. if debug {
  168. l.Debugln("Already processed device with UUID", existingResult.uuid, "continuing...")
  169. }
  170. continue
  171. }
  172. }
  173. // No existing results, okay to append
  174. results = append(results, result)
  175. }
  176. if debug {
  177. l.Debugln("Discovery for device type " + deviceType + " finished.")
  178. }
  179. return results
  180. }
  181. func handleSearchResponse(deviceType string, knownDevices []IGD, resp []byte, length int, resultChannel chan<- IGD, resultWaitGroup *sync.WaitGroup) {
  182. defer resultWaitGroup.Done() // Signal when we've finished processing
  183. if debug {
  184. l.Debugln("Handling UPnP response:\n\n" + string(resp[:length]))
  185. }
  186. reader := bufio.NewReader(bytes.NewBuffer(resp[:length]))
  187. request := &http.Request{}
  188. response, err := http.ReadResponse(reader, request)
  189. if err != nil {
  190. l.Infoln(err)
  191. return
  192. }
  193. respondingDeviceType := response.Header.Get("St")
  194. if respondingDeviceType != deviceType {
  195. l.Infoln("Unrecognized UPnP device of type " + respondingDeviceType)
  196. return
  197. }
  198. deviceDescriptionLocation := response.Header.Get("Location")
  199. if deviceDescriptionLocation == "" {
  200. l.Infoln("Invalid IGD response: no location specified.")
  201. return
  202. }
  203. deviceDescriptionURL, err := url.Parse(deviceDescriptionLocation)
  204. if err != nil {
  205. l.Infoln("Invalid IGD location: " + err.Error())
  206. }
  207. deviceUSN := response.Header.Get("USN")
  208. if deviceUSN == "" {
  209. l.Infoln("Invalid IGD response: USN not specified.")
  210. return
  211. }
  212. deviceUUID := strings.TrimLeft(strings.Split(deviceUSN, "::")[0], "uuid:")
  213. matched, err := regexp.MatchString("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}", deviceUUID)
  214. if !matched {
  215. l.Infoln("Invalid IGD response: invalid device UUID", deviceUUID, "(continuing anyway)")
  216. }
  217. // Don't re-add devices that are already known
  218. for _, knownDevice := range knownDevices {
  219. if deviceUUID == knownDevice.uuid {
  220. if debug {
  221. l.Debugln("Ignoring known device with UUID " + deviceUUID)
  222. }
  223. return
  224. }
  225. }
  226. response, err = http.Get(deviceDescriptionLocation)
  227. if err != nil {
  228. l.Infoln(err)
  229. return
  230. }
  231. defer response.Body.Close()
  232. if response.StatusCode >= 400 {
  233. l.Infoln(errors.New(response.Status))
  234. return
  235. }
  236. var upnpRoot upnpRoot
  237. err = xml.NewDecoder(response.Body).Decode(&upnpRoot)
  238. if err != nil {
  239. l.Infoln(err)
  240. return
  241. }
  242. services, err := getServiceDescriptions(deviceDescriptionLocation, upnpRoot.Device)
  243. if err != nil {
  244. l.Infoln(err)
  245. return
  246. }
  247. // Figure out our IP number, on the network used to reach the IGD.
  248. // We do this in a fairly roundabout way by connecting to the IGD and
  249. // checking the address of the local end of the socket. I'm open to
  250. // suggestions on a better way to do this...
  251. localIPAddress, err := localIP(deviceDescriptionURL)
  252. if err != nil {
  253. l.Infoln(err)
  254. return
  255. }
  256. igd := IGD{
  257. uuid: deviceUUID,
  258. friendlyName: upnpRoot.Device.FriendlyName,
  259. url: deviceDescriptionURL,
  260. services: services,
  261. localIPAddress: localIPAddress,
  262. }
  263. resultChannel <- igd
  264. if debug {
  265. l.Debugln("Finished handling of UPnP response.")
  266. }
  267. }
  268. func localIP(url *url.URL) (string, error) {
  269. conn, err := net.Dial("tcp", url.Host)
  270. if err != nil {
  271. return "", err
  272. }
  273. defer conn.Close()
  274. localIPAddress, _, err := net.SplitHostPort(conn.LocalAddr().String())
  275. if err != nil {
  276. return "", err
  277. }
  278. return localIPAddress, nil
  279. }
  280. func getChildDevices(d upnpDevice, deviceType string) []upnpDevice {
  281. var result []upnpDevice
  282. for _, dev := range d.Devices {
  283. if dev.DeviceType == deviceType {
  284. result = append(result, dev)
  285. }
  286. }
  287. return result
  288. }
  289. func getChildServices(d upnpDevice, serviceType string) []upnpService {
  290. var result []upnpService
  291. for _, svc := range d.Services {
  292. if svc.ServiceType == serviceType {
  293. result = append(result, svc)
  294. }
  295. }
  296. return result
  297. }
  298. func getServiceDescriptions(rootURL string, device upnpDevice) ([]IGDService, error) {
  299. var result []IGDService
  300. if device.DeviceType == "urn:schemas-upnp-org:device:InternetGatewayDevice:1" {
  301. descriptions := getIGDServices(rootURL, device,
  302. "urn:schemas-upnp-org:device:WANDevice:1",
  303. "urn:schemas-upnp-org:device:WANConnectionDevice:1",
  304. []string{"urn:schemas-upnp-org:service:WANIPConnection:1", "urn:schemas-upnp-org:service:WANPPPConnection:1"})
  305. result = append(result, descriptions...)
  306. } else if device.DeviceType == "urn:schemas-upnp-org:device:InternetGatewayDevice:2" {
  307. descriptions := getIGDServices(rootURL, device,
  308. "urn:schemas-upnp-org:device:WANDevice:2",
  309. "urn:schemas-upnp-org:device:WANConnectionDevice:2",
  310. []string{"urn:schemas-upnp-org:service:WANIPConnection:2", "urn:schemas-upnp-org:service:WANPPPConnection:1"})
  311. result = append(result, descriptions...)
  312. } else {
  313. return result, errors.New("[" + rootURL + "] Malformed root device description: not an InternetGatewayDevice.")
  314. }
  315. if len(result) < 1 {
  316. return result, errors.New("[" + rootURL + "] Malformed device description: no compatible service descriptions found.")
  317. } else {
  318. return result, nil
  319. }
  320. }
  321. func getIGDServices(rootURL string, device upnpDevice, wanDeviceURN string, wanConnectionURN string, serviceURNs []string) []IGDService {
  322. var result []IGDService
  323. devices := getChildDevices(device, wanDeviceURN)
  324. if len(devices) < 1 {
  325. l.Infoln("[" + rootURL + "] Malformed InternetGatewayDevice description: no WANDevices specified.")
  326. return result
  327. }
  328. for _, device := range devices {
  329. connections := getChildDevices(device, wanConnectionURN)
  330. if len(connections) < 1 {
  331. l.Infoln("[" + rootURL + "] Malformed " + wanDeviceURN + " description: no WANConnectionDevices specified.")
  332. }
  333. for _, connection := range connections {
  334. for _, serviceURN := range serviceURNs {
  335. services := getChildServices(connection, serviceURN)
  336. if len(services) < 1 && debug {
  337. l.Debugln("[" + rootURL + "] No services of type " + serviceURN + " found on connection.")
  338. }
  339. for _, service := range services {
  340. if len(service.ControlURL) == 0 {
  341. l.Infoln("[" + rootURL + "] Malformed " + service.ServiceType + " description: no control URL.")
  342. } else {
  343. u, _ := url.Parse(rootURL)
  344. replaceRawPath(u, service.ControlURL)
  345. if debug {
  346. l.Debugln("[" + rootURL + "] Found " + service.ServiceType + " with URL " + u.String())
  347. }
  348. service := IGDService{serviceID: service.ServiceID, serviceURL: u.String(), serviceURN: service.ServiceType}
  349. result = append(result, service)
  350. }
  351. }
  352. }
  353. }
  354. }
  355. return result
  356. }
  357. func replaceRawPath(u *url.URL, rp string) {
  358. asURL, err := url.Parse(rp)
  359. if err != nil {
  360. return
  361. } else if asURL.IsAbs() {
  362. u.Path = asURL.Path
  363. u.RawQuery = asURL.RawQuery
  364. } else {
  365. var p, q string
  366. fs := strings.Split(rp, "?")
  367. p = fs[0]
  368. if len(fs) > 1 {
  369. q = fs[1]
  370. }
  371. if p[0] == '/' {
  372. u.Path = p
  373. } else {
  374. u.Path += p
  375. }
  376. u.RawQuery = q
  377. }
  378. }
  379. func soapRequest(url, service, function, message string) ([]byte, error) {
  380. tpl := `<?xml version="1.0" ?>
  381. <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  382. <s:Body>%s</s:Body>
  383. </s:Envelope>
  384. `
  385. var resp []byte
  386. body := fmt.Sprintf(tpl, message)
  387. req, err := http.NewRequest("POST", url, strings.NewReader(body))
  388. if err != nil {
  389. return resp, err
  390. }
  391. req.Header.Set("Content-Type", `text/xml; charset="utf-8"`)
  392. req.Header.Set("User-Agent", "syncthing/1.0")
  393. req.Header.Set("SOAPAction", fmt.Sprintf(`"%s#%s"`, service, function))
  394. req.Header.Set("Connection", "Close")
  395. req.Header.Set("Cache-Control", "no-cache")
  396. req.Header.Set("Pragma", "no-cache")
  397. if debug {
  398. l.Debugln("SOAP Request URL: " + url)
  399. l.Debugln("SOAP Action: " + req.Header.Get("SOAPAction"))
  400. l.Debugln("SOAP Request:\n\n" + body)
  401. }
  402. r, err := http.DefaultClient.Do(req)
  403. if err != nil {
  404. return resp, err
  405. }
  406. resp, _ = ioutil.ReadAll(r.Body)
  407. if debug {
  408. l.Debugln("SOAP Response:\n\n" + string(resp) + "\n")
  409. }
  410. r.Body.Close()
  411. if r.StatusCode >= 400 {
  412. return resp, errors.New(function + ": " + r.Status)
  413. }
  414. return resp, nil
  415. }
  416. // Add a port mapping to all relevant services on the specified InternetGatewayDevice.
  417. // Port mapping will fail and return an error if action is fails for _any_ of the relevant services.
  418. // For this reason, it is generally better to configure port mapping for each individual service instead.
  419. func (n *IGD) AddPortMapping(protocol Protocol, externalPort, internalPort int, description string, timeout int) error {
  420. for _, service := range n.services {
  421. err := service.AddPortMapping(n.localIPAddress, protocol, externalPort, internalPort, description, timeout)
  422. if err != nil {
  423. return err
  424. }
  425. }
  426. return nil
  427. }
  428. // Delete a port mapping from all relevant services on the specified InternetGatewayDevice.
  429. // Port mapping will fail and return an error if action is fails for _any_ of the relevant services.
  430. // For this reason, it is generally better to configure port mapping for each individual service instead.
  431. func (n *IGD) DeletePortMapping(protocol Protocol, externalPort int) error {
  432. for _, service := range n.services {
  433. err := service.DeletePortMapping(protocol, externalPort)
  434. if err != nil {
  435. return err
  436. }
  437. }
  438. return nil
  439. }
  440. type soapGetExternalIPAddressResponseEnvelope struct {
  441. XMLName xml.Name
  442. Body soapGetExternalIPAddressResponseBody `xml:"Body"`
  443. }
  444. type soapGetExternalIPAddressResponseBody struct {
  445. XMLName xml.Name
  446. GetExternalIPAddressResponse getExternalIPAddressResponse `xml:"GetExternalIPAddressResponse"`
  447. }
  448. type getExternalIPAddressResponse struct {
  449. NewExternalIPAddress string `xml:"NewExternalIPAddress"`
  450. }
  451. // Add a port mapping to the specified IGD service.
  452. func (s *IGDService) AddPortMapping(localIPAddress string, protocol Protocol, externalPort, internalPort int, description string, timeout int) error {
  453. tpl := `<u:AddPortMapping xmlns:u="%s">
  454. <NewRemoteHost></NewRemoteHost>
  455. <NewExternalPort>%d</NewExternalPort>
  456. <NewProtocol>%s</NewProtocol>
  457. <NewInternalPort>%d</NewInternalPort>
  458. <NewInternalClient>%s</NewInternalClient>
  459. <NewEnabled>1</NewEnabled>
  460. <NewPortMappingDescription>%s</NewPortMappingDescription>
  461. <NewLeaseDuration>%d</NewLeaseDuration>
  462. </u:AddPortMapping>`
  463. body := fmt.Sprintf(tpl, s.serviceURN, externalPort, protocol, internalPort, localIPAddress, description, timeout)
  464. _, err := soapRequest(s.serviceURL, s.serviceURN, "AddPortMapping", body)
  465. if err != nil {
  466. return err
  467. }
  468. return nil
  469. }
  470. // Delete a port mapping from the specified IGD service.
  471. func (s *IGDService) DeletePortMapping(protocol Protocol, externalPort int) error {
  472. tpl := `<u:DeletePortMapping xmlns:u="%s">
  473. <NewRemoteHost></NewRemoteHost>
  474. <NewExternalPort>%d</NewExternalPort>
  475. <NewProtocol>%s</NewProtocol>
  476. </u:DeletePortMapping>`
  477. body := fmt.Sprintf(tpl, s.serviceURN, externalPort, protocol)
  478. _, err := soapRequest(s.serviceURL, s.serviceURN, "DeletePortMapping", body)
  479. if err != nil {
  480. return err
  481. }
  482. return nil
  483. }
  484. // Query the IGD service for its external IP address.
  485. // Returns nil if the external IP address is invalid or undefined, along with any relevant errors
  486. func (s *IGDService) GetExternalIPAddress() (net.IP, error) {
  487. tpl := `<u:GetExternalIPAddress xmlns:u="%s" />`
  488. body := fmt.Sprintf(tpl, s.serviceURN)
  489. response, err := soapRequest(s.serviceURL, s.serviceURN, "GetExternalIPAddress", body)
  490. if err != nil {
  491. return nil, err
  492. }
  493. envelope := &soapGetExternalIPAddressResponseEnvelope{}
  494. err = xml.Unmarshal(response, envelope)
  495. if err != nil {
  496. return nil, err
  497. }
  498. result := net.ParseIP(envelope.Body.GetExternalIPAddressResponse.NewExternalIPAddress)
  499. return result, nil
  500. }