diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index e0d79fc4..6484edf9 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -35,7 +35,7 @@ import ( type node struct { core core.Core - state *config.NodeState + config *config.NodeConfig tuntap module.Module // tuntap.TunAdapter multicast module.Module // multicast.Multicast admin module.Module // admin.AdminSocket @@ -272,8 +272,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) - if err != nil { + if err = n.core.Start(cfg, logger); err != nil { logger.Errorln("An error occurred during startup") panic(err) } @@ -284,19 +283,19 @@ func main() { n.tuntap = &tuntap.TunAdapter{} n.tuntap.(*tuntap.TunAdapter).SetSessionGatekeeper(n.sessionFirewall) // Start the admin socket - n.admin.Init(&n.core, n.state, logger, nil) + n.admin.Init(&n.core, cfg, logger, nil) if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } n.admin.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) // Start the multicast interface - n.multicast.Init(&n.core, n.state, logger, nil) + n.multicast.Init(&n.core, cfg, logger, nil) if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } n.multicast.SetupAdminHandlers(n.admin.(*admin.AdminSocket)) // Start the TUN/TAP interface - n.tuntap.Init(&n.core, n.state, logger, nil) + n.tuntap.Init(&n.core, cfg, logger, nil) if err := n.tuntap.Start(); err != nil { logger.Errorln("An error occurred starting TUN/TAP:", err) } @@ -324,16 +323,16 @@ func (n *node) shutdown() { } func (n *node) sessionFirewall(pubkey ed25519.PublicKey, initiator bool) bool { - n.state.Mutex.RLock() - defer n.state.Mutex.RUnlock() + n.config.RLock() + defer n.config.RUnlock() // Allow by default if the session firewall is disabled - if !n.state.Current.SessionFirewall.Enable { + if !n.config.SessionFirewall.Enable { return true } // Reject blacklisted nodes - for _, b := range n.state.Current.SessionFirewall.BlacklistPublicKeys { + for _, b := range n.config.SessionFirewall.BlacklistPublicKeys { key, err := hex.DecodeString(b) if err == nil { if bytes.Equal(key, pubkey) { @@ -343,7 +342,7 @@ func (n *node) sessionFirewall(pubkey ed25519.PublicKey, initiator bool) bool { } // Allow whitelisted nodes - for _, b := range n.state.Current.SessionFirewall.WhitelistPublicKeys { + for _, b := range n.config.SessionFirewall.WhitelistPublicKeys { key, err := hex.DecodeString(b) if err == nil { if bytes.Equal(key, pubkey) { @@ -353,7 +352,7 @@ func (n *node) sessionFirewall(pubkey ed25519.PublicKey, initiator bool) bool { } // Allow outbound sessions if appropriate - if n.state.Current.SessionFirewall.AlwaysAllowOutbound { + if n.config.SessionFirewall.AlwaysAllowOutbound { if initiator { return true } @@ -369,12 +368,12 @@ func (n *node) sessionFirewall(pubkey ed25519.PublicKey, initiator bool) bool { } // Allow direct peers if appropriate - if n.state.Current.SessionFirewall.AllowFromDirect && isDirectPeer { + if n.config.SessionFirewall.AllowFromDirect && isDirectPeer { return true } // Allow remote nodes if appropriate - if n.state.Current.SessionFirewall.AllowFromRemote && !isDirectPeer { + if n.config.SessionFirewall.AllowFromRemote && !isDirectPeer { return true } diff --git a/src/admin/admin.go b/src/admin/admin.go index 8322b06f..7d5c66d1 100644 --- a/src/admin/admin.go +++ b/src/admin/admin.go @@ -63,12 +63,13 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(js } // Init runs the initial admin setup. -func (a *AdminSocket) Init(c *core.Core, state *config.NodeState, log *log.Logger, options interface{}) error { +func (a *AdminSocket) Init(c *core.Core, nc *config.NodeConfig, log *log.Logger, options interface{}) error { a.core = c a.log = log a.handlers = make(map[string]handler) - current := state.GetCurrent() - a.listenaddr = current.AdminListen + nc.RLock() + a.listenaddr = nc.AdminListen + nc.RUnlock() a.done = make(chan struct{}) close(a.done) // Start in a done / not-started state _ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) { diff --git a/src/config/config.go b/src/config/config.go index 073e1597..af47face 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -24,41 +24,11 @@ import ( "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) -// 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 active node configuration. -func (s *NodeState) GetCurrent() NodeConfig { - s.Mutex.RLock() - defer s.Mutex.RUnlock() - return s.Current -} - -// 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. -func (s *NodeState) Replace(n NodeConfig) { - s.Mutex.Lock() - defer s.Mutex.Unlock() - s.Previous = s.Current - s.Current = n -} - // 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 { + sync.RWMutex 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."` diff --git a/src/core/core.go b/src/core/core.go index f3942cdb..b98776b9 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -25,7 +25,7 @@ type Core struct { // guarantee that it will be covered by the mutex phony.Inbox *iw.PacketConn - config config.NodeState // Config + config *config.NodeConfig // Config secret ed25519.PrivateKey public ed25519.PublicKey links links @@ -42,9 +42,9 @@ func (c *Core) _init() error { c.log = log.New(ioutil.Discard, "", 0) } - current := c.config.GetCurrent() - - sigPriv, err := hex.DecodeString(current.PrivateKey) + c.config.RLock() + sigPriv, err := hex.DecodeString(c.config.PrivateKey) + c.config.RUnlock() if err != nil { return err } @@ -64,11 +64,11 @@ func (c *Core) _init() error { // configure them. The loop ensures that disconnected peers will eventually // be reconnected with. func (c *Core) _addPeerLoop() { - // Get the peers from the config - these could change! - current := c.config.GetCurrent() + c.config.RLock() + defer c.config.RUnlock() // Add peers from the Peers section - for _, peer := range current.Peers { + for _, peer := range c.config.Peers { go func(peer string, intf string) { u, err := url.Parse(peer) if err != nil { @@ -81,7 +81,7 @@ func (c *Core) _addPeerLoop() { } // Add peers from the InterfacePeers section - for intf, intfpeers := range current.InterfacePeers { + for intf, intfpeers := range c.config.InterfacePeers { for _, peer := range intfpeers { go func(peer string, intf string) { u, err := url.Parse(peer) @@ -107,21 +107,17 @@ func (c *Core) _addPeerLoop() { // 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 = nc if name := version.BuildName(); name != "unknown" { c.log.Infoln("Build name:", name) @@ -133,30 +129,20 @@ func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) (*config.NodeState c.log.Infoln("Starting up...") if err := c._init(); err != nil { c.log.Errorln("Failed to initialize core") - return nil, err + return err } if err := c.links.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 - //} - - //if err := c.router.start(); err != nil { - // c.log.Errorln("Failed to start router") - // return nil, err - //} - c.addPeerTimer = time.AfterFunc(0, func() { c.Act(nil, c._addPeerLoop) }) c.log.Infoln("Startup complete") - return &c.config, nil + return nil } // Stop shuts down the Yggdrasil node. diff --git a/src/core/core_test.go b/src/core/core_test.go index 74012333..7e7f32d1 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -40,14 +40,12 @@ func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger { // Verbosity flag is passed to logger. func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) { nodeA = new(Core) - _, err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ", verbose)) - if err != nil { + if err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ", verbose)); err != nil { t.Fatal(err) } nodeB = new(Core) - _, err = nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose)) - if err != nil { + if err := nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose)); err != nil { t.Fatal(err) } diff --git a/src/core/link.go b/src/core/link.go index 80bd0c30..b4cd15ff 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -188,7 +188,9 @@ func (intf *link) handler() (chan struct{}, error) { } } // Check if we're authorized to connect to this key / IP - allowed := intf.links.core.config.GetCurrent().AllowedPublicKeys + intf.links.core.config.RLock() + allowed := intf.links.core.config.AllowedPublicKeys + intf.links.core.config.RUnlock() isallowed := len(allowed) == 0 for _, k := range allowed { if k == hex.EncodeToString(meta.key) { // TODO: this is yuck diff --git a/src/core/tcp.go b/src/core/tcp.go index eced7726..17e37660 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -104,9 +104,9 @@ func (t *tcp) init(l *links) error { t.listeners = make(map[string]*TcpListener) t.mutex.Unlock() - t.links.core.config.Mutex.RLock() - defer t.links.core.config.Mutex.RUnlock() - for _, listenaddr := range t.links.core.config.Current.Listen { + t.links.core.config.RLock() + defer t.links.core.config.RUnlock() + for _, listenaddr := range t.links.core.config.Listen { u, err := url.Parse(listenaddr) if err != nil { t.links.core.log.Errorln("Failed to parse listener: listener", listenaddr, "is not correctly formatted, ignoring") diff --git a/src/module/module.go b/src/module/module.go index d13b8cd2..8f915005 100644 --- a/src/module/module.go +++ b/src/module/module.go @@ -11,7 +11,7 @@ import ( // Module is an interface that defines which functions must be supported by a // given Yggdrasil module. type Module interface { - Init(core *core.Core, state *config.NodeState, log *log.Logger, options interface{}) error + Init(core *core.Core, state *config.NodeConfig, log *log.Logger, options interface{}) error Start() error Stop() error SetupAdminHandlers(a *admin.AdminSocket) diff --git a/src/multicast/multicast.go b/src/multicast/multicast.go index c1b87890..02ff0c44 100644 --- a/src/multicast/multicast.go +++ b/src/multicast/multicast.go @@ -23,7 +23,7 @@ import ( type Multicast struct { phony.Inbox core *core.Core - config *config.NodeState + config *config.NodeConfig log *log.Logger sock *ipv6.PacketConn groupAddr string @@ -45,14 +45,13 @@ type listenerInfo struct { } // Init prepares the multicast interface for use. -func (m *Multicast) Init(core *core.Core, state *config.NodeState, log *log.Logger, options interface{}) error { +func (m *Multicast) Init(core *core.Core, nc *config.NodeConfig, log *log.Logger, options interface{}) error { m.core = core - m.config = state + m.config = nc m.log = log m.listeners = make(map[string]*listenerInfo) m._interfaces = make(map[string]interfaceInfo) - current := m.config.GetCurrent() - m.listenPort = current.LinkLocalTCPPort + m.listenPort = m.config.LinkLocalTCPPort m.groupAddr = "[ff02::114]:9001" return nil } @@ -73,7 +72,9 @@ func (m *Multicast) _start() error { if m.isOpen { return fmt.Errorf("multicast module is already started") } - if len(m.config.GetCurrent().MulticastInterfaces) == 0 { + m.config.RLock() + defer m.config.RUnlock() + if len(m.config.MulticastInterfaces) == 0 { return nil } m.log.Infoln("Starting multicast module") @@ -161,8 +162,7 @@ func (m *Multicast) Interfaces() map[string]net.Interface { func (m *Multicast) getAllowedInterfaces() map[string]net.Interface { interfaces := make(map[string]net.Interface) // Get interface expressions from config - current := m.config.GetCurrent() - exprs := current.MulticastInterfaces + exprs := m.config.MulticastInterfaces // Ask the system for network interfaces allifaces, err := net.Interfaces() if err != nil { diff --git a/src/tuntap/tun.go b/src/tuntap/tun.go index 49d8e426..ecc7c06c 100644 --- a/src/tuntap/tun.go +++ b/src/tuntap/tun.go @@ -35,7 +35,7 @@ type MTU uint16 type TunAdapter struct { core *core.Core store keyStore - config *config.NodeState + config *config.NodeConfig log *log.Logger addr address.Address subnet address.Subnet @@ -103,13 +103,17 @@ func MaximumMTU() uint64 { // Init initialises the TUN 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(core *core.Core, config *config.NodeState, log *log.Logger, options interface{}) error { +func (tun *TunAdapter) Init(core *core.Core, config *config.NodeConfig, log *log.Logger, options interface{}) error { tun.core = core tun.store.init(tun) tun.config = config tun.log = log tun.proto.init(tun) - tun.proto.nodeinfo.setNodeInfo(config.Current.NodeInfo, config.Current.NodeInfoPrivacy) + tun.config.RLock() + if err := tun.proto.nodeinfo.setNodeInfo(tun.config.NodeInfo, tun.config.NodeInfoPrivacy); err != nil { + return fmt.Errorf("tun.proto.nodeinfo.setNodeInfo: %w", err) + } + tun.config.RUnlock() if err := tun.core.SetOutOfBandHandler(tun.oobHandler); err != nil { return fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) } @@ -130,7 +134,8 @@ func (tun *TunAdapter) _start() error { if tun.isOpen { return errors.New("TUN module is already started") } - current := tun.config.GetCurrent() + tun.config.RLock() + defer tun.config.RUnlock() if tun.config == nil { return errors.New("no configuration available to TUN") } @@ -139,21 +144,21 @@ func (tun *TunAdapter) _start() error { tun.addr = *address.AddrForKey(pk) tun.subnet = *address.SubnetForKey(pk) addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) - if current.IfName == "none" || current.IfName == "dummy" { + if tun.config.IfName == "none" || tun.config.IfName == "dummy" { tun.log.Debugln("Not starting TUN as ifname is none or dummy") tun.isEnabled = false go tun.write() return nil } - mtu := current.IfMTU + mtu := tun.config.IfMTU if tun.maxSessionMTU() < mtu { mtu = tun.maxSessionMTU() } - if err := tun.setup(current.IfName, addr, mtu); err != nil { + if err := tun.setup(tun.config.IfName, addr, mtu); err != nil { return err } if tun.MTU() != mtu { - tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", current.IfMTU, tun.MTU(), MaximumMTU()) + tun.log.Warnf("Warning: Interface MTU %d automatically adjusted to %d (supported range is 1280-%d)", tun.config.IfMTU, tun.MTU(), MaximumMTU()) } tun.isOpen = true tun.isEnabled = true