github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/host/network.go (about) 1 package host 2 3 // TODO: seems like there would be problems with the negotiation protocols if 4 // the renter tried something like 'form' or 'renew' but then the connections 5 // dropped after the host completed the transaction but before the host was 6 // able to send the host signatures for the transaction. 7 // 8 // Especially on a renew, the host choosing to hold the renter signatures 9 // hostage could be a pretty significant problem, and would require the renter 10 // to attempt a double-spend to either force the transaction onto the 11 // blockchain or to make sure that the host cannot abscond with the funds 12 // without commitment. 13 // 14 // Incentive for the host to do such a thing is pretty low - they will still 15 // have to keep all the files following a renew in order to get the money. 16 17 import ( 18 "net" 19 "sync/atomic" 20 "time" 21 22 "SiaPrime/build" 23 "SiaPrime/encoding" 24 "SiaPrime/modules" 25 "SiaPrime/types" 26 ) 27 28 // rpcSettingsDeprecated is a specifier for a deprecated settings request. 29 var rpcSettingsDeprecated = types.Specifier{'S', 'e', 't', 't', 'i', 'n', 'g', 's'} 30 31 // threadedUpdateHostname periodically runs 'managedLearnHostname', which 32 // checks if the host's hostname has changed, and makes an updated host 33 // announcement if so. 34 func (h *Host) threadedUpdateHostname(closeChan chan struct{}) { 35 defer close(closeChan) 36 for { 37 h.managedLearnHostname() 38 // Wait 30 minutes to check again. If the hostname is changing 39 // regularly (more than once a week), we want the host to be able to be 40 // seen as having 95% uptime. Every minute that the announcement is 41 // pointing to the wrong address is a minute of perceived downtime to 42 // the renters. 43 select { 44 case <-h.tg.StopChan(): 45 return 46 case <-time.After(time.Minute * 30): 47 continue 48 } 49 } 50 } 51 52 // threadedTrackWorkingStatus periodically checks if the host is working, 53 // where working is defined as having received 3 settings calls in the past 15 54 // minutes. 55 func (h *Host) threadedTrackWorkingStatus(closeChan chan struct{}) { 56 defer close(closeChan) 57 58 // Before entering the longer loop, try a greedy, faster attempt to verify 59 // that the host is working. 60 prevSettingsCalls := atomic.LoadUint64(&h.atomicSettingsCalls) 61 select { 62 case <-h.tg.StopChan(): 63 return 64 case <-time.After(workingStatusFirstCheck): 65 } 66 settingsCalls := atomic.LoadUint64(&h.atomicSettingsCalls) 67 68 // sanity check 69 if prevSettingsCalls > settingsCalls { 70 build.Severe("the host's settings calls decremented") 71 } 72 73 h.mu.Lock() 74 if settingsCalls-prevSettingsCalls >= workingStatusThreshold { 75 h.workingStatus = modules.HostWorkingStatusWorking 76 } 77 // First check is quick, don't set to 'not working' if host has not been 78 // contacted enough times. 79 h.mu.Unlock() 80 81 for { 82 prevSettingsCalls = atomic.LoadUint64(&h.atomicSettingsCalls) 83 select { 84 case <-h.tg.StopChan(): 85 return 86 case <-time.After(workingStatusFrequency): 87 } 88 settingsCalls = atomic.LoadUint64(&h.atomicSettingsCalls) 89 90 // sanity check 91 if prevSettingsCalls > settingsCalls { 92 build.Severe("the host's settings calls decremented") 93 continue 94 } 95 96 h.mu.Lock() 97 if settingsCalls-prevSettingsCalls >= workingStatusThreshold { 98 h.workingStatus = modules.HostWorkingStatusWorking 99 } else { 100 h.workingStatus = modules.HostWorkingStatusNotWorking 101 } 102 h.mu.Unlock() 103 } 104 } 105 106 // threadedTrackConnectabilityStatus periodically checks if the host is 107 // connectable at its netaddress. 108 func (h *Host) threadedTrackConnectabilityStatus(closeChan chan struct{}) { 109 defer close(closeChan) 110 111 // Wait briefly before checking the first time. This gives time for any port 112 // forwarding to complete. 113 select { 114 case <-h.tg.StopChan(): 115 return 116 case <-time.After(connectabilityCheckFirstWait): 117 } 118 119 for { 120 h.mu.RLock() 121 autoAddr := h.autoAddress 122 userAddr := h.settings.NetAddress 123 h.mu.RUnlock() 124 125 activeAddr := autoAddr 126 if userAddr != "" { 127 activeAddr = userAddr 128 } 129 130 dialer := &net.Dialer{ 131 Cancel: h.tg.StopChan(), 132 Timeout: connectabilityCheckTimeout, 133 } 134 conn, err := dialer.Dial("tcp", string(activeAddr)) 135 136 var status modules.HostConnectabilityStatus 137 if err != nil { 138 status = modules.HostConnectabilityStatusNotConnectable 139 } else { 140 conn.Close() 141 status = modules.HostConnectabilityStatusConnectable 142 } 143 h.mu.Lock() 144 h.connectabilityStatus = status 145 h.mu.Unlock() 146 147 select { 148 case <-h.tg.StopChan(): 149 return 150 case <-time.After(connectabilityCheckFrequency): 151 } 152 } 153 } 154 155 // initNetworking performs actions like port forwarding, and gets the 156 // host established on the network. 157 func (h *Host) initNetworking(address string) (err error) { 158 // Create the listener and setup the close procedures. 159 h.listener, err = h.dependencies.Listen("tcp", address) 160 if err != nil { 161 return err 162 } 163 // Automatically close the listener when h.tg.Stop() is called. 164 threadedListenerClosedChan := make(chan struct{}) 165 h.tg.OnStop(func() { 166 err := h.listener.Close() 167 if err != nil { 168 h.log.Println("WARN: closing the listener failed:", err) 169 } 170 171 // Wait until the threadedListener has returned to continue shutdown. 172 <-threadedListenerClosedChan 173 }) 174 175 // Set the initial working state of the host 176 h.workingStatus = modules.HostWorkingStatusChecking 177 178 // Set the initial connectability state of the host 179 h.connectabilityStatus = modules.HostConnectabilityStatusChecking 180 181 // Set the port. 182 _, port, err := net.SplitHostPort(h.listener.Addr().String()) 183 if err != nil { 184 return err 185 } 186 h.port = port 187 if build.Release == "testing" { 188 // Set the autoAddress to localhost for testing builds only. 189 h.autoAddress = modules.NetAddress(net.JoinHostPort("localhost", h.port)) 190 } 191 192 // Non-blocking, perform port forwarding and create the hostname discovery 193 // thread. 194 go func() { 195 // Add this function to the threadgroup, so that the logger will not 196 // disappear before port closing can be registered to the threadgrourp 197 // OnStop functions. 198 err := h.tg.Add() 199 if err != nil { 200 // If this goroutine is not run before shutdown starts, this 201 // codeblock is reachable. 202 return 203 } 204 defer h.tg.Done() 205 206 err = h.g.ForwardPort(port) 207 if err != nil { 208 h.log.Println("ERROR: failed to forward port:", err) 209 } 210 211 threadedUpdateHostnameClosedChan := make(chan struct{}) 212 go h.threadedUpdateHostname(threadedUpdateHostnameClosedChan) 213 h.tg.OnStop(func() { 214 <-threadedUpdateHostnameClosedChan 215 }) 216 217 threadedTrackWorkingStatusClosedChan := make(chan struct{}) 218 go h.threadedTrackWorkingStatus(threadedTrackWorkingStatusClosedChan) 219 h.tg.OnStop(func() { 220 <-threadedTrackWorkingStatusClosedChan 221 }) 222 223 threadedTrackConnectabilityStatusClosedChan := make(chan struct{}) 224 go h.threadedTrackConnectabilityStatus(threadedTrackConnectabilityStatusClosedChan) 225 h.tg.OnStop(func() { 226 <-threadedTrackConnectabilityStatusClosedChan 227 }) 228 }() 229 230 // Launch the listener. 231 go h.threadedListen(threadedListenerClosedChan) 232 return nil 233 } 234 235 // threadedHandleConn handles an incoming connection to the host, typically an 236 // RPC. 237 func (h *Host) threadedHandleConn(conn net.Conn) { 238 err := h.tg.Add() 239 if err != nil { 240 return 241 } 242 defer h.tg.Done() 243 244 // Close the conn on host.Close or when the method terminates, whichever comes 245 // first. 246 connCloseChan := make(chan struct{}) 247 defer close(connCloseChan) 248 go func() { 249 select { 250 case <-h.tg.StopChan(): 251 case <-connCloseChan: 252 } 253 conn.Close() 254 }() 255 256 // Set an initial duration that is generous, but finite. RPCs can extend 257 // this if desired. 258 err = conn.SetDeadline(time.Now().Add(5 * time.Minute)) 259 if err != nil { 260 h.log.Println("WARN: could not set deadline on connection:", err) 261 return 262 } 263 264 // Read a specifier indicating which action is being called. 265 var id types.Specifier 266 if err := encoding.ReadObject(conn, &id, 16); err != nil { 267 atomic.AddUint64(&h.atomicUnrecognizedCalls, 1) 268 h.log.Debugf("WARN: incoming conn %v was malformed: %v", conn.RemoteAddr(), err) 269 return 270 } 271 272 switch id { 273 case modules.RPCDownload: 274 atomic.AddUint64(&h.atomicDownloadCalls, 1) 275 err = extendErr("incoming RPCDownload failed: ", h.managedRPCDownload(conn)) 276 case modules.RPCRenewContract: 277 atomic.AddUint64(&h.atomicRenewCalls, 1) 278 err = extendErr("incoming RPCRenewContract failed: ", h.managedRPCRenewContract(conn)) 279 case modules.RPCFormContract: 280 atomic.AddUint64(&h.atomicFormContractCalls, 1) 281 err = extendErr("incoming RPCFormContract failed: ", h.managedRPCFormContract(conn)) 282 case modules.RPCReviseContract: 283 atomic.AddUint64(&h.atomicReviseCalls, 1) 284 err = extendErr("incoming RPCReviseContract failed: ", h.managedRPCReviseContract(conn)) 285 case modules.RPCSettings: 286 atomic.AddUint64(&h.atomicSettingsCalls, 1) 287 err = extendErr("incoming RPCSettings failed: ", h.managedRPCSettings(conn)) 288 case rpcSettingsDeprecated: 289 h.log.Debugln("Received deprecated settings call") 290 default: 291 h.log.Debugf("WARN: incoming conn %v requested unknown RPC \"%v\"", conn.RemoteAddr(), id) 292 atomic.AddUint64(&h.atomicUnrecognizedCalls, 1) 293 } 294 if err != nil { 295 atomic.AddUint64(&h.atomicErroredCalls, 1) 296 err = extendErr("error with "+conn.RemoteAddr().String()+": ", err) 297 h.managedLogError(err) 298 } 299 } 300 301 // listen listens for incoming RPCs and spawns an appropriate handler for each. 302 func (h *Host) threadedListen(closeChan chan struct{}) { 303 defer close(closeChan) 304 305 // Receive connections until an error is returned by the listener. When an 306 // error is returned, there will be no more calls to receive. 307 for { 308 // Block until there is a connection to handle. 309 conn, err := h.listener.Accept() 310 if err != nil { 311 return 312 } 313 314 go h.threadedHandleConn(conn) 315 316 // Soft-sleep to ratelimit the number of incoming connections. 317 select { 318 case <-h.tg.StopChan(): 319 case <-time.After(rpcRatelimit): 320 } 321 } 322 } 323 324 // NetAddress returns the address at which the host can be reached. 325 func (h *Host) NetAddress() modules.NetAddress { 326 h.mu.RLock() 327 defer h.mu.RUnlock() 328 329 if h.settings.NetAddress != "" { 330 return h.settings.NetAddress 331 } 332 return h.autoAddress 333 } 334 335 // NetworkMetrics returns information about the types of rpc calls that have 336 // been made to the host. 337 func (h *Host) NetworkMetrics() modules.HostNetworkMetrics { 338 h.mu.RLock() 339 defer h.mu.RUnlock() 340 return modules.HostNetworkMetrics{ 341 DownloadCalls: atomic.LoadUint64(&h.atomicDownloadCalls), 342 ErrorCalls: atomic.LoadUint64(&h.atomicErroredCalls), 343 FormContractCalls: atomic.LoadUint64(&h.atomicFormContractCalls), 344 RenewCalls: atomic.LoadUint64(&h.atomicRenewCalls), 345 ReviseCalls: atomic.LoadUint64(&h.atomicReviseCalls), 346 SettingsCalls: atomic.LoadUint64(&h.atomicSettingsCalls), 347 UnrecognizedCalls: atomic.LoadUint64(&h.atomicUnrecognizedCalls), 348 } 349 }