You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

375 lines
11 KiB

11 years ago
11 years ago
11 years ago
  1. // from taipei-torrent
  2. package peer
  3. // Just enough UPnP to be able to forward ports
  4. //
  5. import (
  6. "bytes"
  7. "encoding/xml"
  8. "errors"
  9. "io/ioutil"
  10. "net"
  11. "net/http"
  12. "strconv"
  13. "strings"
  14. "time"
  15. )
  16. type upnpNAT struct {
  17. serviceURL string
  18. ourIP string
  19. urnDomain string
  20. }
  21. // protocol is either "udp" or "tcp"
  22. type NAT interface {
  23. GetExternalAddress() (addr net.IP, err error)
  24. AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error)
  25. DeletePortMapping(protocol string, externalPort, internalPort int) (err error)
  26. }
  27. func Discover() (nat NAT, err error) {
  28. ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
  29. if err != nil {
  30. return
  31. }
  32. conn, err := net.ListenPacket("udp4", ":0")
  33. if err != nil {
  34. return
  35. }
  36. socket := conn.(*net.UDPConn)
  37. defer socket.Close()
  38. err = socket.SetDeadline(time.Now().Add(3 * time.Second))
  39. if err != nil {
  40. return
  41. }
  42. st := "InternetGatewayDevice:1"
  43. buf := bytes.NewBufferString(
  44. "M-SEARCH * HTTP/1.1\r\n" +
  45. "HOST: 239.255.255.250:1900\r\n" +
  46. "ST: ssdp:all\r\n" +
  47. "MAN: \"ssdp:discover\"\r\n" +
  48. "MX: 2\r\n\r\n")
  49. message := buf.Bytes()
  50. answerBytes := make([]byte, 1024)
  51. for i := 0; i < 3; i++ {
  52. _, err = socket.WriteToUDP(message, ssdp)
  53. if err != nil {
  54. return
  55. }
  56. var n int
  57. n, _, err = socket.ReadFromUDP(answerBytes)
  58. for {
  59. n, _, err = socket.ReadFromUDP(answerBytes)
  60. if err != nil {
  61. break
  62. }
  63. answer := string(answerBytes[0:n])
  64. if strings.Index(answer, st) < 0 {
  65. continue
  66. }
  67. // HTTP header field names are case-insensitive.
  68. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
  69. locString := "\r\nlocation:"
  70. answer = strings.ToLower(answer)
  71. locIndex := strings.Index(answer, locString)
  72. if locIndex < 0 {
  73. continue
  74. }
  75. loc := answer[locIndex+len(locString):]
  76. endIndex := strings.Index(loc, "\r\n")
  77. if endIndex < 0 {
  78. continue
  79. }
  80. locURL := strings.TrimSpace(loc[0:endIndex])
  81. var serviceURL, urnDomain string
  82. serviceURL, urnDomain, err = getServiceURL(locURL)
  83. if err != nil {
  84. return
  85. }
  86. var ourIP net.IP
  87. ourIP, err = localIPv4()
  88. if err != nil {
  89. return
  90. }
  91. nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP.String(), urnDomain: urnDomain}
  92. return
  93. }
  94. }
  95. err = errors.New("UPnP port discovery failed.")
  96. return
  97. }
  98. type Envelope struct {
  99. XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
  100. Soap *SoapBody
  101. }
  102. type SoapBody struct {
  103. XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
  104. ExternalIP *ExternalIPAddressResponse
  105. }
  106. type ExternalIPAddressResponse struct {
  107. XMLName xml.Name `xml:"GetExternalIPAddressResponse"`
  108. IPAddress string `xml:"NewExternalIPAddress"`
  109. }
  110. type ExternalIPAddress struct {
  111. XMLName xml.Name `xml:"NewExternalIPAddress"`
  112. IP string
  113. }
  114. type Service struct {
  115. ServiceType string `xml:"serviceType"`
  116. ControlURL string `xml:"controlURL"`
  117. }
  118. type DeviceList struct {
  119. Device []Device `xml:"device"`
  120. }
  121. type ServiceList struct {
  122. Service []Service `xml:"service"`
  123. }
  124. type Device struct {
  125. XMLName xml.Name `xml:"device"`
  126. DeviceType string `xml:"deviceType"`
  127. DeviceList DeviceList `xml:"deviceList"`
  128. ServiceList ServiceList `xml:"serviceList"`
  129. }
  130. type Root struct {
  131. Device Device
  132. }
  133. func getChildDevice(d *Device, deviceType string) *Device {
  134. dl := d.DeviceList.Device
  135. for i := 0; i < len(dl); i++ {
  136. if strings.Index(dl[i].DeviceType, deviceType) >= 0 {
  137. return &dl[i]
  138. }
  139. }
  140. return nil
  141. }
  142. func getChildService(d *Device, serviceType string) *Service {
  143. sl := d.ServiceList.Service
  144. for i := 0; i < len(sl); i++ {
  145. if strings.Index(sl[i].ServiceType, serviceType) >= 0 {
  146. return &sl[i]
  147. }
  148. }
  149. return nil
  150. }
  151. func localIPv4() (net.IP, error) {
  152. tt, err := net.Interfaces()
  153. if err != nil {
  154. return nil, err
  155. }
  156. for _, t := range tt {
  157. aa, err := t.Addrs()
  158. if err != nil {
  159. return nil, err
  160. }
  161. for _, a := range aa {
  162. ipnet, ok := a.(*net.IPNet)
  163. if !ok {
  164. continue
  165. }
  166. v4 := ipnet.IP.To4()
  167. if v4 == nil || v4[0] == 127 { // loopback address
  168. continue
  169. }
  170. return v4, nil
  171. }
  172. }
  173. return nil, errors.New("cannot find local IP address")
  174. }
  175. func getServiceURL(rootURL string) (url, urnDomain string, err error) {
  176. r, err := http.Get(rootURL)
  177. if err != nil {
  178. return
  179. }
  180. defer r.Body.Close()
  181. if r.StatusCode >= 400 {
  182. err = errors.New(string(r.StatusCode))
  183. return
  184. }
  185. var root Root
  186. err = xml.NewDecoder(r.Body).Decode(&root)
  187. if err != nil {
  188. return
  189. }
  190. a := &root.Device
  191. if strings.Index(a.DeviceType, "InternetGatewayDevice:1") < 0 {
  192. err = errors.New("No InternetGatewayDevice")
  193. return
  194. }
  195. b := getChildDevice(a, "WANDevice:1")
  196. if b == nil {
  197. err = errors.New("No WANDevice")
  198. return
  199. }
  200. c := getChildDevice(b, "WANConnectionDevice:1")
  201. if c == nil {
  202. err = errors.New("No WANConnectionDevice")
  203. return
  204. }
  205. d := getChildService(c, "WANIPConnection:1")
  206. if d == nil {
  207. // Some routers don't follow the UPnP spec, and put WanIPConnection under WanDevice,
  208. // instead of under WanConnectionDevice
  209. d = getChildService(b, "WANIPConnection:1")
  210. if d == nil {
  211. err = errors.New("No WANIPConnection")
  212. return
  213. }
  214. }
  215. // Extract the domain name, which isn't always 'schemas-upnp-org'
  216. urnDomain = strings.Split(d.ServiceType, ":")[1]
  217. url = combineURL(rootURL, d.ControlURL)
  218. return
  219. }
  220. func combineURL(rootURL, subURL string) string {
  221. protocolEnd := "://"
  222. protoEndIndex := strings.Index(rootURL, protocolEnd)
  223. a := rootURL[protoEndIndex+len(protocolEnd):]
  224. rootIndex := strings.Index(a, "/")
  225. return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL
  226. }
  227. func soapRequest(url, function, message, domain string) (r *http.Response, err error) {
  228. fullMessage := "<?xml version=\"1.0\" ?>" +
  229. "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
  230. "<s:Body>" + message + "</s:Body></s:Envelope>"
  231. req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage))
  232. if err != nil {
  233. return nil, err
  234. }
  235. req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"")
  236. req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3")
  237. //req.Header.Set("Transfer-Encoding", "chunked")
  238. req.Header.Set("SOAPAction", "\"urn:"+domain+":service:WANIPConnection:1#"+function+"\"")
  239. req.Header.Set("Connection", "Close")
  240. req.Header.Set("Cache-Control", "no-cache")
  241. req.Header.Set("Pragma", "no-cache")
  242. // log.Stderr("soapRequest ", req)
  243. r, err = http.DefaultClient.Do(req)
  244. if err != nil {
  245. return nil, err
  246. }
  247. /*if r.Body != nil {
  248. defer r.Body.Close()
  249. }*/
  250. if r.StatusCode >= 400 {
  251. // log.Stderr(function, r.StatusCode)
  252. err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
  253. r = nil
  254. return
  255. }
  256. return
  257. }
  258. type statusInfo struct {
  259. externalIpAddress string
  260. }
  261. func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) {
  262. message := "<u:GetExternalIPAddress xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
  263. "</u:GetExternalIPAddress>"
  264. var response *http.Response
  265. response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain)
  266. if response != nil {
  267. defer response.Body.Close()
  268. }
  269. if err != nil {
  270. return
  271. }
  272. var envelope Envelope
  273. data, err := ioutil.ReadAll(response.Body)
  274. reader := bytes.NewReader(data)
  275. xml.NewDecoder(reader).Decode(&envelope)
  276. info = statusInfo{envelope.Soap.ExternalIP.IPAddress}
  277. if err != nil {
  278. return
  279. }
  280. return
  281. }
  282. func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
  283. info, err := n.getExternalIPAddress()
  284. if err != nil {
  285. return
  286. }
  287. addr = net.ParseIP(info.externalIpAddress)
  288. return
  289. }
  290. func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) {
  291. // A single concatenation would break ARM compilation.
  292. message := "<u:AddPortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
  293. "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)
  294. message += "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>"
  295. message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +
  296. "<NewInternalClient>" + n.ourIP + "</NewInternalClient>" +
  297. "<NewEnabled>1</NewEnabled><NewPortMappingDescription>"
  298. message += description +
  299. "</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +
  300. "</NewLeaseDuration></u:AddPortMapping>"
  301. var response *http.Response
  302. response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain)
  303. if response != nil {
  304. defer response.Body.Close()
  305. }
  306. if err != nil {
  307. return
  308. }
  309. // TODO: check response to see if the port was forwarded
  310. // log.Println(message, response)
  311. mappedExternalPort = externalPort
  312. _ = response
  313. return
  314. }
  315. func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {
  316. message := "<u:DeletePortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
  317. "<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
  318. "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" +
  319. "</u:DeletePortMapping>"
  320. var response *http.Response
  321. response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain)
  322. if response != nil {
  323. defer response.Body.Close()
  324. }
  325. if err != nil {
  326. return
  327. }
  328. // TODO: check response to see if the port was deleted
  329. // log.Println(message, response)
  330. _ = response
  331. return
  332. }