github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/host/network.go (about) 1 package host 2 3 import ( 4 "net" 5 "sync/atomic" 6 "time" 7 8 "github.com/NebulousLabs/Sia/build" 9 "github.com/NebulousLabs/Sia/encoding" 10 "github.com/NebulousLabs/Sia/modules" 11 "github.com/NebulousLabs/Sia/types" 12 ) 13 14 // rpcSettingsDeprecated is a specifier for a deprecated settings request. 15 var rpcSettingsDeprecated = types.Specifier{'S', 'e', 't', 't', 'i', 'n', 'g', 's'} 16 17 // threadedUpdateHostname periodically runs 'managedLearnHostname', which 18 // checks if the host's hostname has changed, and makes an updated host 19 // announcement if so. 20 // 21 // TODO: This thread doesn't actually have clean shutdown, because the sleep is 22 // outside of of the resource lock. 23 func (h *Host) threadedUpdateHostname() { 24 for { 25 h.resourceLock.RLock() 26 if h.closed { 27 // The host is closed, the goroutine can exit. 28 h.resourceLock.RUnlock() 29 break 30 } 31 h.managedLearnHostname() 32 h.resourceLock.RUnlock() 33 34 // Wait 30 minutes to check again. If the hostname is changing 35 // regularly (more than once a week), we want the host to be able to be 36 // seen as having 95% uptime. Every minute that the announcement is 37 // pointing to the wrong address is a minute of perceived downtime to 38 // the renters. 39 time.Sleep(time.Minute * 30) 40 } 41 } 42 43 // initNetworking performs actions like port forwarding, and gets the host 44 // established on the network. 45 func (h *Host) initNetworking(address string) (err error) { 46 // Create listener and set address. 47 h.listener, err = h.dependencies.listen("tcp", address) 48 if err != nil { 49 return err 50 } 51 _, port, err := net.SplitHostPort(h.listener.Addr().String()) 52 if err != nil { 53 return err 54 } 55 h.mu.Lock() 56 h.port = port 57 if build.Release == "testing" { 58 h.autoAddress = modules.NetAddress(net.JoinHostPort("localhost", h.port)) 59 } 60 h.mu.Unlock() 61 62 // Non-blocking, perform port forwarding and hostname discovery. 63 go func() { 64 h.resourceLock.RLock() 65 defer h.resourceLock.RUnlock() 66 if h.closed { 67 return 68 } 69 70 err := h.managedForwardPort() 71 if err != nil { 72 h.log.Println("ERROR: failed to forward port:", err) 73 } 74 75 // Spin up the hostname checker. The hostname checker should not be 76 // spun up until after the port has been forwarded, because it can 77 // result in announcements being submitted to the blockchain. 78 go h.threadedUpdateHostname() 79 }() 80 81 // Launch the listener. 82 go h.threadedListen() 83 return nil 84 } 85 86 // threadedHandleConn handles an incoming connection to the host, typically an 87 // RPC. 88 func (h *Host) threadedHandleConn(conn net.Conn) { 89 h.resourceLock.RLock() 90 defer h.resourceLock.RUnlock() 91 if h.closed { 92 return 93 } 94 95 // Set an initial duration that is generous, but finite. RPCs can extend 96 // this if desired. 97 err := conn.SetDeadline(time.Now().Add(5 * time.Minute)) 98 if err != nil { 99 h.log.Println("WARN: could not set deadline on connection:", err) 100 return 101 } 102 defer conn.Close() 103 104 // Read a specifier indicating which action is being called. 105 var id types.Specifier 106 if err := encoding.ReadObject(conn, &id, 16); err != nil { 107 atomic.AddUint64(&h.atomicUnrecognizedCalls, 1) 108 h.log.Debugf("WARN: incoming conn %v was malformed: %v", conn.RemoteAddr(), err) 109 return 110 } 111 112 switch id { 113 case modules.RPCDownload: 114 atomic.AddUint64(&h.atomicDownloadCalls, 1) 115 err = h.managedRPCDownload(conn) 116 case modules.RPCRenewContract: 117 atomic.AddUint64(&h.atomicRenewCalls, 1) 118 err = h.managedRPCRenewContract(conn) 119 case modules.RPCFormContract: 120 atomic.AddUint64(&h.atomicFormContractCalls, 1) 121 err = h.managedRPCFormContract(conn) 122 case modules.RPCReviseContract: 123 atomic.AddUint64(&h.atomicReviseCalls, 1) 124 err = h.managedRPCReviseContract(conn) 125 case modules.RPCRecentRevision: 126 atomic.AddUint64(&h.atomicRecentRevisionCalls, 1) 127 _, _, err = h.managedRPCRecentRevision(conn) 128 case modules.RPCSettings: 129 atomic.AddUint64(&h.atomicSettingsCalls, 1) 130 err = h.managedRPCSettings(conn) 131 default: 132 h.log.Debugf("WARN: incoming conn %v requested unknown RPC \"%v\"", conn.RemoteAddr(), id) 133 if id != rpcSettingsDeprecated { 134 // Only mark the call as unrecognized if it is not one of the 135 // legacy calls. 136 atomic.AddUint64(&h.atomicUnrecognizedCalls, 1) 137 } 138 } 139 if err != nil { 140 atomic.AddUint64(&h.atomicErroredCalls, 1) 141 142 // If there have been less than 1000 errored rpcs, print the error 143 // message. This is to help developers debug live systems that are 144 // running into issues. Ultimately though, this error can be triggered 145 // by a malicious actor, and therefore should not be logged except for 146 // DEBUG builds. 147 erroredCalls := atomic.LoadUint64(&h.atomicErroredCalls) 148 if erroredCalls < 1e3 { 149 h.log.Printf("WARN: incoming RPC \"%v\" failed: %v", id, err) 150 } else { 151 h.log.Debugf("WARN: incoming RPC \"%v\" failed: %v", id, err) 152 } 153 } 154 } 155 156 // listen listens for incoming RPCs and spawns an appropriate handler for each. 157 // 158 // TODO: Does not seem like this function ever actually lets go of the resource 159 // lock. 160 func (h *Host) threadedListen() { 161 h.resourceLock.RLock() 162 defer h.resourceLock.RUnlock() 163 if h.closed { 164 return 165 } 166 167 // Receive connections until an error is returned by the listener. When an 168 // error is returned, there will be no more calls to receive. 169 for { 170 // Block until there is a connection to handle. 171 conn, err := h.listener.Accept() 172 if err != nil { 173 return 174 } 175 176 // Grab the resource lock before creating a goroutine. 177 go h.threadedHandleConn(conn) 178 } 179 } 180 181 // NetAddress returns the address at which the host can be reached. 182 func (h *Host) NetAddress() modules.NetAddress { 183 h.mu.RLock() 184 defer h.mu.RUnlock() 185 186 if h.settings.NetAddress != "" { 187 return h.settings.NetAddress 188 } 189 return h.autoAddress 190 } 191 192 // NetworkMetrics returns information about the types of rpc calls that have 193 // been made to the host. 194 func (h *Host) NetworkMetrics() modules.HostNetworkMetrics { 195 h.mu.RLock() 196 defer h.mu.RUnlock() 197 return modules.HostNetworkMetrics{ 198 // TODO: Up/Down bandwidth 199 200 DownloadCalls: atomic.LoadUint64(&h.atomicDownloadCalls), 201 ErrorCalls: atomic.LoadUint64(&h.atomicErroredCalls), 202 FormContractCalls: atomic.LoadUint64(&h.atomicFormContractCalls), 203 RenewCalls: atomic.LoadUint64(&h.atomicRenewCalls), 204 ReviseCalls: atomic.LoadUint64(&h.atomicReviseCalls), 205 SettingsCalls: atomic.LoadUint64(&h.atomicSettingsCalls), 206 UnrecognizedCalls: atomic.LoadUint64(&h.atomicUnrecognizedCalls), 207 } 208 }