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