gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/gateway/gateway.go (about) 1 // Package gateway connects a Sia node to the Sia flood network. The flood 2 // network is used to propagate blocks and transactions. The gateway is the 3 // primary avenue that a node uses to hear about transactions and blocks, and 4 // is the primary avenue used to tell the network about blocks that you have 5 // mined or about transactions that you have created. 6 package gateway 7 8 // For the user to be securely connected to the network, the user must be 9 // connected to at least one node which will send them all of the blocks. An 10 // attacker can trick the user into thinking that a different blockchain is the 11 // full blockchain if the user is not connected to any nodes who are seeing + 12 // broadcasting the real chain (and instead is connected only to attacker nodes 13 // or to nodes that are not broadcasting). This situation is called an eclipse 14 // attack. 15 // 16 // Connecting to a large number of nodes increases the resiliancy of the 17 // network, but also puts a networking burden on the nodes and can slow down 18 // block propagation or increase orphan rates. The gateway's job is to keep the 19 // network efficient while also protecting the user against attacks. 20 // 21 // The gateway keeps a list of nodes that it knows about. It uses this list to 22 // form connections with other nodes, and then uses those connections to 23 // participate in the flood network. The primary vector for an attacker to 24 // achieve an eclipse attack is node list domination. If a gateway's nodelist 25 // is heavily dominated by attacking nodes, then when the gateway chooses to 26 // make random connections the gateway is at risk of selecting only attacker 27 // nodes. 28 // 29 // The gateway defends itself from these attacks by minimizing the amount of 30 // control that an attacker has over the node list and peer list. The first 31 // major defense is that the gateway maintains 8 'outbound' relationships, 32 // which means that the gateway created those relationships instead of an 33 // attacker. If a node forms a connection to you, that node is called 34 // 'inbound', and because it may be an attacker node, it is not trusted. 35 // Outbound nodes can also be attacker nodes, but they are less likely to be 36 // attacker nodes because you chose them, instead of them choosing you. 37 // 38 // If the gateway forms too many connections, the gateway will allow incoming 39 // connections by kicking an existing peer. But, to limit the amount of control 40 // that an attacker may have, only inbound peers are selected to be kicked. 41 // Furthermore, to increase the difficulty of attack, if a new inbound 42 // connection shares the same IP address as an existing connection, the shared 43 // connection is the connection that gets dropped (unless that connection is a 44 // local or outbound connection). 45 // 46 // Nodes are added to a peerlist in two methods. The first method is that a 47 // gateway will ask its outbound peers for a list of nodes. If the node list is 48 // below a certain size (see consts.go), the gateway will repeatedly ask 49 // outbound peers to expand the list. Nodes are also added to the nodelist 50 // after they successfully form a connection with the gateway. To limit the 51 // attacker's ability to add nodes to the nodelist, connections are 52 // ratelimited. An attacker with lots of IP addresses still has the ability to 53 // fill up the nodelist, however getting 90% dominance of the nodelist requires 54 // forming thousands of connections, which will take hours or days. By that 55 // time, the attacked node should already have its set of outbound peers, 56 // limiting the amount of damage that the attacker can do. 57 // 58 // To limit DNS-based tomfoolry, nodes are only added to the nodelist if their 59 // connection information takes the form of an IP address. 60 // 61 // Some research has been done on Bitcoin's flood networks. The more relevant 62 // research has been listed below. The papers listed first are more relevant. 63 // Eclipse Attacks on Bitcoin's Peer-to-Peer Network (Heilman, Kendler, Zohar, Goldberg) 64 // Stubborn Mining: Generalizing Selfish Mining and Combining with an Eclipse Attack (Nayak, Kumar, Miller, Shi) 65 // An Overview of BGP Hijacking (https://www.bishopfox.com/blog/2015/08/an-overview-of-bgp-hijacking/) 66 67 // TODO: Currently the gateway does not do much in terms of bucketing. The 68 // gateway should make sure that it has outbound peers from a wide range of IP 69 // addresses, and when kicking inbound peers it shouldn't just favor kicking 70 // peers of the same IP address, it should favor kicking peers of the same ip 71 // address range. 72 // 73 // TODO: There is no public key exchange, so communications cannot be 74 // effectively encrypted or authenticated. 75 // 76 // TODO: Gateway hostname discovery currently has significant centralization, 77 // namely the fallback is a single third-party website that can easily form any 78 // response it wants. Instead, multiple TLS-protected third party websites 79 // should be used, and the plurality answer should be accepted as the true 80 // hostname. 81 // 82 // TODO: The gateway currently does hostname discovery in a non-blocking way, 83 // which means that the first few peers that it connects to may not get the 84 // correct hostname. This means that you may give the remote peer the wrong 85 // hostname, which means they will not be able to dial you back, which means 86 // they will not add you to their node list. 87 // 88 // TODO: The gateway should encrypt and authenticate all communications. Though 89 // the gateway participates in a flood network, practical attacks have been 90 // demonstrated which have been able to confuse nodes by manipulating messages 91 // from their peers. Encryption + authentication would have made the attack 92 // more difficult. 93 94 import ( 95 "fmt" 96 "net" 97 "os" 98 "path/filepath" 99 "sync" 100 "time" 101 102 "gitlab.com/NebulousLabs/errors" 103 "gitlab.com/NebulousLabs/fastrand" 104 "gitlab.com/NebulousLabs/ratelimit" 105 106 "gitlab.com/SiaPrime/SiaPrime/modules" 107 "gitlab.com/SiaPrime/SiaPrime/persist" 108 109 siasync "gitlab.com/SiaPrime/SiaPrime/sync" 110 ) 111 112 var ( 113 errNoPeers = errors.New("no peers") 114 errUnreachable = errors.New("peer did not respond to ping") 115 ) 116 117 // Gateway implements the modules.Gateway interface. 118 type Gateway struct { 119 listener net.Listener 120 myAddr modules.NetAddress 121 port string 122 rl *ratelimit.RateLimit 123 124 // handlers are the RPCs that the Gateway can handle. 125 // 126 // initRPCs are the RPCs that the Gateway calls upon connecting to a peer. 127 handlers map[rpcID]modules.RPCFunc 128 initRPCs map[string]modules.RPCFunc 129 130 // blacklist are peers that the gateway shouldn't connect to 131 // 132 // nodes is the set of all known nodes (i.e. potential peers). 133 // 134 // peers are the nodes that the gateway is currently connected to. 135 // 136 // peerTG is a special thread group for tracking peer connections, and will 137 // block shutdown until all peer connections have been closed out. The peer 138 // connections are put in a separate TG because of their unique 139 // requirements - they have the potential to live for the lifetime of the 140 // program, but also the potential to close early. Calling threads.OnStop 141 // for each peer could create a huge backlog of functions that do nothing 142 // (because most of the peers disconnected prior to shutdown). And they 143 // can't call threads.Add because they are potentially very long running 144 // and would block any threads.Flush() calls. So a second threadgroup is 145 // added which handles clean-shutdown for the peers, without blocking 146 // threads.Flush() calls. 147 blacklist map[string]struct{} 148 nodes map[modules.NetAddress]*node 149 peers map[modules.NetAddress]*peer 150 peerTG siasync.ThreadGroup 151 152 // Utilities. 153 log *persist.Logger 154 mu sync.RWMutex 155 persist persistence 156 persistDir string 157 threads siasync.ThreadGroup 158 159 // Unique ID 160 staticID gatewayID 161 } 162 163 type gatewayID [8]byte 164 165 // managedSleep will sleep for the given period of time. If the full time 166 // elapses, 'true' is returned. If the sleep is interrupted for shutdown, 167 // 'false' is returned. 168 func (g *Gateway) managedSleep(t time.Duration) (completed bool) { 169 select { 170 case <-time.After(t): 171 return true 172 case <-g.threads.StopChan(): 173 return false 174 } 175 } 176 177 // setRateLimits sets the specified ratelimit after performing input 178 // validation without persisting them. 179 func setRateLimits(rl *ratelimit.RateLimit, downloadSpeed, uploadSpeed int64) error { 180 // Input validation. 181 if downloadSpeed < 0 || uploadSpeed < 0 { 182 return errors.New("download/upload rate can't be below 0") 183 } 184 // Check for sentinel "no limits" value. 185 if downloadSpeed == 0 && uploadSpeed == 0 { 186 rl.SetLimits(0, 0, 0) 187 } else { 188 rl.SetLimits(downloadSpeed, uploadSpeed, 4*4096) 189 } 190 return nil 191 } 192 193 // Address returns the NetAddress of the Gateway. 194 func (g *Gateway) Address() modules.NetAddress { 195 g.mu.RLock() 196 defer g.mu.RUnlock() 197 return g.myAddr 198 } 199 200 // Close saves the state of the Gateway and stops its listener process. 201 func (g *Gateway) Close() error { 202 if err := g.threads.Stop(); err != nil { 203 return err 204 } 205 g.mu.Lock() 206 defer g.mu.Unlock() 207 return errors.Compose(g.saveSync(), g.saveSyncNodes()) 208 } 209 210 // DiscoverAddress discovers and returns the current public IP address of the 211 // gateway. Contrary to Address, DiscoverAddress is blocking and might take 212 // multiple minutes to return. A channel to cancel the discovery can be 213 // supplied optionally. If nil is supplied, a reasonable timeout will be used 214 // by default. 215 func (g *Gateway) DiscoverAddress(cancel <-chan struct{}) (net.IP, error) { 216 return g.managedLearnHostname(cancel) 217 } 218 219 // ForwardPort adds a port mapping to the router. 220 func (g *Gateway) ForwardPort(port string) error { 221 if err := g.threads.Add(); err != nil { 222 return err 223 } 224 defer g.threads.Done() 225 return g.managedForwardPort(port) 226 } 227 228 // RateLimits returns the currently set bandwidth limits of the gateway. 229 func (g *Gateway) RateLimits() (int64, int64) { 230 g.mu.RLock() 231 defer g.mu.RUnlock() 232 return g.persist.MaxDownloadSpeed, g.persist.MaxUploadSpeed 233 } 234 235 // SetRateLimits changes the rate limits for the peer-connections of the 236 // gateway. 237 func (g *Gateway) SetRateLimits(downloadSpeed, uploadSpeed int64) error { 238 g.mu.Lock() 239 defer g.mu.Unlock() 240 // Set the limit in memory. 241 if err := setRateLimits(g.rl, downloadSpeed, uploadSpeed); err != nil { 242 return err 243 } 244 // Update the persistence struct. 245 g.persist.MaxDownloadSpeed = downloadSpeed 246 g.persist.MaxUploadSpeed = uploadSpeed 247 return g.saveSync() 248 } 249 250 // New returns an initialized Gateway. 251 func New(addr string, bootstrap bool, persistDir string) (*Gateway, error) { 252 // Create the directory if it doesn't exist. 253 err := os.MkdirAll(persistDir, 0700) 254 if err != nil { 255 return nil, err 256 } 257 258 g := &Gateway{ 259 handlers: make(map[rpcID]modules.RPCFunc), 260 initRPCs: make(map[string]modules.RPCFunc), 261 262 blacklist: make(map[string]struct{}), 263 nodes: make(map[modules.NetAddress]*node), 264 peers: make(map[modules.NetAddress]*peer), 265 266 persistDir: persistDir, 267 } 268 269 // Set Unique GatewayID 270 fastrand.Read(g.staticID[:]) 271 272 // Create the logger. 273 g.log, err = persist.NewFileLogger(filepath.Join(g.persistDir, logFile)) 274 if err != nil { 275 return nil, err 276 } 277 // Establish the closing of the logger. 278 g.threads.AfterStop(func() { 279 if err := g.log.Close(); err != nil { 280 // The logger may or may not be working here, so use a println 281 // instead. 282 fmt.Println("Failed to close the gateway logger:", err) 283 } 284 }) 285 g.log.Println("INFO: gateway created, started logging") 286 287 // Establish that the peerTG must complete shutdown before the primary 288 // thread group completes shutdown. 289 g.threads.OnStop(func() { 290 err = g.peerTG.Stop() 291 if err != nil { 292 g.log.Println("ERROR: peerTG experienced errors while shutting down:", err) 293 } 294 }) 295 296 // Register RPCs. 297 g.RegisterRPC("ShareNodes", g.shareNodes) 298 g.RegisterRPC("DiscoverIP", g.discoverPeerIP) 299 g.RegisterConnectCall("ShareNodes", g.requestNodes) 300 // Establish the de-registration of the RPCs. 301 g.threads.OnStop(func() { 302 g.UnregisterRPC("ShareNodes") 303 g.UnregisterRPC("DiscoverIP") 304 g.UnregisterConnectCall("ShareNodes") 305 }) 306 307 // Load the old node list and gateway persistence. If it doesn't exist, no 308 // problem, but if it does, we want to know about any errors preventing us 309 // from loading it. 310 if loadErr := g.load(); loadErr != nil && !os.IsNotExist(loadErr) { 311 return nil, loadErr 312 } 313 // Create the ratelimiter and set it to the persisted limits. 314 g.rl = ratelimit.NewRateLimit(0, 0, 0) 315 if err := setRateLimits(g.rl, g.persist.MaxDownloadSpeed, g.persist.MaxUploadSpeed); err != nil { 316 return nil, err 317 } 318 // Spawn the thread to periodically save the gateway. 319 go g.threadedSaveLoop() 320 // Make sure that the gateway saves after shutdown. 321 g.threads.AfterStop(func() { 322 g.mu.Lock() 323 if err := g.saveSync(); err != nil { 324 g.log.Println("ERROR: Unable to save gateway:", err) 325 } 326 if err := g.saveSyncNodes(); err != nil { 327 g.log.Println("ERROR: Unable to save gateway nodes:", err) 328 } 329 g.mu.Unlock() 330 }) 331 332 // Add the bootstrap peers to the node list. 333 if bootstrap { 334 for _, addr := range modules.BootstrapPeers { 335 err := g.addNode(addr) 336 if err != nil && err != errNodeExists { 337 g.log.Printf("WARN: failed to add the bootstrap node '%v': %v", addr, err) 338 } 339 } 340 } 341 342 // Create the listener which will listen for new connections from peers. 343 permanentListenClosedChan := make(chan struct{}) 344 g.listener, err = net.Listen("tcp", addr) 345 if err != nil { 346 return nil, err 347 } 348 // Automatically close the listener when g.threads.Stop() is called. 349 g.threads.OnStop(func() { 350 err := g.listener.Close() 351 if err != nil { 352 g.log.Println("WARN: closing the listener failed:", err) 353 } 354 <-permanentListenClosedChan 355 }) 356 // Set the address and port of the gateway. 357 host, port, err := net.SplitHostPort(g.listener.Addr().String()) 358 g.port = port 359 if err != nil { 360 return nil, err 361 } 362 363 if ip := net.ParseIP(host); ip.IsUnspecified() && ip != nil { 364 // if host is unspecified, set a dummy one for now. 365 host = "localhost" 366 } 367 368 // Set myAddr equal to the address returned by the listener. It will be 369 // overwritten by threadedLearnHostname later on. 370 g.myAddr = modules.NetAddress(net.JoinHostPort(host, port)) 371 372 // Spawn the peer connection listener. 373 go g.permanentListen(permanentListenClosedChan) 374 375 // Spawn the peer manager and provide tools for ensuring clean shutdown. 376 peerManagerClosedChan := make(chan struct{}) 377 g.threads.OnStop(func() { 378 <-peerManagerClosedChan 379 }) 380 go g.permanentPeerManager(peerManagerClosedChan) 381 382 // Spawn the node manager and provide tools for ensuring clean shutdown. 383 nodeManagerClosedChan := make(chan struct{}) 384 g.threads.OnStop(func() { 385 <-nodeManagerClosedChan 386 }) 387 go g.permanentNodeManager(nodeManagerClosedChan) 388 389 // Spawn the node purger and provide tools for ensuring clean shutdown. 390 nodePurgerClosedChan := make(chan struct{}) 391 g.threads.OnStop(func() { 392 <-nodePurgerClosedChan 393 }) 394 go g.permanentNodePurger(nodePurgerClosedChan) 395 396 // Spawn threads to take care of port forwarding and hostname discovery. 397 go g.threadedForwardPort(g.port) 398 go g.threadedLearnHostname() 399 400 return g, nil 401 } 402 403 // enforce that Gateway satisfies the modules.Gateway interface 404 var _ modules.Gateway = (*Gateway)(nil)