github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/gateway/upnp.go (about) 1 package gateway 2 3 import ( 4 "errors" 5 "io" 6 "io/ioutil" 7 "net" 8 "net/http" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/NebulousLabs/go-upnp" 14 15 "github.com/Synthesix/Sia/build" 16 "github.com/Synthesix/Sia/modules" 17 ) 18 19 // myExternalIP discovers the gateway's external IP by querying a centralized 20 // service, http://myexternalip.com. 21 func myExternalIP() (string, error) { 22 // timeout after 10 seconds 23 client := http.Client{Timeout: time.Duration(10 * time.Second)} 24 resp, err := client.Get("http://myexternalip.com/raw") 25 if err != nil { 26 return "", err 27 } 28 defer resp.Body.Close() 29 if resp.StatusCode != http.StatusOK { 30 errResp, _ := ioutil.ReadAll(resp.Body) 31 return "", errors.New(string(errResp)) 32 } 33 buf, err := ioutil.ReadAll(io.LimitReader(resp.Body, 64)) 34 if err != nil { 35 return "", err 36 } 37 if len(buf) == 0 { 38 return "", errors.New("myexternalip.com returned a 0 length IP address") 39 } 40 // trim newline 41 return strings.TrimSpace(string(buf)), nil 42 } 43 44 // threadedLearnHostname discovers the external IP of the Gateway regularly. 45 func (g *Gateway) threadedLearnHostname() { 46 if err := g.threads.Add(); err != nil { 47 return 48 } 49 defer g.threads.Done() 50 51 if build.Release == "testing" { 52 return 53 } 54 55 for { 56 // try UPnP first, then fallback to myexternalip.com and peer-to-peer 57 // discovery. 58 var host string 59 d, err := upnp.Discover() 60 if err == nil { 61 host, err = d.ExternalIP() 62 } 63 if !build.DEBUG && err != nil { 64 host, err = myExternalIP() 65 } 66 if err != nil { 67 host, err = g.managedIPFromPeers() 68 } 69 if err != nil { 70 g.log.Println("WARN: failed to discover external IP:", err) 71 } 72 // If we were unable to discover our IP we try again later. 73 if err != nil { 74 if !g.managedSleep(rediscoverIPIntervalFailure) { 75 return // shutdown interrupted sleep 76 } 77 continue 78 } 79 80 g.mu.RLock() 81 addr := modules.NetAddress(net.JoinHostPort(host, g.port)) 82 g.mu.RUnlock() 83 if err := addr.IsValid(); err != nil { 84 g.log.Printf("WARN: discovered hostname %q is invalid: %v", addr, err) 85 if err != nil { 86 if !g.managedSleep(rediscoverIPIntervalFailure) { 87 return // shutdown interrupted sleep 88 } 89 continue 90 } 91 } 92 93 g.mu.Lock() 94 g.myAddr = addr 95 g.mu.Unlock() 96 97 g.log.Println("INFO: our address is", addr) 98 99 // Rediscover the IP later in case it changed. 100 if !g.managedSleep(rediscoverIPIntervalSuccess) { 101 return // shutdown interrupted sleep 102 } 103 } 104 } 105 106 // threadedForwardPort adds a port mapping to the router. 107 func (g *Gateway) threadedForwardPort(port string) { 108 if err := g.threads.Add(); err != nil { 109 return 110 } 111 defer g.threads.Done() 112 113 if build.Release == "testing" { 114 return 115 } 116 117 d, err := upnp.Discover() 118 if err != nil { 119 g.log.Printf("WARN: could not automatically forward port %s: no UPnP-enabled devices found: %v", port, err) 120 return 121 } 122 123 portInt, _ := strconv.Atoi(port) 124 err = d.Forward(uint16(portInt), "Sia RPC") 125 if err != nil { 126 g.log.Printf("WARN: could not automatically forward port %s: %v", port, err) 127 return 128 } 129 130 g.log.Println("INFO: successfully forwarded port", port) 131 132 // Establish port-clearing at shutdown. 133 g.threads.AfterStop(func() { 134 g.managedClearPort(port) 135 }) 136 } 137 138 // managedClearPort removes a port mapping from the router. 139 func (g *Gateway) managedClearPort(port string) { 140 if build.Release == "testing" { 141 return 142 } 143 144 d, err := upnp.Discover() 145 if err != nil { 146 return 147 } 148 149 portInt, _ := strconv.Atoi(port) 150 err = d.Clear(uint16(portInt)) 151 if err != nil { 152 g.log.Printf("WARN: could not automatically unforward port %s: %v", port, err) 153 return 154 } 155 156 g.log.Println("INFO: successfully unforwarded port", port) 157 }