// TestAddress tests that Gateway.Address returns the address of its listener. // Also tests that the address is not unspecified and is a loopback address. // The address must be a loopback address for testing. func TestAddress(t *testing.T) { if testing.Short() { t.SkipNow() } g := newTestingGateway("TestAddress", t) defer g.Close() if g.Address() != g.myAddr { t.Fatal("Address does not return g.myAddr") } if g.Address() != modules.NetAddress(g.listener.Addr().String()) { t.Fatalf("wrong address: expected %v, got %v", g.listener.Addr(), g.Address()) } host := modules.NetAddress(g.listener.Addr().String()).Host() ip := net.ParseIP(host) if ip == nil { t.Fatal("address is not an IP address") } if ip.IsUnspecified() { t.Fatal("expected a non-unspecified address") } if !ip.IsLoopback() { t.Fatal("expected a loopback address") } }
func TestAddress(t *testing.T) { g := newTestingGateway("TestAddress", t) defer g.Close() if g.Address() != g.myAddr { t.Fatal("Address does not return g.myAddr") } port := modules.NetAddress(g.listener.Addr().String()).Port() expAddr := modules.NetAddress(net.JoinHostPort("::1", port)) if g.Address() != expAddr { t.Fatalf("Wrong address: expected %v, got %v", expAddr, g.Address()) } }
// learnHostname discovers the external IP of the Host. The Host cannot // announce until the external IP is known. func (h *Host) learnHostname() { if build.Release == "testing" { return } var host string // try UPnP first, then fallback to myexternalip.com d, err := upnp.Discover() if err == nil { host, err = d.ExternalIP() } if err != nil { host, err = myExternalIP() } if err != nil { h.log.Println("WARN: failed to discover external IP") return } h.mu.Lock() h.myAddr = modules.NetAddress(net.JoinHostPort(host, h.myAddr.Port())) h.HostSettings.IPAddress = h.myAddr h.save() h.mu.Unlock() }
// threadedAcceptConn adds a connecting node as a peer. func (g *Gateway) threadedAcceptConn(conn net.Conn) { if g.threads.Add() != nil { return } defer g.threads.Done() addr := modules.NetAddress(conn.RemoteAddr().String()) g.log.Debugf("INFO: %v wants to connect", addr) remoteVersion, err := acceptConnVersionHandshake(conn, build.Version) if err != nil { g.log.Debugf("INFO: %v wanted to connect but version handshake failed: %v", addr, err) conn.Close() return } if build.VersionCmp(remoteVersion, "1.0.0") < 0 { err = g.managedAcceptConnOldPeer(conn, remoteVersion) } else { err = g.managedAcceptConnNewPeer(conn, remoteVersion) } if err != nil { g.log.Debugf("INFO: %v wanted to connect, but failed: %v", addr, err) conn.Close() return } g.log.Debugf("INFO: accepted connection from new peer %v (v%v)", addr, remoteVersion) }
// managedLearnHostname discovers the external IP of the Host. If the host's // net address is blank and the host's auto address appears to have changed, // the host will make an announcement on the blockchain. func (h *Host) managedLearnHostname() { if build.Release == "testing" { return } h.mu.RLock() netAddr := h.settings.NetAddress h.mu.RUnlock() // If the settings indicate that an address has been manually set, there is // no reason to learn the hostname. if netAddr != "" { return } // try UPnP first, then fallback to myexternalip.com var hostname string d, err := upnp.Discover() if err == nil { hostname, err = d.ExternalIP() } if err != nil { hostname, err = myExternalIP() } if err != nil { h.log.Println("WARN: failed to discover external IP") return } h.mu.Lock() defer h.mu.Unlock() autoAddress := modules.NetAddress(net.JoinHostPort(hostname, h.port)) if err := autoAddress.IsValid(); err != nil { h.log.Printf("WARN: discovered hostname %q is invalid: %v", autoAddress, err) return } if autoAddress == h.autoAddress && h.announced { // Nothing to do - the auto address has not changed and the previous // annoucement was successful. return } h.autoAddress = autoAddress err = h.save() if err != nil { h.log.Println(err) } // Announce the host, but only if the host is either accepting contracts or // has a storage obligation. If the host is not accepting contracts and has // no open contracts, there is no reason to notify anyone that the host's // address has changed. if h.settings.AcceptingContracts || h.financialMetrics.ContractCount > 0 { err = h.announce(autoAddress) if err != nil { // Set h.announced to false, as the address has changed yet the // renewed annoucement has failed. h.announced = false h.log.Debugln("unable to announce address after upnp-detected address change:", err) } } }
// learnHostname discovers the external IP of the Gateway. Once the IP has // been discovered, it registers the ShareNodes RPC to be called on new // connections, advertising the IP to other nodes. func (g *Gateway) learnHostname() { if build.Release == "testing" { return } var host string // try UPnP first, then fallback to myexternalip.com d, err := upnp.Discover() if err == nil { host, err = d.ExternalIP() } if err != nil { host, err = myExternalIP() } if err != nil { g.log.Println("WARN: failed to discover external IP") return } id := g.mu.Lock() g.myAddr = modules.NetAddress(net.JoinHostPort(host, g.myAddr.Port())) g.mu.Unlock(id) g.log.Println("INFO: our address is", g.myAddr) // now that we know our address, we can start advertising it g.RegisterConnectCall("RelayNode", g.sendAddress) }
// threadedAcceptConn adds a connecting node as a peer. func (g *Gateway) threadedAcceptConn(conn net.Conn) { if g.threads.Add() != nil { conn.Close() return } defer g.threads.Done() conn.SetDeadline(time.Now().Add(connStdDeadline)) addr := modules.NetAddress(conn.RemoteAddr().String()) g.log.Debugf("INFO: %v wants to connect", addr) remoteVersion, err := acceptConnVersionHandshake(conn, build.Version) if err != nil { g.log.Debugf("INFO: %v wanted to connect but version handshake failed: %v", addr, err) conn.Close() return } if build.VersionCmp(remoteVersion, handshakeUpgradeVersion) < 0 { err = g.managedAcceptConnOldPeer(conn, remoteVersion) } else { err = g.managedAcceptConnNewPeer(conn, remoteVersion) } if err != nil { g.log.Debugf("INFO: %v wanted to connect, but failed: %v", addr, err) conn.Close() return } // Handshake successful, remove the deadline. conn.SetDeadline(time.Time{}) g.log.Debugf("INFO: accepted connection from new peer %v (v%v)", addr, remoteVersion) }
// initNetworking performs actions like port forwarding, and gets the host // established on the network. func (h *Host) initNetworking(address string) error { // Create listener and set address. var err error h.listener, err = net.Listen("tcp", address) if err != nil { return err } h.netAddress = modules.NetAddress(h.listener.Addr().String()) // Non-blocking, perform port forwarding and hostname discovery. go func() { h.resourceLock.RLock() defer h.resourceLock.RUnlock() if h.closed { return } h.mu.RLock() port := h.netAddress.Port() h.mu.RUnlock() err := h.forwardPort(port) if err != nil { h.log.Println("ERROR: failed to forward port:", err) } h.learnHostname() }() // Launch the listener. go h.threadedListen() return nil }
// New returns an initialized Gateway. func New(addr string, persistDir string) (g *Gateway, err error) { // Create the directory if it doesn't exist. err = os.MkdirAll(persistDir, 0700) if err != nil { return } // Create the logger. logger, err := makeLogger(persistDir) if err != nil { return } g = &Gateway{ handlers: make(map[rpcID]modules.RPCFunc), initRPCs: make(map[string]modules.RPCFunc), peers: make(map[modules.NetAddress]*peer), nodes: make(map[modules.NetAddress]struct{}), persistDir: persistDir, mu: sync.New(modules.SafeMutexDelay, 2), log: logger, } // Load the old peer list. If it doesn't exist, no problem, but if it does, // we want to know about any errors preventing us from loading it. if loadErr := g.load(); loadErr != nil && !os.IsNotExist(loadErr) { return nil, loadErr } // Create listener and set address. g.listener, err = net.Listen("tcp", addr) if err != nil { return } _, port, _ := net.SplitHostPort(g.listener.Addr().String()) g.myAddr = modules.NetAddress(net.JoinHostPort("::1", port)) // Register RPCs. g.RegisterRPC("ShareNodes", g.shareNodes) g.RegisterRPC("RelayNode", g.relayNode) g.RegisterConnectCall("ShareNodes", g.requestNodes) g.log.Println("INFO: gateway created, started logging") // Learn our external IP. go g.learnHostname() // Automatically forward the RPC port, if possible. go g.forwardPort(port) // Spawn the peer and node managers. These will attempt to keep the peer // and node lists healthy. go g.threadedPeerManager() go g.threadedNodeManager() // Spawn the primary listener. go g.listen() return }
// acceptConn adds a connecting node as a peer. func (g *Gateway) acceptConn(conn net.Conn) { addr := modules.NetAddress(conn.RemoteAddr().String()) g.log.Printf("INFO: %v wants to connect", addr) // read version var remoteVersion string if err := encoding.ReadObject(conn, &remoteVersion, maxAddrLength); err != nil { conn.Close() g.log.Printf("INFO: %v wanted to connect, but we could not read their version: %v", addr, err) return } // check that version is acceptable // NOTE: this version must be bumped whenever the gateway or consensus // breaks compatibility. if build.VersionCmp(remoteVersion, "0.3.3") < 0 { encoding.WriteObject(conn, "reject") conn.Close() g.log.Printf("INFO: %v wanted to connect, but their version (%v) was unacceptable", addr, remoteVersion) return } // respond with our version if err := encoding.WriteObject(conn, build.Version); err != nil { conn.Close() g.log.Printf("INFO: could not write version ack to %v: %v", addr, err) return } // If we are already fully connected, kick out an old peer to make room // for the new one. Importantly, prioritize kicking a peer with the same // IP as the connecting peer. This protects against Sybil attacks. id := g.mu.Lock() if len(g.peers) >= fullyConnectedThreshold { // first choose a random peer, preferably inbound. If have only // outbound peers, we'll wind up kicking an outbound peer; but // subsequent inbound connections will kick each other instead of // continuing to replace outbound peers. kick, err := g.randomInboundPeer() if err != nil { kick, _ = g.randomPeer() } // if another peer shares this IP, choose that one instead for p := range g.peers { if p.Host() == addr.Host() { kick = p break } } g.peers[kick].sess.Close() delete(g.peers, kick) g.log.Printf("INFO: disconnected from %v to make room for %v", kick, addr) } // add the peer g.addPeer(&peer{addr: addr, sess: muxado.Server(conn), inbound: true}) g.mu.Unlock(id) g.log.Printf("INFO: accepted connection from new peer %v (v%v)", addr, remoteVersion) }
func TestShareNodes(t *testing.T) { g1 := newTestingGateway("TestShareNodes1", t) defer g1.Close() g2 := newTestingGateway("TestShareNodes2", t) defer g2.Close() // add a node to g2 id := g2.mu.Lock() g2.addNode(dummyNode) g2.mu.Unlock(id) // connect err := g1.Connect(g2.Address()) if err != nil { t.Fatal("couldn't connect:", err) } // g1 should have received the node time.Sleep(100 * time.Millisecond) id = g1.mu.Lock() if g1.addNode(dummyNode) == nil { t.Fatal("gateway did not receive nodes during Connect:", g1.nodes) } g1.mu.Unlock(id) // remove all nodes from both peers id = g1.mu.Lock() g1.nodes = map[modules.NetAddress]struct{}{} g1.mu.Unlock(id) id = g2.mu.Lock() g2.nodes = map[modules.NetAddress]struct{}{} g2.mu.Unlock(id) // SharePeers should now return no peers var nodes []modules.NetAddress err = g1.RPC(g2.Address(), "ShareNodes", func(conn modules.PeerConn) error { return encoding.ReadObject(conn, &nodes, maxSharedNodes*maxAddrLength) }) if err != nil { t.Fatal(err) } if len(nodes) != 0 { t.Fatal("gateway gave non-existent addresses:", nodes) } // sharing should be capped at maxSharedNodes for i := 0; i < maxSharedNodes+10; i++ { g2.addNode(modules.NetAddress("111.111.111.111:" + strconv.Itoa(i))) } err = g1.RPC(g2.Address(), "ShareNodes", func(conn modules.PeerConn) error { return encoding.ReadObject(conn, &nodes, maxSharedNodes*maxAddrLength) }) if err != nil { t.Fatal(err) } if len(nodes) != maxSharedNodes { t.Fatalf("gateway gave wrong number of nodes: expected %v, got %v", maxSharedNodes, len(nodes)) } }
// New returns an initialized Gateway. func New(addr string, persistDir string) (g *Gateway, err error) { // Create the directory if it doesn't exist. err = os.MkdirAll(persistDir, 0700) if err != nil { return } // Create the logger. logger, err := makeLogger(persistDir) if err != nil { return } g = &Gateway{ handlers: make(map[rpcID]modules.RPCFunc), initRPCs: make(map[string]modules.RPCFunc), peers: make(map[modules.NetAddress]*peer), nodes: make(map[modules.NetAddress]struct{}), persistDir: persistDir, mu: sync.New(modules.SafeMutexDelay, 2), log: logger, } // Register RPCs. g.RegisterRPC("ShareNodes", g.shareNodes) g.RegisterRPC("RelayNode", g.relayNode) g.RegisterConnectCall("ShareNodes", g.requestNodes) g.RegisterConnectCall("RelayNode", g.sendAddress) g.log.Println("INFO: gateway created, started logging") // Create listener and set address. g.listener, err = net.Listen("tcp", addr) if err != nil { return } _, port, _ := net.SplitHostPort(g.listener.Addr().String()) g.myAddr = modules.NetAddress(net.JoinHostPort(modules.ExternalIP, port)) g.log.Println("INFO: our address is", g.myAddr) // Spawn the primary listener. go g.listen() // Load the old peer list. If it doesn't exist, no problem, but if it does, // we want to know about any errors preventing us from loading it. if loadErr := g.load(); loadErr != nil && !os.IsNotExist(loadErr) { return nil, loadErr } // Spawn the connector loop. This will continually attempt to add nodes as // peers to ensure we stay well-connected. go g.makeOutboundConnections() return }
// gatewayRemoveHandler handles the API call to remove a peer from the gateway. func (srv *Server) gatewayRemoveHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { addr := modules.NetAddress(ps.ByName("netaddress")) err := srv.gateway.Disconnect(addr) if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } writeSuccess(w) }
// gatewayPeersRemoveHandler handles the API call to remove a peer from the gateway. func (srv *Server) gatewayPeersRemoveHandler(w http.ResponseWriter, req *http.Request) { addr := modules.NetAddress(req.FormValue("address")) err := srv.gateway.Disconnect(addr) if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } writeSuccess(w) }
// gatewayDisconnectHandler handles the API call to remove a peer from the gateway. func (api *API) gatewayDisconnectHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { addr := modules.NetAddress(ps.ByName("netaddress")) err := api.gateway.Disconnect(addr) if err != nil { WriteError(w, Error{err.Error()}, http.StatusBadRequest) return } WriteSuccess(w) }
// TestConnectRejectsInvalidAddrs tests that Connect only connects to valid IP // addresses. func TestConnectRejectsInvalidAddrs(t *testing.T) { if testing.Short() { t.SkipNow() } g := newTestingGateway("TestConnectRejectsInvalidAddrs", t) defer g.Close() g2 := newTestingGateway("TestConnectRejectsInvalidAddrs2", t) defer g2.Close() _, g2Port, err := net.SplitHostPort(string(g2.Address())) if err != nil { t.Fatal(err) } tests := []struct { addr modules.NetAddress wantErr bool msg string }{ { addr: "127.0.0.1:123", wantErr: true, msg: "Connect should reject unreachable addresses", }, { addr: "111.111.111.111:0", wantErr: true, msg: "Connect should reject invalid NetAddresses", }, { addr: modules.NetAddress(net.JoinHostPort("localhost", g2Port)), wantErr: true, msg: "Connect should reject non-IP addresses", }, { addr: g2.Address(), msg: "Connect failed to connect to another gateway", }, { addr: g2.Address(), wantErr: true, msg: "Connect should reject an address it's already connected to", }, } for _, tt := range tests { err := g.Connect(tt.addr) if tt.wantErr != (err != nil) { t.Errorf("%v, wantErr: %v, err: %v", tt.msg, tt.wantErr, err) } } }
// hostAnnounceHandler handles the API call to get the host to announce itself // to the network. func (srv *Server) hostAnnounceHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { var err error if addr := req.FormValue("netaddress"); addr != "" { err = srv.host.AnnounceAddress(modules.NetAddress(addr)) } else { err = srv.host.Announce() } if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } writeSuccess(w) }
// hostAnnounceHandler handles the API call to get the host to announce itself // to the network. func (srv *Server) hostAnnounceHandler(w http.ResponseWriter, req *http.Request) { // Announce checks that the host is connectible before proceeding. The // user can override this check by manually specifying the address. var err error if addr := req.FormValue("address"); addr != "" { err = srv.host.ForceAnnounce(modules.NetAddress(addr)) } else { err = srv.host.Announce() } if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } writeSuccess(w) }
// managedAcceptConnOldPeer accepts a connection request from peers < v1.0.0. // The requesting peer is added as a peer, but is not added to the node list // (older peers do not share their dialback address). The peer is only added if // a nil error is returned. func (g *Gateway) managedAcceptConnOldPeer(conn net.Conn, remoteVersion string) error { addr := modules.NetAddress(conn.RemoteAddr().String()) g.mu.Lock() defer g.mu.Unlock() g.acceptPeer(&peer{ Peer: modules.Peer{ NetAddress: addr, Inbound: true, Version: remoteVersion, }, sess: muxado.Server(conn), }) return nil }
// processConfig checks the configuration values and performs cleanup on // incorrect-but-allowed values. func processConfig(config Config) (Config, error) { var err error config.Siad.APIaddr = processNetAddr(config.Siad.APIaddr) if !config.Siad.AllowAPIBind { addr := modules.NetAddress(config.Siad.APIaddr) if !addr.IsLoopback() && addr.Host() != "" { return Config{}, errors.New("you must pass --disable-api-security to bind Siad to a non-localhost address") } } config.Siad.RPCaddr = processNetAddr(config.Siad.RPCaddr) config.Siad.HostAddr = processNetAddr(config.Siad.HostAddr) config.Siad.Modules, err = processModules(config.Siad.Modules) if err != nil { return Config{}, err } return config, nil }
// shareNodes is the receiving end of the ShareNodes RPC. It writes up to 10 // randomly selected nodes to the caller. func (g *Gateway) shareNodes(conn modules.PeerConn) error { conn.SetDeadline(time.Now().Add(connStdDeadline)) // Assemble a list of nodes to send to the peer. var nodes []modules.NetAddress func() { g.mu.RLock() defer g.mu.RUnlock() // Create a random permutation of nodes from the gateway to iterate // through. gnodes := make([]modules.NetAddress, 0, len(g.nodes)) for node := range g.nodes { gnodes = append(gnodes, node) } perm, err := crypto.Perm(len(g.nodes)) if err != nil { g.log.Severe("Unable to get random permutation for sharing nodes") } // Iterate through the random permutation of nodes and select the // desirable ones. remoteNA := modules.NetAddress(conn.RemoteAddr().String()) for _, i := range perm { // Don't share local peers with remote peers. That means that if 'node' // is loopback, it will only be shared if the remote peer is also // loopback. And if 'node' is private, it will only be shared if the // remote peer is either the loopback or is also private. node := gnodes[i] if node.IsLoopback() && !remoteNA.IsLoopback() { continue } if node.IsLocal() && !remoteNA.IsLocal() { continue } nodes = append(nodes, node) if uint64(len(nodes)) == maxSharedNodes { break } } }() return encoding.WriteObject(conn, nodes) }
func TestMakeOutboundConnections(t *testing.T) { if testing.Short() { t.SkipNow() } g1 := newTestingGateway("TestMakeOutboundConnections1", t) defer g1.Close() // first add 8 dummy peers id := g1.mu.Lock() for i := 0; i < 8; i++ { peerAddr := modules.NetAddress("foo" + strconv.Itoa(i)) g1.peers[peerAddr] = &peer{addr: peerAddr, sess: nil} } g1.mu.Unlock(id) // makeOutboundConnections should now sleep for 5 seconds time.Sleep(1 * time.Second) // remove a peer while makeOutboundConnections is asleep, and add a new // connectable address to the node list id = g1.mu.Lock() delete(g1.peers, "foo1") g1.mu.Unlock(id) g2 := newTestingGateway("TestMakeOutboundConnections2", t) defer g2.Close() id = g1.mu.Lock() g1.nodes[g2.Address()] = struct{}{} // manual insertion to bypass addNode g1.mu.Unlock(id) // when makeOutboundConnections wakes up, it should connect to g2. time.Sleep(5 * time.Second) id = g1.mu.RLock() defer g1.mu.RUnlock(id) if len(g1.peers) != 8 { t.Fatal("gateway did not reach 8 peers:", g1.peers) } if g1.peers[g2.Address()] == nil { t.Fatal("gateway did not connect to g2") } }
// managedAcceptConnOldPeer accepts a connection request from peers < v1.0.0. // The requesting peer is added as a peer, but is not added to the node list // (older peers do not share their dialback address). The peer is only added if // a nil error is returned. func (g *Gateway) managedAcceptConnOldPeer(conn net.Conn, remoteVersion string) error { addr := modules.NetAddress(conn.RemoteAddr().String()) g.mu.Lock() defer g.mu.Unlock() // Old peers are unable to give us a dialback port, so we can't verify // whether or not they are local peers. g.acceptPeer(&peer{ Peer: modules.Peer{ Inbound: true, Local: false, NetAddress: addr, Version: remoteVersion, }, sess: muxado.Server(conn), }) return nil }
// relayBlock is an RPC that accepts a block from a peer. func (cs *ConsensusSet) relayBlock(conn modules.PeerConn) error { // Decode the block from the connection. var b types.Block err := encoding.ReadObject(conn, &b, types.BlockSizeLimit) if err != nil { return err } // Submit the block to the consensus set. err = cs.AcceptBlock(b) if err == errOrphan { // If the block is an orphan, try to find the parents. The block // received from the peer is discarded and will be downloaded again if // the parent is found. go cs.gateway.RPC(modules.NetAddress(conn.RemoteAddr().String()), "SendBlocks", cs.threadedReceiveBlocks) } if err != nil { return err } return nil }
// verifyAPISecurity checks that the security values are consistent with a // sane, secure system. func verifyAPISecurity(config Config) error { // Make sure that only the loopback address is allowed unless the // --disable-api-security flag has been used. if !config.Siad.AllowAPIBind { addr := modules.NetAddress(config.Siad.APIaddr) if !addr.IsLoopback() { if addr.Host() == "" { return fmt.Errorf("a blank host will listen on all interfaces, did you mean localhost:%v?\nyou must pass --disable-api-security to bind Siad to a non-localhost address", addr.Port()) } return errors.New("you must pass --disable-api-security to bind Siad to a non-localhost address") } return nil } // If the --disable-api-security flag is used, enforce that // --authenticate-api must also be used. if config.Siad.AllowAPIBind && !config.Siad.AuthenticateAPI { return errors.New("cannot use --disable-api-security without setting an api password") } return nil }
// TestHostAnnounceAddress checks that the host announce address function is // operating correctly. func TestHostAnnounceAddress(t *testing.T) { if testing.Short() { t.SkipNow() } t.Parallel() ht, err := newHostTester("TestHostAnnounceAddress") if err != nil { t.Fatal(err) } defer ht.Close() // Create an announcement finder to scan the blockchain for host // announcements. af, err := newAnnouncementFinder(ht.cs) if err != nil { t.Fatal(err) } defer af.Close() // Create an announcement, then use the address finding module to scan the // blockchain for the host's address. addr := modules.NetAddress("foo.com:1234") err = ht.host.AnnounceAddress(addr) if err != nil { t.Fatal(err) } _, err = ht.miner.AddBlock() if err != nil { t.Fatal(err) } if len(af.netAddresses) != 1 { t.Fatal("could not find host announcement in blockchain") } if af.netAddresses[0] != addr { t.Error("announcement has wrong address") } if !bytes.Equal(af.publicKeys[0].Key, ht.host.publicKey.Key) { t.Error("announcement has wrong host key") } }
// threadedLearnHostname discovers the external IP of the Gateway. Once the IP // has been discovered, it registers the ShareNodes RPC to be called on new // connections, advertising the IP to other nodes. func (g *Gateway) threadedLearnHostname() { if err := g.threads.Add(); err != nil { return } defer g.threads.Done() if build.Release == "testing" { return } var host string // try UPnP first, then fallback to myexternalip.com d, err := upnp.Discover() if err == nil { host, err = d.ExternalIP() } if err != nil { host, err = myExternalIP() } if err != nil { g.log.Println("WARN: failed to discover external IP:", err) return } g.mu.RLock() addr := modules.NetAddress(net.JoinHostPort(host, g.port)) g.mu.RUnlock() if err := addr.IsValid(); err != nil { g.log.Printf("WARN: discovered hostname %q is invalid: %v", addr, err) return } g.mu.Lock() g.myAddr = addr g.mu.Unlock() g.log.Println("INFO: our address is", addr) }
// acceptConnPortHandshake performs the port handshake and should be called on // the side accepting a connection request. The remote address is only returned // if err == nil. func acceptConnPortHandshake(conn net.Conn) (remoteAddr modules.NetAddress, err error) { host, _, err := net.SplitHostPort(conn.RemoteAddr().String()) if err != nil { return "", err } // Read the peer's port that we can dial them back on. var dialbackPort string err = encoding.ReadObject(conn, &dialbackPort, 13) // Max port # is 65535 (5 digits long) + 8 byte string length prefix if err != nil { return "", fmt.Errorf("could not read remote peer's port: %v", err) } remoteAddr = modules.NetAddress(net.JoinHostPort(host, dialbackPort)) if err := remoteAddr.IsValid(); err != nil { return "", fmt.Errorf("peer's address (%v) is invalid: %v", remoteAddr, err) } // Sanity check to ensure that appending the port string to the host didn't // change the host. Only necessary because the peer sends the port as a string // instead of an integer. if remoteAddr.Host() != host { return "", fmt.Errorf("peer sent a port which modified the host") } return remoteAddr, nil }
// acceptConn adds a connecting node as a peer. func (g *Gateway) acceptConn(conn net.Conn) { addr := modules.NetAddress(conn.RemoteAddr().String()) g.log.Printf("INFO: %v wants to connect", addr) // don't connect to an IP address more than once if build.Release != "testing" { id := g.mu.RLock() for p := range g.peers { if p.Host() == addr.Host() { g.mu.RUnlock(id) conn.Close() g.log.Printf("INFO: rejected connection from %v: already connected", addr) return } } g.mu.RUnlock(id) } // read version var remoteVersion string if err := encoding.ReadObject(conn, &remoteVersion, maxAddrLength); err != nil { conn.Close() g.log.Printf("INFO: %v wanted to connect, but we could not read their version: %v", addr, err) return } // decide whether to accept // NOTE: this version must be bumped whenever the gateway or consensus // breaks compatibility. if build.VersionCmp(remoteVersion, "0.3.3") < 0 { encoding.WriteObject(conn, "reject") conn.Close() g.log.Printf("INFO: %v wanted to connect, but their version (%v) was unacceptable", addr, remoteVersion) return } // respond with our version if err := encoding.WriteObject(conn, "0.3.3"); err != nil { conn.Close() g.log.Printf("INFO: could not write version ack to %v: %v", addr, err) return } // If we are already fully connected, kick out an old inbound peer to make // room for the new one. Among other things, this ensures that bootstrap // nodes will always be connectible. Worst case, you'll connect, receive a // node list, and immediately get booted. But once you have the node list // you should be able to connect to less full peers. id := g.mu.Lock() if len(g.peers) >= fullyConnectedThreshold { oldPeer, err := g.randomInboundPeer() if err == nil { g.peers[oldPeer].sess.Close() delete(g.peers, oldPeer) g.log.Printf("INFO: disconnected from %v to make room for %v", oldPeer, addr) } } // add the peer g.addPeer(&peer{addr: addr, sess: muxado.Server(conn), inbound: true}) g.mu.Unlock(id) g.log.Printf("INFO: accepted connection from new peer %v (v%v)", addr, remoteVersion) }
// fakeAddr returns a modules.NetAddress to be used in a HostEntry. Such // addresses are needed in order to satisfy the HostDB's "1 host per IP" rule. func fakeAddr(n uint8) modules.NetAddress { return modules.NetAddress("127.0.0." + strconv.Itoa(int(n)) + ":0") }