gitlab.com/jokerrs1/Sia@v1.3.2/modules/host/upnp.go (about) 1 package host 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/Sia/build" 14 "github.com/NebulousLabs/Sia/modules" 15 16 "github.com/NebulousLabs/go-upnp" 17 ) 18 19 // managedLearnHostname discovers the external IP of the Host. If the host's 20 // net address is blank and the host's auto address appears to have changed, 21 // the host will make an announcement on the blockchain. 22 func (h *Host) managedLearnHostname() { 23 if build.Release == "testing" { 24 return 25 } 26 27 // Fetch a group of host vars that will be used to dictate the logic of the 28 // function. 29 h.mu.RLock() 30 netAddr := h.settings.NetAddress 31 hostPort := h.port 32 hostAutoAddress := h.autoAddress 33 hostAnnounced := h.announced 34 hostAcceptingContracts := h.settings.AcceptingContracts 35 hostContractCount := h.financialMetrics.ContractCount 36 h.mu.RUnlock() 37 38 // If the settings indicate that an address has been manually set, there is 39 // no reason to learn the hostname. 40 if netAddr != "" { 41 return 42 } 43 h.log.Println("No manually set net address. Scanning to automatically determine address.") 44 45 // try UPnP first, then fallback to myexternalip.com 46 var hostname string 47 d, err := upnp.Discover() 48 if err == nil { 49 hostname, err = d.ExternalIP() 50 } 51 if err != nil { 52 hostname, err = myExternalIP() 53 } 54 if err != nil { 55 h.log.Println("WARN: failed to discover external IP") 56 return 57 } 58 59 autoAddress := modules.NetAddress(net.JoinHostPort(hostname, hostPort)) 60 if err := autoAddress.IsValid(); err != nil { 61 h.log.Printf("WARN: discovered hostname %q is invalid: %v", autoAddress, err) 62 return 63 } 64 if autoAddress == hostAutoAddress && hostAnnounced { 65 // Nothing to do - the auto address has not changed and the previous 66 // annoucement was successful. 67 return 68 } 69 70 h.mu.Lock() 71 h.autoAddress = autoAddress 72 err = h.saveSync() 73 h.mu.Unlock() 74 if err != nil { 75 h.log.Println(err) 76 } 77 78 // Announce the host, but only if the host is either accepting contracts or 79 // has a storage obligation. If the host is not accepting contracts and has 80 // no open contracts, there is no reason to notify anyone that the host's 81 // address has changed. 82 if hostAcceptingContracts || hostContractCount > 0 { 83 h.log.Println("Host external IP address changed from", hostAutoAddress, "to", autoAddress, "- performing host announcement.") 84 err = h.managedAnnounce(autoAddress) 85 if err != nil { 86 // Set h.announced to false, as the address has changed yet the 87 // renewed annoucement has failed. 88 h.mu.Lock() 89 h.announced = false 90 h.mu.Unlock() 91 h.log.Println("unable to announce address after upnp-detected address change:", err) 92 } 93 } 94 } 95 96 // managedForwardPort adds a port mapping to the router. 97 func (h *Host) managedForwardPort(port string) error { 98 if build.Release == "testing" { 99 // Add a blocking placeholder where testing is able to mock behaviors 100 // such as a port forward action that blocks for 10 seconds before 101 // completing. 102 if h.dependencies.Disrupt("managedForwardPort") { 103 return nil 104 } 105 106 // Port forwarding functions are frequently unavailable during testing, 107 // and the long blocking can be highly disruptive. Under normal 108 // scenarios, return without complaint, and without running the 109 // port-forward logic. 110 return nil 111 } 112 113 // If the port is invalid, there is no need to perform any of the other 114 // tasks. 115 portInt, err := strconv.Atoi(port) 116 if err != nil { 117 return err 118 } 119 120 d, err := upnp.Discover() 121 if err != nil { 122 h.log.Printf("WARN: could not automatically forward port %s: %v", port, err) 123 return err 124 } 125 err = d.Forward(uint16(portInt), "Sia Host") 126 if err != nil { 127 h.log.Printf("WARN: could not automatically forward port %s: %v", port, err) 128 return err 129 } 130 131 h.log.Println("INFO: successfully forwarded port", port) 132 return nil 133 } 134 135 // managedClearPort removes a port mapping from the router. 136 func (h *Host) managedClearPort() error { 137 if build.Release == "testing" { 138 // Allow testing to force an error to be returned here. 139 if h.dependencies.Disrupt("managedClearPort return error") { 140 return errors.New("Mocked managedClearPortErr") 141 } 142 return nil 143 } 144 145 // If the port is invalid, there is no need to perform any of the other 146 // tasks. 147 h.mu.RLock() 148 port := h.port 149 h.mu.RUnlock() 150 portInt, err := strconv.Atoi(port) 151 if err != nil { 152 return err 153 } 154 155 d, err := upnp.Discover() 156 if err != nil { 157 return err 158 } 159 err = d.Clear(uint16(portInt)) 160 if err != nil { 161 return err 162 } 163 164 h.log.Println("INFO: successfully unforwarded port", port) 165 return nil 166 } 167 168 // myExternalIP discovers the host's external IP by querying a centralized 169 // service, http://myexternalip.com. 170 func myExternalIP() (string, error) { 171 // timeout after 10 seconds 172 client := http.Client{Timeout: time.Duration(10 * time.Second)} 173 resp, err := client.Get("http://myexternalip.com/raw") 174 if err != nil { 175 return "", err 176 } 177 defer resp.Body.Close() 178 if resp.StatusCode != http.StatusOK { 179 errResp, _ := ioutil.ReadAll(resp.Body) 180 return "", errors.New(string(errResp)) 181 } 182 buf, err := ioutil.ReadAll(io.LimitReader(resp.Body, 64)) 183 if err != nil { 184 return "", err 185 } 186 if len(buf) == 0 { 187 return "", errors.New("myexternalip.com returned a 0 length IP address") 188 } 189 // trim newline 190 return strings.TrimSpace(string(buf)), nil 191 }