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  }