github.com/avahowell/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  }