diff --git a/src/address/address.go b/src/address/address.go index 5c13257b..3960b783 100644 --- a/src/address/address.go +++ b/src/address/address.go @@ -1,22 +1,24 @@ +// Package address contains the types used by yggdrasil to represent IPv6 addresses or prefixes, as well as functions for working with these types. +// Of particular importance are the functions used to derive addresses or subnets from a NodeID, or to get the NodeID and bitmask of the bits visible from an address, which is needed for DHT searches. package address import "github.com/yggdrasil-network/yggdrasil-go/src/crypto" -// address represents an IPv6 address in the yggdrasil address range. +// Address represents an IPv6 address in the yggdrasil address range. type Address [16]byte -// subnet represents an IPv6 /64 subnet in the yggdrasil subnet range. +// Subnet represents an IPv6 /64 subnet in the yggdrasil subnet range. type Subnet [8]byte -// address_prefix is the prefix used for all addresses and subnets in the network. +// GetPrefix returns the address prefix used by yggdrasil. // The current implementation requires this to be a muliple of 8 bits + 7 bits. // The 8th bit of the last byte is used to signal nodes (0) or /64 prefixes (1). -// Nodes that configure this differently will be unable to communicate with eachother, though routing and the DHT machinery *should* still work. +// Nodes that configure this differently will be unable to communicate with eachother using IP packets, though routing and the DHT machinery *should* still work. func GetPrefix() [1]byte { return [...]byte{0x02} } -// isValid returns true if an address falls within the range used by nodes in the network. +// IsValid returns true if an address falls within the range used by nodes in the network. func (a *Address) IsValid() bool { prefix := GetPrefix() for idx := range prefix { @@ -27,7 +29,7 @@ func (a *Address) IsValid() bool { return true } -// isValid returns true if a prefix falls within the range usable by the network. +// IsValid returns true if a prefix falls within the range usable by the network. func (s *Subnet) IsValid() bool { prefix := GetPrefix() l := len(prefix) @@ -39,8 +41,8 @@ func (s *Subnet) IsValid() bool { return (*s)[l-1] == prefix[l-1]|0x01 } -// address_addrForNodeID takes a *NodeID as an argument and returns an *address. -// This subnet begins with the address prefix, with the last bit set to 0 to indicate an address. +// AddrForNodeID takes a *NodeID as an argument and returns an *Address. +// This address begins with the contents of GetPrefix(), with the last bit set to 0 to indicate an address. // The following 8 bits are set to the number of leading 1 bits in the NodeID. // The NodeID, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the address. func AddrForNodeID(nid *crypto.NodeID) *Address { @@ -80,7 +82,7 @@ func AddrForNodeID(nid *crypto.NodeID) *Address { return &addr } -// address_subnetForNodeID takes a *NodeID as an argument and returns a *subnet. +// SubnetForNodeID takes a *NodeID as an argument and returns an *Address. // This subnet begins with the address prefix, with the last bit set to 1 to indicate a prefix. // The following 8 bits are set to the number of leading 1 bits in the NodeID. // The NodeID, excluding the leading 1 bits and the first leading 0 bit, is truncated to the appropriate length and makes up the remainder of the subnet. @@ -96,10 +98,10 @@ func SubnetForNodeID(nid *crypto.NodeID) *Subnet { return &snet } -// getNodeIDandMask returns two *NodeID. -// The first is a NodeID with all the bits known from the address set to their correct values. -// The second is a bitmask with 1 bit set for each bit that was known from the address. -// This is used to look up NodeIDs in the DHT and tell if they match an address. +// GetNodeIDandMask returns two *NodeID. +// The first is a NodeID with all the bits known from the Address set to their correct values. +// The second is a bitmask with 1 bit set for each bit that was known from the Address. +// This is used to look up NodeIDs in the DHT and tell if they match an Address. func (a *Address) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { // Mask is a bitmask to mark the bits visible from the address // This means truncated leading 1s, first leading 0, and visible part of addr @@ -126,10 +128,10 @@ func (a *Address) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { return &nid, &mask } -// getNodeIDandMask returns two *NodeID. -// The first is a NodeID with all the bits known from the address set to their correct values. -// The second is a bitmask with 1 bit set for each bit that was known from the subnet. -// This is used to look up NodeIDs in the DHT and tell if they match a subnet. +// GetNodeIDandMask returns two *NodeID. +// The first is a NodeID with all the bits known from the Subnet set to their correct values. +// The second is a bitmask with 1 bit set for each bit that was known from the Subnet. +// This is used to look up NodeIDs in the DHT and tell if they match a Subnet. func (s *Subnet) GetNodeIDandMask() (*crypto.NodeID, *crypto.NodeID) { // As with the address version, but visible parts of the subnet prefix instead var nid crypto.NodeID diff --git a/src/config/config.go b/src/config/config.go index 6c127552..ac88bfc5 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -1,3 +1,19 @@ +/* +The config package contains structures related to the configuration of an +Yggdrasil node. + +The configuration contains, amongst other things, encryption keys which are used +to derive a node's identity, information about peerings and node information +that is shared with the network. There are also some module-specific options +related to TUN/TAP, multicast and the admin socket. + +In order for a node to maintain the same identity across restarts, you should +persist the configuration onto the filesystem or into some configuration storage +so that the encryption keys (and therefore the node ID) do not change. + +Note that Yggdrasil will automatically populate sane defaults for any +configuration option that is not provided. +*/ package config import ( @@ -8,30 +24,30 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) -// NodeState represents the active and previous configuration of the node and -// protects it with a mutex +// NodeState represents the active and previous configuration of an Yggdrasil +// node. A NodeState object is returned when starting an Yggdrasil node. Note +// that this structure and related functions are likely to disappear soon. type NodeState struct { Current NodeConfig Previous NodeConfig Mutex sync.RWMutex } -// Current returns the current node config +// Current returns the active node configuration. func (s *NodeState) GetCurrent() NodeConfig { s.Mutex.RLock() defer s.Mutex.RUnlock() return s.Current } -// Previous returns the previous node config +// Previous returns the previous node configuration. func (s *NodeState) GetPrevious() NodeConfig { s.Mutex.RLock() defer s.Mutex.RUnlock() return s.Previous } -// Replace the node configuration with new configuration. This method returns -// both the new and the previous node configs +// Replace the node configuration with new configuration. func (s *NodeState) Replace(n NodeConfig) { s.Mutex.Lock() defer s.Mutex.Unlock() @@ -39,7 +55,9 @@ func (s *NodeState) Replace(n NodeConfig) { s.Current = n } -// NodeConfig defines all configuration values needed to run a signle yggdrasil node +// NodeConfig is the main configuration structure, containing configuration +// options that are necessary for an Yggdrasil node to run. You will need to +// supply one of these structs to the Yggdrasil core when starting a node. type NodeConfig struct { Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` @@ -62,7 +80,7 @@ type NodeConfig struct { NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` } -// SessionFirewall controls the session firewall configuration +// SessionFirewall controls the session firewall configuration. type SessionFirewall struct { Enable bool `comment:"Enable or disable the session firewall. If disabled, network traffic\nfrom any node will be allowed. If enabled, the below rules apply."` AllowFromDirect bool `comment:"Allow network traffic from directly connected peers."` @@ -72,7 +90,8 @@ type SessionFirewall struct { BlacklistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always rejected,\nregardless of the whitelist, AllowFromDirect or AllowFromRemote."` } -// TunnelRouting contains the crypto-key routing tables for tunneling +// TunnelRouting contains the crypto-key routing tables for tunneling regular +// IPv4 or IPv6 subnets across the Yggdrasil network. type TunnelRouting struct { Enable bool `comment:"Enable or disable tunnel routing."` IPv6RemoteSubnets map[string]string `comment:"IPv6 subnets belonging to remote nodes, mapped to the node's public\nkey, e.g. { \"aaaa:bbbb:cccc::/e\": \"boxpubkey\", ... }"` @@ -81,18 +100,15 @@ type TunnelRouting struct { IPv4LocalSubnets []string `comment:"IPv4 subnets belonging to this node's end of the tunnels. Only traffic\nfrom these ranges will be tunnelled."` } -// SwitchOptions contains tuning options for the switch +// SwitchOptions contains tuning options for the switch. These are advanced +// options and shouldn't be changed unless necessary. type SwitchOptions struct { MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined (in bytes)."` } -// Generates default configuration. This is used when outputting the -genconf -// parameter and also when using -autoconf. The isAutoconf flag is used to -// determine whether the operating system should select a free port by itself -// (which guarantees that there will not be a conflict with any other services) -// or whether to generate a random port number. The only side effect of setting -// isAutoconf is that the TCP and UDP ports will likely end up with different -// port numbers. +// Generates default configuration and returns a pointer to the resulting +// NodeConfig. This is used when outputting the -genconf parameter and also when +// using -autoconf. func GenerateConfig() *NodeConfig { // Generate encryption keys. bpub, bpriv := crypto.NewBoxKeys() @@ -122,16 +138,19 @@ func GenerateConfig() *NodeConfig { return &cfg } -// NewEncryptionKeys generates a new encryption keypair. The encryption keys are -// used to encrypt traffic and to derive the IPv6 address/subnet of the node. +// NewEncryptionKeys replaces the encryption keypair in the NodeConfig with a +// new encryption keypair. The encryption keys are used by the router to encrypt +// traffic and to derive the node ID and IPv6 address/subnet of the node, so +// this is equivalent to discarding the node's identity on the network. func (cfg *NodeConfig) NewEncryptionKeys() { bpub, bpriv := crypto.NewBoxKeys() cfg.EncryptionPublicKey = hex.EncodeToString(bpub[:]) cfg.EncryptionPrivateKey = hex.EncodeToString(bpriv[:]) } -// NewSigningKeys generates a new signing keypair. The signing keys are used to -// derive the structure of the spanning tree. +// NewSigningKeys replaces the signing keypair in the NodeConfig with a new +// signing keypair. The signing keys are used by the switch to derive the +// structure of the spanning tree. func (cfg *NodeConfig) NewSigningKeys() { spub, spriv := crypto.NewSigKeys() cfg.SigningPublicKey = hex.EncodeToString(spub[:]) diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 75736ba7..6c10a2ef 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -1,3 +1,6 @@ +// Package crypto is a wrapper around packages under golang.org/x/crypto/, particulaly curve25519, ed25519, and nacl/box. +// This is used to avoid explicitly importing and using these packages throughout yggdrasil. +// It also includes the all-important NodeID and TreeID types, which are used to identify nodes in the DHT and in the spanning tree's root selection algorithm, respectively. package crypto /* @@ -26,12 +29,21 @@ import ( // NodeID and TreeID +// NodeIDLen is the length (in bytes) of a NodeID. const NodeIDLen = sha512.Size + +// TreeIDLen is the length (in bytes) of a TreeID. const TreeIDLen = sha512.Size + +// handleLen is the length (in bytes) of a Handle. const handleLen = 8 +// NodeID is how a yggdrasil node is identified in the DHT, and is used to derive IPv6 addresses and subnets in the main executable. It is a sha512sum hash of the node's BoxPubKey type NodeID [NodeIDLen]byte + +// TreeID is how a yggdrasil node is identified in the root selection algorithm used to construct the spanning tree. type TreeID [TreeIDLen]byte + type Handle [handleLen]byte func (n *NodeID) String() string { @@ -69,16 +81,19 @@ func (n *NodeID) PrefixLength() int { return len } +// GetNodeID returns the NodeID associated with a BoxPubKey. func GetNodeID(pub *BoxPubKey) *NodeID { h := sha512.Sum512(pub[:]) return (*NodeID)(&h) } +// GetTreeID returns the TreeID associated with a BoxPubKey func GetTreeID(pub *SigPubKey) *TreeID { h := sha512.Sum512(pub[:]) return (*TreeID)(&h) } +// NewHandle returns a new (cryptographically random) Handle, used by the session code to identify which session an incoming packet is associated with. func NewHandle() *Handle { var h Handle _, err := rand.Read(h[:]) @@ -92,14 +107,25 @@ func NewHandle() *Handle { // Signatures +// SigPubKeyLen is the length of a SigPubKey in bytes. const SigPubKeyLen = ed25519.PublicKeySize + +// SigPrivKeyLen is the length of a SigPrivKey in bytes. const SigPrivKeyLen = ed25519.PrivateKeySize + +// SigLen is the length of SigBytes. const SigLen = ed25519.SignatureSize +// SigPubKey is a public ed25519 signing key. type SigPubKey [SigPubKeyLen]byte + +// SigPrivKey is a private ed25519 signing key. type SigPrivKey [SigPrivKeyLen]byte + +// SigBytes is an ed25519 signature. type SigBytes [SigLen]byte +// NewSigKeys generates a public/private ed25519 key pair. func NewSigKeys() (*SigPubKey, *SigPrivKey) { var pub SigPubKey var priv SigPrivKey @@ -112,6 +138,7 @@ func NewSigKeys() (*SigPubKey, *SigPrivKey) { return &pub, &priv } +// Sign returns the SigBytes signing a message. func Sign(priv *SigPrivKey, msg []byte) *SigBytes { var sig SigBytes sigSlice := ed25519.Sign(priv[:], msg) @@ -119,12 +146,14 @@ func Sign(priv *SigPrivKey, msg []byte) *SigBytes { return &sig } +// Verify returns true if the provided signature matches the key and message. func Verify(pub *SigPubKey, msg []byte, sig *SigBytes) bool { // Should sig be an array instead of a slice?... // It's fixed size, but return ed25519.Verify(pub[:], msg, sig[:]) } +// Public returns the SigPubKey associated with this SigPrivKey. func (p SigPrivKey) Public() SigPubKey { priv := make(ed25519.PrivateKey, ed25519.PrivateKeySize) copy(priv[:], p[:]) @@ -138,17 +167,34 @@ func (p SigPrivKey) Public() SigPubKey { // NaCl-like crypto "box" (curve25519+xsalsa20+poly1305) +// BoxPubKeyLen is the length of a BoxPubKey in bytes. const BoxPubKeyLen = 32 + +// BoxPrivKeyLen is the length of a BoxPrivKey in bytes. const BoxPrivKeyLen = 32 + +// BoxSharedKeyLen is the length of a BoxSharedKey in bytes. const BoxSharedKeyLen = 32 + +// BoxNonceLen is the length of a BoxNonce in bytes. const BoxNonceLen = 24 + +// BoxOverhead is the length of the overhead from boxing something. const BoxOverhead = box.Overhead +// BoxPubKey is a NaCl-like "box" public key (curve25519+xsalsa20+poly1305). type BoxPubKey [BoxPubKeyLen]byte + +// BoxPrivKey is a NaCl-like "box" private key (curve25519+xsalsa20+poly1305). type BoxPrivKey [BoxPrivKeyLen]byte + +// BoxSharedKey is a NaCl-like "box" shared key (curve25519+xsalsa20+poly1305). type BoxSharedKey [BoxSharedKeyLen]byte + +// BoxNonce is the nonce used in NaCl-like crypto "box" operations (curve25519+xsalsa20+poly1305), and must not be reused for different messages encrypted using the same BoxSharedKey. type BoxNonce [BoxNonceLen]byte +// NewBoxKeys generates a new pair of public/private crypto box keys. func NewBoxKeys() (*BoxPubKey, *BoxPrivKey) { pubBytes, privBytes, err := box.GenerateKey(rand.Reader) if err != nil { @@ -159,6 +205,7 @@ func NewBoxKeys() (*BoxPubKey, *BoxPrivKey) { return pub, priv } +// GetSharedKey returns the shared key derived from your private key and the destination's public key. func GetSharedKey(myPrivKey *BoxPrivKey, othersPubKey *BoxPubKey) *BoxSharedKey { var shared [BoxSharedKeyLen]byte @@ -168,6 +215,7 @@ func GetSharedKey(myPrivKey *BoxPrivKey, return (*BoxSharedKey)(&shared) } +// BoxOpen returns a message and true if it successfull opens a crypto box using the provided shared key and nonce. func BoxOpen(shared *BoxSharedKey, boxed []byte, nonce *BoxNonce) ([]byte, bool) { @@ -178,6 +226,9 @@ func BoxOpen(shared *BoxSharedKey, return unboxed, success } +// BoxSeal seals a crypto box using the provided shared key, returning the box and the nonce needed to decrypt it. +// If nonce is nil, a random BoxNonce will be used and returned. +// If nonce is non-nil, then nonce.Increment() will be called before using it, and the incremented BoxNonce is what is returned. func BoxSeal(shared *BoxSharedKey, unboxed []byte, nonce *BoxNonce) ([]byte, *BoxNonce) { if nonce == nil { nonce = NewBoxNonce() @@ -190,6 +241,7 @@ func BoxSeal(shared *BoxSharedKey, unboxed []byte, nonce *BoxNonce) ([]byte, *Bo return boxed, nonce } +// NewBoxNonce generates a (cryptographically) random BoxNonce. func NewBoxNonce() *BoxNonce { var nonce BoxNonce _, err := rand.Read(nonce[:]) @@ -204,6 +256,7 @@ func NewBoxNonce() *BoxNonce { return &nonce } +// Increment adds 2 to a BoxNonce, which is useful if one node intends to send only with odd BoxNonce values, and the other only with even BoxNonce values. func (n *BoxNonce) Increment() { oldNonce := *n n[len(n)-1] += 2 @@ -214,6 +267,7 @@ func (n *BoxNonce) Increment() { } } +// Public returns the BoxPubKey associated with this BoxPrivKey. func (p BoxPrivKey) Public() BoxPubKey { var boxPub [BoxPubKeyLen]byte var boxPriv [BoxPrivKeyLen]byte @@ -222,9 +276,9 @@ func (p BoxPrivKey) Public() BoxPubKey { return boxPub } -// Used to subtract one nonce from another, staying in the range +- 64. -// This is used by the nonce progression machinery to advance the bitmask of recently received packets (indexed by nonce), or to check the appropriate bit of the bitmask. -// It's basically part of the machinery that prevents replays and duplicate packets. +// Minus is the result of subtracting the provided BoNonce from this BoxNonce, bounded at +- 64. +// It's primarily used to determine if a new BoxNonce is higher than the last known BoxNonce from a crypto session, and by how much. +// This is used in the machinery that makes sure replayed packets can't keep a session open indefinitely or stuck using old/bad information about a node. func (n *BoxNonce) Minus(m *BoxNonce) int64 { diff := int64(0) for idx := range n { diff --git a/src/util/bytes_mobile.go b/src/util/bytes_mobile.go index 09e78050..f862c0cd 100644 --- a/src/util/bytes_mobile.go +++ b/src/util/bytes_mobile.go @@ -8,12 +8,14 @@ func init() { debug.SetGCPercent(25) } -// On mobile, just return a nil slice. +// GetBytes always returns a nil slice on mobile platforms. func GetBytes() []byte { return nil } -// On mobile, don't do anything. +// PutBytes does literally nothing on mobile platforms. +// This is done rather than keeping a free list of bytes on platforms with memory constraints. +// It's needed to help keep memory usage low enough to fall under the limits set for e.g. iOS NEPacketTunnelProvider apps. func PutBytes(bs []byte) { return } diff --git a/src/util/bytes_other.go b/src/util/bytes_other.go index 41b8bec0..7c966087 100644 --- a/src/util/bytes_other.go +++ b/src/util/bytes_other.go @@ -7,12 +7,12 @@ import "sync" // This is used to buffer recently used slices of bytes, to prevent allocations in the hot loops. var byteStore = sync.Pool{New: func() interface{} { return []byte(nil) }} -// Gets an empty slice from the byte store. +// GetBytes returns a 0-length (possibly nil) slice of bytes from a free list, so it may have a larger capacity. func GetBytes() []byte { return byteStore.Get().([]byte)[:0] } -// Puts a slice in the store. +// PutBytes stores a slice in a free list, where it can potentially be reused to prevent future allocations. func PutBytes(bs []byte) { byteStore.Put(bs) } diff --git a/src/util/cancellation.go b/src/util/cancellation.go index af4721bb..6b2002c8 100644 --- a/src/util/cancellation.go +++ b/src/util/cancellation.go @@ -7,15 +7,22 @@ import ( "time" ) +// Cancellation is used to signal when things should shut down, such as signaling anything associated with a Conn to exit. +// This is and is similar to a context, but with an error to specify the reason for the cancellation. type Cancellation interface { - Finished() <-chan struct{} - Cancel(error) error - Error() error + Finished() <-chan struct{} // Finished returns a channel which will be closed when Cancellation.Cancel is first called. + Cancel(error) error // Cancel closes the channel returned by Finished and sets the error returned by error, or else returns the existing error if the Cancellation has already run. + Error() error // Error returns the error provided to Cancel, or nil if no error has been provided. } +// CancellationFinalized is an error returned if a cancellation object was garbage collected and the finalizer was run. +// If you ever see this, then you're probably doing something wrong with your code. var CancellationFinalized = errors.New("finalizer called") + +// CancellationTimeoutError is used when a CancellationWithTimeout or CancellationWithDeadline is cancelled due to said timeout. var CancellationTimeoutError = errors.New("timeout") +// CancellationFinalizer is set as a finalizer when creating a new cancellation with NewCancellation(), and generally shouldn't be needed by the user, but is included in case other implementations of the same interface want to make use of it. func CancellationFinalizer(c Cancellation) { c.Cancel(CancellationFinalized) } @@ -27,6 +34,7 @@ type cancellation struct { done bool } +// NewCancellation returns a pointer to a struct satisfying the Cancellation interface. func NewCancellation() Cancellation { c := cancellation{ cancel: make(chan struct{}), @@ -35,10 +43,12 @@ func NewCancellation() Cancellation { return &c } +// Finished returns a channel which will be closed when Cancellation.Cancel is first called. func (c *cancellation) Finished() <-chan struct{} { return c.cancel } +// Cancel closes the channel returned by Finished and sets the error returned by error, or else returns the existing error if the Cancellation has already run. func (c *cancellation) Cancel(err error) error { c.mutex.Lock() defer c.mutex.Unlock() @@ -52,6 +62,7 @@ func (c *cancellation) Cancel(err error) error { } } +// Error returns the error provided to Cancel, or nil if no error has been provided. func (c *cancellation) Error() error { c.mutex.RLock() err := c.err @@ -59,6 +70,7 @@ func (c *cancellation) Error() error { return err } +// CancellationChild returns a new Cancellation which can be Cancelled independently of the parent, but which will also be Cancelled if the parent is Cancelled first. func CancellationChild(parent Cancellation) Cancellation { child := NewCancellation() go func() { @@ -71,6 +83,7 @@ func CancellationChild(parent Cancellation) Cancellation { return child } +// CancellationWithTimeout returns a ChildCancellation that will automatically be Cancelled with a CancellationTimeoutError after the timeout. func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancellation { child := CancellationChild(parent) go func() { @@ -85,6 +98,7 @@ func CancellationWithTimeout(parent Cancellation, timeout time.Duration) Cancell return child } +// CancellationWithTimeout returns a ChildCancellation that will automatically be Cancelled with a CancellationTimeoutError after the specified deadline. func CancellationWithDeadline(parent Cancellation, deadline time.Time) Cancellation { return CancellationWithTimeout(parent, deadline.Sub(time.Now())) } diff --git a/src/util/util.go b/src/util/util.go index 97250122..a7a54562 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -1,3 +1,5 @@ +// Package util contains miscellaneous utilities used by yggdrasil. +// In particular, this includes a crypto worker pool, Cancellation machinery, and a sync.Pool used to reuse []byte. package util // These are misc. utility functions that didn't really fit anywhere else @@ -9,22 +11,22 @@ import ( "time" ) -// A wrapper around runtime.Gosched() so it doesn't need to be imported elsewhere. +// Yield just executes runtime.Gosched(), and is included so we don't need to explicitly import runtime elsewhere. func Yield() { runtime.Gosched() } -// A wrapper around runtime.LockOSThread() so it doesn't need to be imported elsewhere. +// LockThread executes runtime.LockOSThread(), and is included so we don't need to explicitly import runtime elsewhere. func LockThread() { runtime.LockOSThread() } -// A wrapper around runtime.UnlockOSThread() so it doesn't need to be imported elsewhere. +// UnlockThread executes runtime.UnlockOSThread(), and is included so we don't need to explicitly import runtime elsewhere. func UnlockThread() { runtime.UnlockOSThread() } -// Gets a slice of the appropriate length, reusing existing slice capacity when possible +// ResizeBytes returns a slice of the specified length. If the provided slice has sufficient capacity, it will be resized and returned rather than allocating a new slice. func ResizeBytes(bs []byte, length int) []byte { if cap(bs) >= length { return bs[:length] @@ -33,7 +35,7 @@ func ResizeBytes(bs []byte, length int) []byte { } } -// This is a workaround to go's broken timer implementation +// TimerStop stops a timer and makes sure the channel is drained, returns true if the timer was stopped before firing. func TimerStop(t *time.Timer) bool { stopped := t.Stop() select { @@ -43,10 +45,8 @@ func TimerStop(t *time.Timer) bool { return stopped } -// Run a blocking function with a timeout. -// Returns true if the function returns. -// Returns false if the timer fires. -// The blocked function remains blocked--the caller is responsible for somehow killing it. +// FuncTimeout runs the provided function in a separate goroutine, and returns true if the function finishes executing before the timeout passes, or false if the timeout passes. +// It includes no mechanism to stop the function if the timeout fires, so the user is expected to do so on their own (such as with a Cancellation or a context). func FuncTimeout(f func(), timeout time.Duration) bool { success := make(chan struct{}) go func() { @@ -63,9 +63,8 @@ func FuncTimeout(f func(), timeout time.Duration) bool { } } -// This calculates the difference between two arrays and returns items -// that appear in A but not in B - useful somewhat when reconfiguring -// and working out what configuration items changed +// Difference loops over two strings and returns the elements of A which do not appear in B. +// This is somewhat useful when needing to determine which elements of a configuration file have changed. func Difference(a, b []string) []string { ab := []string{} mb := map[string]bool{} @@ -93,7 +92,7 @@ func DecodeCoordString(in string) (out []uint64) { return out } -// GetFlowLabel takes an IP packet as an argument and returns some information about the traffic flow. +// GetFlowKey takes an IP packet as an argument and returns some information about the traffic flow. // For IPv4 packets, this is derived from the source and destination protocol and port numbers. // For IPv6 packets, this is derived from the FlowLabel field of the packet if this was set, otherwise it's handled like IPv4. // The FlowKey is then used internally by Yggdrasil for congestion control. diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index b54d7a11..4245f439 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -16,29 +16,37 @@ import ( ) // Peer represents a single peer object. This contains information from the -// preferred switch port for this peer, although there may be more than one in -// reality. +// preferred switch port for this peer, although there may be more than one +// active switch port connection to the peer in reality. +// +// This struct is informational only - you cannot manipulate peer connections +// using instances of this struct. You should use the AddPeer or RemovePeer +// functions instead. type Peer struct { - PublicKey crypto.BoxPubKey - Endpoint string - BytesSent uint64 - BytesRecvd uint64 - Protocol string - Port uint64 - Uptime time.Duration + PublicKey crypto.BoxPubKey // The public key of the remote node + Endpoint string // The connection string used to connect to the peer + BytesSent uint64 // Number of bytes sent to this peer + BytesRecvd uint64 // Number of bytes received from this peer + Protocol string // The transport protocol that this peer is connected with, typically "tcp" + Port uint64 // Switch port number for this peer connection + Uptime time.Duration // How long this peering has been active for } // SwitchPeer represents a switch connection to a peer. Note that there may be // multiple switch peers per actual peer, e.g. if there are multiple connections // to a given node. +// +// This struct is informational only - you cannot manipulate switch peer +// connections using instances of this struct. You should use the AddPeer or +// RemovePeer functions instead. type SwitchPeer struct { - PublicKey crypto.BoxPubKey - Coords []uint64 - BytesSent uint64 - BytesRecvd uint64 - Port uint64 - Protocol string - Endpoint string + PublicKey crypto.BoxPubKey // The public key of the remote node + Coords []uint64 // The coordinates of the remote node + BytesSent uint64 // Number of bytes sent via this switch port + BytesRecvd uint64 // Number of bytes received via this switch port + Port uint64 // Switch port number for this switch peer + Protocol string // The transport protocol that this switch port is connected with, typically "tcp" + Endpoint string // The connection string used to connect to the switch peer } // DHTEntry represents a single DHT entry that has been learned or cached from @@ -64,32 +72,36 @@ type NodeInfoPayload []byte // congestion and a list of switch queues created in response to congestion on a // given link. type SwitchQueues struct { - Queues []SwitchQueue - Count uint64 - Size uint64 - HighestCount uint64 - HighestSize uint64 - MaximumSize uint64 + Queues []SwitchQueue // An array of SwitchQueue objects containing information about individual queues + Count uint64 // The current number of active switch queues + Size uint64 // The current total size of active switch queues + HighestCount uint64 // The highest recorded number of switch queues so far + HighestSize uint64 // The highest recorded total size of switch queues so far + MaximumSize uint64 // The maximum allowed total size of switch queues, as specified by config } -// SwitchQueue represents a single switch queue, which is created in response -// to congestion on a given link. +// SwitchQueue represents a single switch queue. Switch queues are only created +// in response to congestion on a given link and represent how much data has +// been temporarily cached for sending once the congestion has cleared. type SwitchQueue struct { - ID string - Size uint64 - Packets uint64 - Port uint64 + ID string // The ID of the switch queue + Size uint64 // The total size, in bytes, of the queue + Packets uint64 // The number of packets in the queue + Port uint64 // The switch port to which the queue applies } -// Session represents an open session with another node. +// Session represents an open session with another node. Sessions are opened in +// response to traffic being exchanged between two nodes using Conn objects. +// Note that sessions will automatically be closed by Yggdrasil if no traffic is +// exchanged for around two minutes. type Session struct { - PublicKey crypto.BoxPubKey - Coords []uint64 - BytesSent uint64 - BytesRecvd uint64 - MTU uint16 - Uptime time.Duration - WasMTUFixed bool + PublicKey crypto.BoxPubKey // The public key of the remote node + Coords []uint64 // The coordinates of the remote node + BytesSent uint64 // Bytes sent to the session + BytesRecvd uint64 // Bytes received from the session + MTU uint16 // The maximum supported message size of the session + Uptime time.Duration // How long this session has been active for + WasMTUFixed bool // This field is no longer used } // GetPeers returns one or more Peer objects containing information about active @@ -236,7 +248,10 @@ func (c *Core) GetSessions() []Session { return sessions } -// ConnListen returns a listener for Yggdrasil session connections. +// ConnListen returns a listener for Yggdrasil session connections. You can only +// call this function once as each Yggdrasil node can only have a single +// ConnListener. Make sure to keep the reference to this for as long as it is +// needed. func (c *Core) ConnListen() (*Listener, error) { c.router.sessions.listenerMutex.Lock() defer c.router.sessions.listenerMutex.Unlock() @@ -251,7 +266,10 @@ func (c *Core) ConnListen() (*Listener, error) { return c.router.sessions.listener, nil } -// ConnDialer returns a dialer for Yggdrasil session connections. +// ConnDialer returns a dialer for Yggdrasil session connections. Since +// ConnDialers are stateless, you can request as many dialers as you like, +// although ideally you should request only one and keep the reference to it for +// as long as it is needed. func (c *Core) ConnDialer() (*Dialer, error) { return &Dialer{ core: c, @@ -265,48 +283,69 @@ func (c *Core) ListenTCP(uri string) (*TcpListener, error) { return c.link.tcp.listen(uri) } -// NodeID gets the node ID. +// NodeID gets the node ID. This is derived from your router encryption keys. +// Remote nodes wanting to open connections to your node will need to know your +// node ID. func (c *Core) NodeID() *crypto.NodeID { return crypto.GetNodeID(&c.boxPub) } -// TreeID gets the tree ID. +// TreeID gets the tree ID. This is derived from your switch signing keys. There +// is typically no need to share this key. func (c *Core) TreeID() *crypto.TreeID { return crypto.GetTreeID(&c.sigPub) } -// SigningPublicKey gets the node's signing public key. +// SigningPublicKey gets the node's signing public key, as used by the switch. func (c *Core) SigningPublicKey() string { return hex.EncodeToString(c.sigPub[:]) } -// EncryptionPublicKey gets the node's encryption public key. +// EncryptionPublicKey gets the node's encryption public key, as used by the +// router. func (c *Core) EncryptionPublicKey() string { return hex.EncodeToString(c.boxPub[:]) } -// Coords returns the current coordinates of the node. +// Coords returns the current coordinates of the node. Note that these can +// change at any time for a number of reasons, not limited to but including +// changes to peerings (either yours or a parent nodes) or changes to the network +// root. +// +// This function may return an empty array - this is normal behaviour if either +// you are the root of the network that you are connected to, or you are not +// connected to any other nodes (effectively making you the root of a +// single-node network). func (c *Core) Coords() []uint64 { table := c.switchTable.table.Load().(lookupTable) return wire_coordsBytestoUint64s(table.self.getCoords()) } // Address gets the IPv6 address of the Yggdrasil node. This is always a /128 -// address. +// address. The IPv6 address is only relevant when the node is operating as an +// IP router and often is meaningless when embedded into an application, unless +// that application also implements either VPN functionality or deals with IP +// packets specifically. func (c *Core) Address() net.IP { address := net.IP(address.AddrForNodeID(c.NodeID())[:]) return address } // Subnet gets the routed IPv6 subnet of the Yggdrasil node. This is always a -// /64 subnet. +// /64 subnet. The IPv6 subnet is only relevant when the node is operating as an +// IP router and often is meaningless when embedded into an application, unless +// that application also implements either VPN functionality or deals with IP +// packets specifically. func (c *Core) Subnet() net.IPNet { subnet := address.SubnetForNodeID(c.NodeID())[:] subnet = append(subnet, 0, 0, 0, 0, 0, 0, 0, 0) return net.IPNet{IP: subnet, Mask: net.CIDRMask(64, 128)} } -// MyNodeInfo gets the currently configured nodeinfo. +// MyNodeInfo gets the currently configured nodeinfo. NodeInfo is typically +// specified through the "NodeInfo" option in the node configuration or using +// the SetNodeInfo function, although it may also contain other built-in values +// such as "buildname", "buildversion" etc. func (c *Core) MyNodeInfo() NodeInfoPayload { return c.router.nodeinfo.getNodeInfo() } @@ -356,7 +395,9 @@ func (c *Core) SetSessionGatekeeper(f func(pubkey *crypto.BoxPubKey, initiator b } // SetLogger sets the output logger of the Yggdrasil node after startup. This -// may be useful if you want to redirect the output later. +// may be useful if you want to redirect the output later. Note that this +// expects a Logger from the github.com/gologme/log package and not from Go's +// built-in log package. func (c *Core) SetLogger(log *log.Logger) { c.log = log } @@ -427,12 +468,17 @@ func (c *Core) DisconnectPeer(port uint64) error { } // GetAllowedEncryptionPublicKeys returns the public keys permitted for incoming -// peer connections. +// peer connections. If this list is empty then all incoming peer connections +// are accepted by default. func (c *Core) GetAllowedEncryptionPublicKeys() []string { return c.peers.getAllowedEncryptionPublicKeys() } // AddAllowedEncryptionPublicKey whitelists a key for incoming peer connections. +// By default all incoming peer connections are accepted, but adding public keys +// to the whitelist using this function enables strict checking from that point +// forward. Once the whitelist is enabled, only peer connections from +// whitelisted public keys will be accepted. func (c *Core) AddAllowedEncryptionPublicKey(bstr string) (err error) { c.peers.addAllowedEncryptionPublicKey(bstr) return nil diff --git a/src/yggdrasil/conn.go b/src/yggdrasil/conn.go index 9af5d240..eb2bb74b 100644 --- a/src/yggdrasil/conn.go +++ b/src/yggdrasil/conn.go @@ -53,6 +53,9 @@ func (e *ConnError) Closed() bool { return e.closed } +// The Conn struct is a reference to an active connection session between the +// local node and a remote node. Conn implements the io.ReadWriteCloser +// interface and is used to send and receive traffic with a remote node. type Conn struct { phony.Inbox core *Core @@ -78,6 +81,11 @@ func newConn(core *Core, nodeID *crypto.NodeID, nodeMask *crypto.NodeID, session return &conn } +// String returns a string that uniquely identifies a connection. Currently this +// takes a form similar to "conn=0x0000000", which contains a memory reference +// to the Conn object. While this value should always be unique for each Conn +// object, the format of this is not strictly defined and may change in the +// future. func (c *Conn) String() string { var s string phony.Block(c, func() { s = fmt.Sprintf("conn=%p", c) }) @@ -162,7 +170,12 @@ func (c *Conn) _getDeadlineCancellation(t *time.Time) (util.Cancellation, bool) } } -// SetReadCallback sets a callback which will be called whenever a packet is received. +// SetReadCallback allows you to specify a function that will be called whenever +// a packet is received. This should be used if you wish to implement +// asynchronous patterns for receiving data from the remote node. +// +// Note that if a read callback has been supplied, you should no longer attempt +// to use the synchronous Read function. func (c *Conn) SetReadCallback(callback func([]byte)) { c.Act(nil, func() { c.readCallback = callback @@ -217,7 +230,14 @@ func (c *Conn) readNoCopy() ([]byte, error) { } } -// Implements net.Conn.Read +// Read allows you to read from the connection in a synchronous fashion. The +// function will block up until the point that either new data is available, the +// connection has been closed or the read deadline has been reached. If the +// function succeeds, the number of bytes read from the connection will be +// returned. Otherwise, an error condition will be returned. +// +// Note that you can also implement asynchronous reads by using SetReadCallback. +// If you do that, you should no longer attempt to use the Read function. func (c *Conn) Read(b []byte) (int, error) { bs, err := c.readNoCopy() if err != nil { @@ -259,9 +279,9 @@ func (c *Conn) _write(msg FlowKeyMessage) error { return nil } -// WriteFrom should be called by a phony.Actor, and tells the Conn to send a message. -// This is used internaly by Write. -// If the callback is called with a non-nil value, then it is safe to reuse the argument FlowKeyMessage. +// WriteFrom should be called by a phony.Actor, and tells the Conn to send a +// message. This is used internaly by Write. If the callback is called with a +// non-nil value, then it is safe to reuse the argument FlowKeyMessage. func (c *Conn) WriteFrom(from phony.Actor, msg FlowKeyMessage, callback func(error)) { c.Act(from, func() { callback(c._write(msg)) @@ -291,7 +311,11 @@ func (c *Conn) writeNoCopy(msg FlowKeyMessage) error { return err } -// Write implement the Write function of a net.Conn, and makes use of WriteNoCopy under the hood. +// Write allows you to write to the connection in a synchronous fashion. This +// function may block until either the write has completed, the connection has +// been closed or the write deadline has been reached. If the function succeeds, +// the number of written bytes is returned. Otherwise, an error condition is +// returned. func (c *Conn) Write(b []byte) (int, error) { written := len(b) msg := FlowKeyMessage{Message: append(util.GetBytes(), b...)} @@ -303,6 +327,10 @@ func (c *Conn) Write(b []byte) (int, error) { return written, err } +// Close will close an open connection and any blocking operations on the +// connection will unblock and return. From this point forward, the connection +// can no longer be used and you should no longer attempt to Read or Write to +// the connection. func (c *Conn) Close() (err error) { phony.Block(c, func() { if c.session != nil { @@ -317,10 +345,13 @@ func (c *Conn) Close() (err error) { return } +// LocalAddr returns the complete node ID of the local side of the connection. +// This is always going to return your own node's node ID. func (c *Conn) LocalAddr() crypto.NodeID { return *crypto.GetNodeID(&c.core.boxPub) } +// RemoteAddr returns the complete node ID of the remote side of the connection. func (c *Conn) RemoteAddr() crypto.NodeID { // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors... var n crypto.NodeID @@ -328,18 +359,32 @@ func (c *Conn) RemoteAddr() crypto.NodeID { return n } +// SetDeadline is equivalent to calling both SetReadDeadline and +// SetWriteDeadline with the same value, configuring the maximum amount of time +// that synchronous Read and Write operations can block for. If no deadline is +// configured, Read and Write operations can potentially block indefinitely. func (c *Conn) SetDeadline(t time.Time) error { c.SetReadDeadline(t) c.SetWriteDeadline(t) return nil } +// SetReadDeadline configures the maximum amount of time that a synchronous Read +// operation can block for. A Read operation will unblock at the point that the +// read deadline is reached if no other condition (such as data arrival or +// connection closure) happens first. If no deadline is configured, Read +// operations can potentially block indefinitely. func (c *Conn) SetReadDeadline(t time.Time) error { // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors... phony.Block(c, func() { c.readDeadline = &t }) return nil } +// SetWriteDeadline configures the maximum amount of time that a synchronous +// Write operation can block for. A Write operation will unblock at the point +// that the read deadline is reached if no other condition (such as data sending +// or connection closure) happens first. If no deadline is configured, Write +// operations can potentially block indefinitely. func (c *Conn) SetWriteDeadline(t time.Time) error { // TODO warn that this can block while waiting for the Conn actor to run, so don't call it from other actors... phony.Block(c, func() { c.writeDeadline = &t }) diff --git a/src/yggdrasil/doc.go b/src/yggdrasil/doc.go new file mode 100644 index 00000000..44d39b66 --- /dev/null +++ b/src/yggdrasil/doc.go @@ -0,0 +1,176 @@ +/* +Package yggdrasil implements the core functionality of the Yggdrasil Network. + +Introduction + +Yggdrasil is a proof-of-concept mesh network which provides end-to-end encrypted +communication between nodes in a decentralised fashion. The network is arranged +using a globally-agreed spanning tree which provides each node with a locator +(coordinates relative to the root) and a distributed hash table (DHT) mechanism +for finding other nodes. + +Each node also implements a router, which is responsible for encryption of +traffic, searches and connections, and a switch, which is responsible ultimately +for forwarding traffic across the network. + +While many Yggdrasil nodes in existence today are IP nodes - that is, they are +transporting IPv6 packets, like a kind of mesh VPN - it is also possible to +integrate Yggdrasil into your own applications and use it as a generic data +transport, similar to UDP. + +This library is what you need to integrate and use Yggdrasil in your own +application. + +Basics + +In order to start an Yggdrasil node, you should start by generating node +configuration, which amongst other things, includes encryption keypairs which +are used to generate the node's identity, and supply a logger which Yggdrasil's +output will be written to. + +This may look something like this: + + import ( + "os" + "github.com/gologme/log" + "github.com/yggdrasil-network/yggdrasil-go/src/config" + "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" + ) + + type node struct { + core yggdrasil.Core + config *config.NodeConfig + log *log.Logger + } + +You then can supply node configuration and a logger: + + n := node{} + n.log = log.New(os.Stdout, "", log.Flags()) + n.config = config.GenerateConfig() + +In the above example, we ask the config package to supply new configuration each +time, which results in fresh encryption keys and therefore a new identity. It is +normally preferable in most cases to persist node configuration onto the +filesystem or into some configuration store so that the node's identity does not +change each time that the program starts. Note that Yggdrasil will automatically +fill in any missing configuration items with sane defaults. + +Once you have supplied a logger and some node configuration, you can then start +the node: + + n.core.Start(n.config, n.log) + +Add some peers to connect to the network: + + n.core.AddPeer("tcp://some-host.net:54321", "") + n.core.AddPeer("tcp://[2001::1:2:3]:54321", "") + n.core.AddPeer("tcp://1.2.3.4:54321", "") + +You can also ask the API for information about our node: + + n.log.Println("My node ID is", n.core.NodeID()) + n.log.Println("My public key is", n.core.EncryptionPublicKey()) + n.log.Println("My coords are", n.core.Coords()) + +Incoming Connections + +Once your node is started, you can then listen for connections from other nodes +by asking the API for a Listener: + + listener, err := n.core.ConnListen() + if err != nil { + // ... + } + +The Listener has a blocking Accept function which will wait for incoming +connections from remote nodes. It will return a Conn when a connection is +received. If the node never receives any incoming connections then this function +can block forever, so be prepared for that, perhaps by listening in a separate +goroutine. + +Assuming that you have defined a myConnectionHandler function to deal with +incoming connections: + + for { + conn, err := listener.Accept() + if err != nil { + // ... + } + + // We've got a new connection + go myConnectionHandler(conn) + } + +Outgoing Connections + +If you know the node ID of the remote node that you want to talk to, you can +dial an outbound connection to it. To do this, you should first ask the API for +a Dialer: + + dialer, err := n.core.ConnDialer() + if err != nil { + // ... + } + +You can then dial using the 16-byte node ID in hexadecimal format, for example: + + conn, err := dialer.Dial("nodeid", "24a58cfce691ec016b0f698f7be1bee983cea263781017e99ad3ef62b4ef710a45d6c1a072c5ce46131bd574b78818c9957042cafeeed13966f349e94eb771bf") + if err != nil { + // ... + } + +Using Connections + +Conn objects are implementations of io.ReadWriteCloser, and as such, you can +Read, Write and Close them as necessary. + +Each Read or Write operation can deal with a buffer with a maximum size of 65535 +bytes - any bigger than this and the operation will return an error. + +For example, to write to the Conn from the supplied buffer: + + buf := []byte{1, 2, 3, 4, 5} + w, err := conn.Write(buf) + if err != nil { + // ... + } else { + // written w bytes + } + +Reading from the Conn into the supplied buffer: + + buf := make([]byte, 65535) + r, err := conn.Read(buf) + if err != nil { + // ... + } else { + // read r bytes + } + +When you are happy that a connection is no longer required, you can discard it: + + err := conn.Close() + if err != nil { + // ... + } + +Limitations + +You should be aware of the following limitations when working with the Yggdrasil +library: + +Individual messages written through Yggdrasil connections can not exceed 65535 +bytes in size. Yggdrasil has no concept of fragmentation, so if you try to send +a message that exceeds 65535 bytes in size, it will be dropped altogether and +an error will be returned. + +Yggdrasil connections are unreliable by nature. Messages are delivered on a +best-effort basis, and employs congestion control where appropriate to ensure +that congestion does not affect message transport, but Yggdrasil will not +retransmit any messages that have been lost. If reliable delivery is important +then you should manually implement acknowledgement and retransmission of +messages. + +*/ +package yggdrasil