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