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.

380 lines
9.6 KiB

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