diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index e9a21c13..ba56d6b7 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -18,7 +18,6 @@ import ( gsyslog "github.com/hashicorp/go-syslog" "github.com/hjson/hjson-go" "github.com/kardianos/minwinsvc" - "github.com/mitchellh/mapstructure" "github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/config" @@ -31,7 +30,6 @@ import ( type node struct { core yggdrasil.Core - state *config.NodeState tuntap tuntap.TunAdapter multicast multicast.Multicast admin admin.AdminSocket @@ -66,47 +64,10 @@ func readConfig(useconf *bool, useconffile *string, normaliseconf *bool) *config panic(err) } } - // Generate a new configuration - this gives us a set of sane defaults - - // then parse the configuration we loaded above on top of it. The effect - // of this is that any configuration item that is missing from the provided - // configuration will use a sane default. + // Generate blank configuration cfg := config.GenerateConfig() - var dat map[string]interface{} - if err := hjson.Unmarshal(conf, &dat); err != nil { - panic(err) - } - // Check for fields that have changed type recently, e.g. the Listen config - // option is now a []string rather than a string - if listen, ok := dat["Listen"].(string); ok { - dat["Listen"] = []string{listen} - } - if tunnelrouting, ok := dat["TunnelRouting"].(map[string]interface{}); ok { - if c, ok := tunnelrouting["IPv4Sources"]; ok { - delete(tunnelrouting, "IPv4Sources") - tunnelrouting["IPv4LocalSubnets"] = c - } - if c, ok := tunnelrouting["IPv6Sources"]; ok { - delete(tunnelrouting, "IPv6Sources") - tunnelrouting["IPv6LocalSubnets"] = c - } - if c, ok := tunnelrouting["IPv4Destinations"]; ok { - delete(tunnelrouting, "IPv4Destinations") - tunnelrouting["IPv4RemoteSubnets"] = c - } - if c, ok := tunnelrouting["IPv6Destinations"]; ok { - delete(tunnelrouting, "IPv6Destinations") - tunnelrouting["IPv6RemoteSubnets"] = c - } - } - // Sanitise the config - confJson, err := json.Marshal(dat) - if err != nil { - panic(err) - } - json.Unmarshal(confJson, &cfg) - // Overlay our newly mapped configuration onto the autoconf node config that - // we generated above. - if err = mapstructure.Decode(dat, &cfg); err != nil { + // ... and then update it with the supplied HJSON input + if err := cfg.UnmarshalHJSON(conf); err != nil { panic(err) } return cfg @@ -119,9 +80,9 @@ func doGenconf(isjson bool) string { var bs []byte var err error if isjson { - bs, err = json.MarshalIndent(cfg, "", " ") + bs, err = cfg.MarshalJSON() } else { - bs, err = hjson.Marshal(cfg) + bs, err = cfg.MarshalHJSON() } if err != nil { panic(err) @@ -224,7 +185,7 @@ func main() { n := node{} // Now start Yggdrasil - this starts the DHT, router, switch and other core // components needed for Yggdrasil to operate - n.state, err = n.core.Start(cfg, logger) + err = n.core.Start(cfg, logger) if err != nil { logger.Errorln("An error occurred during startup") panic(err) @@ -232,12 +193,12 @@ func main() { // Register the session firewall gatekeeper function n.core.SetSessionGatekeeper(n.sessionFirewall) // Start the admin socket - n.admin.Init(&n.core, n.state, logger, nil) + n.admin.Init(&n.core, logger, nil) if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } // Start the multicast interface - n.multicast.Init(&n.core, n.state, logger, nil) + n.multicast.Init(&n.core, logger, nil) if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } @@ -245,7 +206,7 @@ func main() { // Start the TUN/TAP interface if listener, err := n.core.ConnListen(); err == nil { if dialer, err := n.core.ConnDialer(); err == nil { - n.tuntap.Init(n.state, logger, listener, dialer) + n.tuntap.Init(&n.core, logger, listener, dialer) if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } @@ -281,8 +242,8 @@ func main() { cfg = readConfig(useconf, useconffile, normaliseconf) logger.Infoln("Reloading configuration from", *useconffile) n.core.UpdateConfig(cfg) - n.tuntap.UpdateConfig(cfg) - n.multicast.UpdateConfig(cfg) + n.tuntap.UpdateConfig() + n.multicast.UpdateConfig() } else { logger.Errorln("Reloading config at runtime is only possible with -useconffile") } @@ -300,18 +261,17 @@ func (n *node) shutdown() { } func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { - n.state.Mutex.RLock() - defer n.state.Mutex.RUnlock() + current := n.core.GetConfig() // Allow by default if the session firewall is disabled - if !n.state.Current.SessionFirewall.Enable { + if !current.SessionFirewall.Enable { return true } // Prepare for checking whitelist/blacklist var box crypto.BoxPubKey // Reject blacklisted nodes - for _, b := range n.state.Current.SessionFirewall.BlacklistEncryptionPublicKeys { + for _, b := range current.SessionFirewall.BlacklistEncryptionPublicKeys { key, err := hex.DecodeString(b) if err == nil { copy(box[:crypto.BoxPubKeyLen], key) @@ -322,7 +282,7 @@ func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { } // Allow whitelisted nodes - for _, b := range n.state.Current.SessionFirewall.WhitelistEncryptionPublicKeys { + for _, b := range current.SessionFirewall.WhitelistEncryptionPublicKeys { key, err := hex.DecodeString(b) if err == nil { copy(box[:crypto.BoxPubKeyLen], key) @@ -333,7 +293,7 @@ func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { } // Allow outbound sessions if appropriate - if n.state.Current.SessionFirewall.AlwaysAllowOutbound { + if current.SessionFirewall.AlwaysAllowOutbound { if initiator { return true } @@ -349,12 +309,12 @@ func (n *node) sessionFirewall(pubkey *crypto.BoxPubKey, initiator bool) bool { } // Allow direct peers if appropriate - if n.state.Current.SessionFirewall.AllowFromDirect && isDirectPeer { + if current.SessionFirewall.AllowFromDirect && isDirectPeer { return true } // Allow remote nodes if appropriate - if n.state.Current.SessionFirewall.AllowFromRemote && !isDirectPeer { + if current.SessionFirewall.AllowFromRemote && !isDirectPeer { return true } diff --git a/src/admin/admin.go b/src/admin/admin.go index c7fc151d..fdc005ff 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -15,7 +15,6 @@ import ( "github.com/gologme/log" "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" "github.com/yggdrasil-network/yggdrasil-go/src/version" @@ -54,7 +53,7 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(In } // init runs the initial admin setup. -func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) { +func (a *AdminSocket) Init(c *yggdrasil.Core, log *log.Logger, options interface{}) { a.core = c a.log = log a.reconfigure = make(chan chan error, 1) @@ -62,17 +61,15 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. go func() { for { e := <-a.reconfigure - current, previous := state.GetCurrent(), state.GetPrevious() - if current.AdminListen != previous.AdminListen { - a.listenaddr = current.AdminListen + if newlistenaddr := c.GetConfig().AdminListen; newlistenaddr != a.listenaddr { + a.listenaddr = newlistenaddr a.Stop() a.Start() } e <- nil } }() - current := state.GetCurrent() - a.listenaddr = current.AdminListen + a.listenaddr = c.GetConfig().AdminListen a.AddHandler("list", []string{}, func(in Info) (Info, error) { handlers := make(map[string]interface{}) for handlername, handler := range a.handlers { @@ -314,6 +311,9 @@ func (a *AdminSocket) Init(c *yggdrasil.Core, state *config.NodeState, log *log. // start runs the admin API socket to listen for / respond to admin API calls. func (a *AdminSocket) Start() error { + if a.core == nil { + return errors.New("admin socket has not been initialised, call Init first") + } if a.listenaddr != "none" && a.listenaddr != "" { go a.listen() } diff --git a/src/config/admin.go b/src/config/admin.go new file mode 100644 index 00000000..c7da322e --- /dev/null +++ b/src/config/admin.go @@ -0,0 +1,5 @@ +package config + +type AdminConfig struct { + AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."` +} diff --git a/src/config/config.go b/src/config/config.go index 6c127552..12f61d35 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -2,61 +2,29 @@ package config import ( "encoding/hex" - "sync" + "encoding/json" + hjson "github.com/hjson/hjson-go" + "github.com/mitchellh/mapstructure" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) -// NodeState represents the active and previous configuration of the node and -// protects it with a mutex -type NodeState struct { - Current NodeConfig - Previous NodeConfig - Mutex sync.RWMutex -} - -// Current returns the current node config -func (s *NodeState) GetCurrent() NodeConfig { - s.Mutex.RLock() - defer s.Mutex.RUnlock() - return s.Current -} - -// Previous returns the previous node config -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 -func (s *NodeState) Replace(n NodeConfig) { - s.Mutex.Lock() - defer s.Mutex.Unlock() - s.Previous = s.Current - s.Current = n -} - // NodeConfig defines all configuration values needed to run a signle yggdrasil 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."` - Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntcp://0.0.0.0:0 or tcp://[::]:0 to listen on all interfaces."` - AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."` - MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` - AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow incoming TCP peering\nconnections from. If left empty/undefined then all connections will\nbe allowed by default. This does not affect outgoing peerings, nor\ndoes it affect link-local peers discovered via multicast."` - EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` - EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"` - SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` - SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` - LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` - IfName string `comment:"Local network interface name for TUN/TAP adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN/TAP."` - IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."` - IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` + 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."` + Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntcp://0.0.0.0:0 or tcp://[::]:0 to listen on all interfaces."` + AdminConfig `` + MulticastConfig `` + AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow incoming TCP peering\nconnections from. If left empty/undefined then all connections will\nbe allowed by default. This does not affect outgoing peerings, nor\ndoes it affect link-local peers discovered via multicast."` + EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` + EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"` + SigningPublicKey string `comment:"Your public signing key. You should not ordinarily need to share\nthis with anyone."` + SigningPrivateKey string `comment:"Your private signing key. DO NOT share this with anyone!"` + LinkLocalTCPPort uint16 `comment:"The port number to be used for the link-local TCP listeners for the\nconfigured MulticastInterfaces. This option does not affect listeners\nspecified in the Listen option. Unless you plan to firewall link-local\ntraffic, it is best to leave this as the default value of 0. This\noption cannot currently be changed by reloading config during runtime."` + TunTapConfig `` SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` - TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` SwitchOptions SwitchOptions `comment:"Advanced options for tuning the switch. Normally you will not need\nto edit these options."` NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` 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."` @@ -72,15 +40,6 @@ 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 -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\", ... }"` - IPv6LocalSubnets []string `comment:"IPv6 subnets belonging to this node's end of the tunnels. Only traffic\nfrom these ranges (or the Yggdrasil node's IPv6 address/subnet)\nwill be tunnelled."` - IPv4RemoteSubnets map[string]string `comment:"IPv4 subnets belonging to remote nodes, mapped to the node's public\nkey, e.g. { \"a.b.c.d/e\": \"boxpubkey\", ... }"` - 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 type SwitchOptions struct { MaxTotalQueueSize uint64 `comment:"Maximum size of all switch queues combined (in bytes)."` @@ -137,3 +96,83 @@ func (cfg *NodeConfig) NewSigningKeys() { cfg.SigningPublicKey = hex.EncodeToString(spub[:]) cfg.SigningPrivateKey = hex.EncodeToString(spriv[:]) } + +// MarshalJSON exports the configuration into JSON format. No comments are +// included in the JSON export as comments are not valid in pure JSON. +func (cfg *NodeConfig) MarshalJSON() ([]byte, error) { + bs, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return nil, err + } + return bs, nil +} + +// MarshalHJSON exports the configuration into HJSON format, complete with +// comments describing what each configuration item does. +func (cfg *NodeConfig) MarshalHJSON() ([]byte, error) { + bs, err := hjson.Marshal(cfg) + if err != nil { + return nil, err + } + return bs, nil +} + +// UnmarshalJSON parses the configuration in pure JSON format and updates the +// NodeConfig accordingly. The input JSON can be partial - only supplied fields +// will be updated. +func (cfg *NodeConfig) UnmarshalJSON(conf []byte) error { + var dat map[string]interface{} + if err := json.Unmarshal(conf, &dat); err != nil { + return err + } + return cfg.decodeConfig(dat) +} + +// UnmarshalHJSON parses the configuration in HJSON format and updates the +// NodeConfig accordingly. The input HJSON can be partial - only supplied fields +// will be updated. +func (cfg *NodeConfig) UnmarshalHJSON(conf []byte) error { + var dat map[string]interface{} + if err := hjson.Unmarshal(conf, &dat); err != nil { + return err + } + return cfg.decodeConfig(dat) +} + +func (cfg *NodeConfig) decodeConfig(dat map[string]interface{}) error { + // Check for fields that have changed type recently, e.g. the Listen config + // option is now a []string rather than a string + if listen, ok := dat["Listen"].(string); ok { + dat["Listen"] = []string{listen} + } + if tunnelrouting, ok := dat["TunnelRouting"].(map[string]interface{}); ok { + if c, ok := tunnelrouting["IPv4Sources"]; ok { + delete(tunnelrouting, "IPv4Sources") + tunnelrouting["IPv4LocalSubnets"] = c + } + if c, ok := tunnelrouting["IPv6Sources"]; ok { + delete(tunnelrouting, "IPv6Sources") + tunnelrouting["IPv6LocalSubnets"] = c + } + if c, ok := tunnelrouting["IPv4Destinations"]; ok { + delete(tunnelrouting, "IPv4Destinations") + tunnelrouting["IPv4RemoteSubnets"] = c + } + if c, ok := tunnelrouting["IPv6Destinations"]; ok { + delete(tunnelrouting, "IPv6Destinations") + tunnelrouting["IPv6RemoteSubnets"] = c + } + } + // Sanitise the config + /*confJson, err := json.Marshal(dat) + if err != nil { + return err + } + json.Unmarshal(confJson, &cfg)*/ + // Overlay our newly mapped configuration onto the autoconf node config that + // we generated above. + if err := mapstructure.Decode(dat, &cfg); err != nil { + return err + } + return nil +} diff --git a/src/config/multicast.go b/src/config/multicast.go new file mode 100644 index 00000000..67063088 --- /dev/null +++ b/src/config/multicast.go @@ -0,0 +1,5 @@ +package config + +type MulticastConfig struct { + MulticastInterfaces []string `comment:"Regular expressions for which interfaces multicast peer discovery\nshould be enabled on. If none specified, multicast peer discovery is\ndisabled. The default value is .* which uses all interfaces."` +} diff --git a/src/config/tuntap.go b/src/config/tuntap.go new file mode 100644 index 00000000..ecf8666f --- /dev/null +++ b/src/config/tuntap.go @@ -0,0 +1,17 @@ +package config + +type TunTapConfig struct { + IfName string `comment:"Local network interface name for TUN/TAP adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN/TAP."` + IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."` + IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` + TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` +} + +// TunnelRouting contains the crypto-key routing tables for tunneling +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\", ... }"` + IPv6LocalSubnets []string `comment:"IPv6 subnets belonging to this node's end of the tunnels. Only traffic\nfrom these ranges (or the Yggdrasil node's IPv6 address/subnet)\nwill be tunnelled."` + IPv4RemoteSubnets map[string]string `comment:"IPv4 subnets belonging to remote nodes, mapped to the node's public\nkey, e.g. { \"a.b.c.d/e\": \"boxpubkey\", ... }"` + IPv4LocalSubnets []string `comment:"IPv4 subnets belonging to this node's end of the tunnels. Only traffic\nfrom these ranges will be tunnelled."` +} diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index 22ac9356..36cd71f1 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -2,6 +2,7 @@ package multicast import ( "context" + "errors" "fmt" "net" "regexp" @@ -9,7 +10,6 @@ import ( "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" "golang.org/x/net/ipv6" ) @@ -20,7 +20,6 @@ import ( // automatically. type Multicast struct { core *yggdrasil.Core - config *config.NodeState log *log.Logger sock *ipv6.PacketConn groupAddr string @@ -30,13 +29,11 @@ type Multicast struct { } // Init prepares the multicast interface for use. -func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log.Logger, options interface{}) error { +func (m *Multicast) Init(core *yggdrasil.Core, log *log.Logger, options interface{}) error { m.core = core - m.config = state m.log = log m.listeners = make(map[string]*yggdrasil.TcpListener) - current := m.config.GetCurrent() - m.listenPort = current.LinkLocalTCPPort + m.listenPort = m.core.GetConfig().LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" return nil } @@ -45,6 +42,9 @@ func (m *Multicast) Init(core *yggdrasil.Core, state *config.NodeState, log *log // listen for multicast beacons from other hosts and will advertise multicast // beacons out to the network. func (m *Multicast) Start() error { + if m.core == nil { + return errors.New("multicast has not been initialised, call Init first") + } addr, err := net.ResolveUDPAddr("udp", m.groupAddr) if err != nil { return err @@ -80,9 +80,8 @@ func (m *Multicast) Stop() error { // UpdateConfig updates the multicast module with the provided config.NodeConfig // and then signals the various module goroutines to reconfigure themselves if // needed. -func (m *Multicast) UpdateConfig(config *config.NodeConfig) { - m.log.Debugln("Reloading multicast configuration...") - m.config.Replace(*config) +func (m *Multicast) UpdateConfig() { + } // GetInterfaces returns the currently known/enabled multicast interfaces. It is @@ -91,7 +90,7 @@ func (m *Multicast) UpdateConfig(config *config.NodeConfig) { func (m *Multicast) Interfaces() map[string]net.Interface { interfaces := make(map[string]net.Interface) // Get interface expressions from config - current := m.config.GetCurrent() + current := m.core.GetConfig() exprs := current.MulticastInterfaces // Ask the system for network interfaces allifaces, err := net.Interfaces() diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index 9af3564a..9018c099 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -44,7 +44,7 @@ func (c *cryptokey) init(tun *TunAdapter) { // Configure the CKR routes. This should only ever be ran by the TUN/TAP actor. func (c *cryptokey) configure() { - current := c.tun.config.GetCurrent() + current := c.tun.core.GetConfig() // Set enabled/disabled state c.setEnabled(current.TunnelRouting.Enable) diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 74d055ee..afb8439c 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -21,7 +21,6 @@ import ( "github.com/yggdrasil-network/water" "github.com/yggdrasil-network/yggdrasil-go/src/address" - "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/yggdrasil" @@ -35,9 +34,9 @@ const tun_ETHER_HEADER_LENGTH = 14 // you should pass this object to the yggdrasil.SetRouterAdapter() function // before calling yggdrasil.Start(). type TunAdapter struct { + core *yggdrasil.Core writer tunWriter reader tunReader - config *config.NodeState log *log.Logger reconfigure chan chan error listener *yggdrasil.Listener @@ -110,8 +109,8 @@ func MaximumMTU() int { // Init initialises the TUN/TAP module. You must have acquired a Listener from // the Yggdrasil core before this point and it must not be in use elsewhere. -func (tun *TunAdapter) Init(config *config.NodeState, log *log.Logger, listener *yggdrasil.Listener, dialer *yggdrasil.Dialer) { - tun.config = config +func (tun *TunAdapter) Init(c *yggdrasil.Core, log *log.Logger, listener *yggdrasil.Listener, dialer *yggdrasil.Dialer) { + tun.core = c tun.log = log tun.listener = listener tun.dialer = dialer @@ -133,9 +132,9 @@ func (tun *TunAdapter) Start() error { } func (tun *TunAdapter) _start() error { - current := tun.config.GetCurrent() - if tun.config == nil || tun.listener == nil || tun.dialer == nil { - return errors.New("No configuration available to TUN/TAP") + current := tun.core.GetConfig() + if tun.core == nil || tun.listener == nil || tun.dialer == nil { + return errors.New("TUN/TAP has not been initialised, call Init first") } var boxPub crypto.BoxPubKey boxPubHex, err := hex.DecodeString(current.EncryptionPublicKey) @@ -200,12 +199,9 @@ func (tun *TunAdapter) _stop() error { // UpdateConfig updates the TUN/TAP module with the provided config.NodeConfig // and then signals the various module goroutines to reconfigure themselves if // needed. -func (tun *TunAdapter) UpdateConfig(config *config.NodeConfig) { +func (tun *TunAdapter) UpdateConfig() { tun.log.Debugln("Reloading TUN/TAP configuration...") - // Replace the active configuration with the supplied one - tun.config.Replace(*config) - // Notify children about the configuration change tun.Act(nil, tun.ckr.configure) } diff --git a/src/yggdrasil/api.go b/src/yggdrasil/api.go index d1753b69..e1911e65 100644 --- a/src/yggdrasil/api.go +++ b/src/yggdrasil/api.go @@ -370,13 +370,12 @@ func (c *Core) AddPeer(addr string, sintf string) error { if err := c.CallPeer(addr, sintf); err != nil { return err } - c.config.Mutex.Lock() + config := c.GetConfig() if sintf == "" { - c.config.Current.Peers = append(c.config.Current.Peers, addr) + config.Peers = append(config.Peers, addr) } else { - c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr) + config.InterfacePeers[sintf] = append(config.InterfacePeers[sintf], addr) } - c.config.Mutex.Unlock() return nil } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 754d7d64..3f216eba 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "errors" "io/ioutil" + "sync/atomic" "time" "github.com/Arceliar/phony" @@ -21,7 +22,7 @@ type Core struct { // We're going to keep our own copy of the provided config - that way we can // guarantee that it will be covered by the mutex phony.Inbox - config config.NodeState // Config + config atomic.Value // *config.NodeConfig boxPub crypto.BoxPubKey boxPriv crypto.BoxPrivKey sigPub crypto.SigPubKey @@ -42,7 +43,7 @@ func (c *Core) _init() error { c.log = log.New(ioutil.Discard, "", 0) } - current := c.config.GetCurrent() + current := c.config.Load().(*config.NodeConfig) boxPrivHex, err := hex.DecodeString(current.EncryptionPrivateKey) if err != nil { @@ -87,7 +88,7 @@ func (c *Core) _init() error { // be reconnected with. func (c *Core) _addPeerLoop() { // Get the peers from the config - these could change! - current := c.config.GetCurrent() + current := c.GetConfig() // Add peers from the Peers section for _, peer := range current.Peers { @@ -109,6 +110,11 @@ func (c *Core) _addPeerLoop() { }) } +// GetConfig atomically returns the current active node configuration. +func (c *Core) GetConfig() *config.NodeConfig { + return c.config.Load().(*config.NodeConfig) +} + // UpdateConfig updates the configuration in Core with the provided // config.NodeConfig and then signals the various module goroutines to // reconfigure themselves if needed. @@ -116,12 +122,16 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { c.Act(nil, func() { c.log.Debugln("Reloading node configuration...") - // Replace the active configuration with the supplied one - c.config.Replace(*config) + new, old := config, c.GetConfig() + c.config.Store(new) // Notify the router and switch about the new configuration - c.router.Act(c, c.router.reconfigure) - c.switchTable.Act(c, c.switchTable.reconfigure) + c.router.Act(c, func() { + c.router.reconfigure(new, old) + }) + c.switchTable.Act(c, func() { + c.switchTable.reconfigure(new, old) + }) }) } @@ -130,21 +140,17 @@ func (c *Core) UpdateConfig(config *config.NodeConfig) { // TCP and UDP sockets, a multicast discovery socket, an admin socket, router, // switch and DHT node. A config.NodeState is returned which contains both the // current and previous configurations (from reconfigures). -func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (conf *config.NodeState, err error) { +func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (err error) { phony.Block(c, func() { - conf, err = c._start(nc, log) + err = c._start(nc, log) }) return } // This function is unsafe and should only be ran by the core actor. -func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState, error) { +func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) error { c.log = log - - c.config = config.NodeState{ - Current: *nc, - Previous: *nc, - } + c.config.Store(nc) if name := version.BuildName(); name != "unknown" { c.log.Infoln("Build name:", name) @@ -158,23 +164,23 @@ func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState if err := c.link.init(c); err != nil { c.log.Errorln("Failed to start link interfaces") - return nil, err + return err } if err := c.switchTable.start(); err != nil { c.log.Errorln("Failed to start switch") - return nil, err + return err } if err := c.router.start(); err != nil { c.log.Errorln("Failed to start router") - return nil, err + return err } c.Act(c, c._addPeerLoop) c.log.Infoln("Startup complete") - return &c.config, nil + return nil } // Stop shuts down the Yggdrasil node. diff --git a/src/yggdrasil/link.go b/src/yggdrasil/link.go index 6e393514..4ee579dd 100644 --- a/src/yggdrasil/link.go +++ b/src/yggdrasil/link.go @@ -14,6 +14,7 @@ import ( "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" @@ -79,8 +80,8 @@ func (l *link) init(c *Core) error { return nil } -func (l *link) reconfigure() { - l.tcp.reconfigure() +func (l *link) reconfigure(current, previous *config.NodeConfig) { + l.tcp.reconfigure(current, previous) } func (l *link) call(uri string, sintf string) error { diff --git a/src/yggdrasil/peer.go b/src/yggdrasil/peer.go index 381e6917..70ff2ea6 100644 --- a/src/yggdrasil/peer.go +++ b/src/yggdrasil/peer.go @@ -10,6 +10,7 @@ import ( "sync/atomic" "time" + "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" @@ -34,7 +35,7 @@ func (ps *peers) init(c *Core) { ps.core = c } -func (ps *peers) reconfigure() { +func (ps *peers) reconfigure(current, previous *config.NodeConfig) { // This is where reconfiguration would go, if we had anything to do } @@ -42,42 +43,34 @@ func (ps *peers) reconfigure() { // because the key is in the whitelist or because the whitelist is empty. func (ps *peers) isAllowedEncryptionPublicKey(box *crypto.BoxPubKey) bool { boxstr := hex.EncodeToString(box[:]) - ps.core.config.Mutex.RLock() - defer ps.core.config.Mutex.RUnlock() - for _, v := range ps.core.config.Current.AllowedEncryptionPublicKeys { + current := ps.core.GetConfig() + for _, v := range current.AllowedEncryptionPublicKeys { if v == boxstr { return true } } - return len(ps.core.config.Current.AllowedEncryptionPublicKeys) == 0 + return len(current.AllowedEncryptionPublicKeys) == 0 } // Adds a key to the whitelist. func (ps *peers) addAllowedEncryptionPublicKey(box string) { - ps.core.config.Mutex.RLock() - defer ps.core.config.Mutex.RUnlock() - ps.core.config.Current.AllowedEncryptionPublicKeys = - append(ps.core.config.Current.AllowedEncryptionPublicKeys, box) + current := ps.core.GetConfig() + current.AllowedEncryptionPublicKeys = append(current.AllowedEncryptionPublicKeys, box) } // Removes a key from the whitelist. func (ps *peers) removeAllowedEncryptionPublicKey(box string) { - ps.core.config.Mutex.RLock() - defer ps.core.config.Mutex.RUnlock() - for k, v := range ps.core.config.Current.AllowedEncryptionPublicKeys { + current := ps.core.GetConfig() + for k, v := range current.AllowedEncryptionPublicKeys { if v == box { - ps.core.config.Current.AllowedEncryptionPublicKeys = - append(ps.core.config.Current.AllowedEncryptionPublicKeys[:k], - ps.core.config.Current.AllowedEncryptionPublicKeys[k+1:]...) + current.AllowedEncryptionPublicKeys = append(current.AllowedEncryptionPublicKeys[:k], current.AllowedEncryptionPublicKeys[k+1:]...) } } } // Gets the whitelist of allowed keys for incoming connections. func (ps *peers) getAllowedEncryptionPublicKeys() []string { - ps.core.config.Mutex.RLock() - defer ps.core.config.Mutex.RUnlock() - return ps.core.config.Current.AllowedEncryptionPublicKeys + return ps.core.GetConfig().AllowedEncryptionPublicKeys } // Atomically gets a map[switchPort]*peer of known peers. diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 64c81701..ed080c01 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -28,6 +28,7 @@ import ( "time" "github.com/yggdrasil-network/yggdrasil-go/src/address" + "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" @@ -65,9 +66,8 @@ func (r *router) init(core *Core) { p.out = func(packets [][]byte) { r.handlePackets(p, packets) } r.out = func(bs []byte) { p.handlePacketFrom(r, bs) } r.nodeinfo.init(r.core) - r.core.config.Mutex.RLock() - r.nodeinfo.setNodeInfo(r.core.config.Current.NodeInfo, r.core.config.Current.NodeInfoPrivacy) - r.core.config.Mutex.RUnlock() + current := r.core.GetConfig() + r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy) r.dht.init(r) r.searches.init(r) r.sessions.init(r) @@ -75,9 +75,8 @@ func (r *router) init(core *Core) { // Reconfigures the router and any child modules. This should only ever be run // by the router actor. -func (r *router) reconfigure() { +func (r *router) reconfigure(current, previous *config.NodeConfig) { // Reconfigure the router - current := r.core.config.GetCurrent() if err := r.nodeinfo.setNodeInfo(current.NodeInfo, current.NodeInfoPrivacy); err != nil { r.core.log.Errorln("Error reloading NodeInfo:", err) } else { diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 8a6d16fc..9e20ca74 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -187,9 +187,7 @@ func (ss *sessions) createSession(theirPermKey *crypto.BoxPubKey) *sessionInfo { sinfo.mySesPriv = *priv sinfo.myNonce = *crypto.NewBoxNonce() sinfo.theirMTU = 1280 - ss.router.core.config.Mutex.RLock() - sinfo.myMTU = uint16(ss.router.core.config.Current.IfMTU) - ss.router.core.config.Mutex.RUnlock() + sinfo.myMTU = uint16(ss.router.core.GetConfig().IfMTU) now := time.Now() sinfo.timeOpened = now sinfo.time = now diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 163c85cf..e01147a2 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -17,6 +17,7 @@ import ( "sync/atomic" "time" + "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/util" @@ -193,22 +194,21 @@ func (t *switchTable) init(core *Core) { t.table.Store(lookupTable{}) t.drop = make(map[crypto.SigPubKey]int64) phony.Block(t, func() { - core.config.Mutex.RLock() - if core.config.Current.SwitchOptions.MaxTotalQueueSize > SwitchQueueTotalMinSize { - t.queues.totalMaxSize = core.config.Current.SwitchOptions.MaxTotalQueueSize + current := t.core.GetConfig() + if current.SwitchOptions.MaxTotalQueueSize > SwitchQueueTotalMinSize { + t.queues.totalMaxSize = current.SwitchOptions.MaxTotalQueueSize } else { t.queues.totalMaxSize = SwitchQueueTotalMinSize } - core.config.Mutex.RUnlock() t.queues.bufs = make(map[string]switch_buffer) t.idle = make(map[switchPort]time.Time) }) } -func (t *switchTable) reconfigure() { +func (t *switchTable) reconfigure(current, previous *config.NodeConfig) { // This is where reconfiguration would go, if we had anything useful to do. - t.core.link.reconfigure() - t.core.peers.reconfigure() + t.core.link.reconfigure(current, previous) + t.core.peers.reconfigure(current, previous) } // Safely gets a copy of this node's locator. diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index cce352bd..d2feb3e0 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -25,6 +25,7 @@ import ( "golang.org/x/net/proxy" + "github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/util" ) @@ -81,9 +82,7 @@ func (t *tcp) init(l *link) error { t.listeners = make(map[string]*TcpListener) t.mutex.Unlock() - t.link.core.config.Mutex.RLock() - defer t.link.core.config.Mutex.RUnlock() - for _, listenaddr := range t.link.core.config.Current.Listen { + for _, listenaddr := range t.link.core.GetConfig().Listen { if listenaddr[:6] != "tcp://" { continue } @@ -95,11 +94,9 @@ func (t *tcp) init(l *link) error { return nil } -func (t *tcp) reconfigure() { - t.link.core.config.Mutex.RLock() - added := util.Difference(t.link.core.config.Current.Listen, t.link.core.config.Previous.Listen) - deleted := util.Difference(t.link.core.config.Previous.Listen, t.link.core.config.Current.Listen) - t.link.core.config.Mutex.RUnlock() +func (t *tcp) reconfigure(current, previous *config.NodeConfig) { + added := util.Difference(current.Listen, previous.Listen) + deleted := util.Difference(previous.Listen, current.Listen) if len(added) > 0 || len(deleted) > 0 { for _, a := range added { if a[:6] != "tcp://" {