diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 58b8230..7f75183 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -36,7 +36,7 @@ import ( ) type node struct { - core core.Core + core *core.Core config *config.NodeConfig tuntap *tuntap.TunAdapter multicast *multicast.Multicast @@ -327,11 +327,32 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { // Setup the Yggdrasil node itself. The node{} type includes a Core, so we // don't need to create this manually. + sk, err := hex.DecodeString(cfg.PrivateKey) + if err != nil { + panic(err) + } + options := []core.SetupOption{ + core.IfName(cfg.IfName), + core.IfMTU(cfg.IfMTU), + } + for _, peer := range cfg.Peers { + options = append(options, core.Peer{URI: peer}) + } + for intf, peers := range cfg.InterfacePeers { + for _, peer := range peers { + options = append(options, core.Peer{URI: peer, SourceInterface: intf}) + } + } + for _, allowed := range cfg.AllowedPublicKeys { + k, err := hex.DecodeString(allowed) + if err != nil { + panic(err) + } + options = append(options, core.AllowedPublicKey(k[:])) + } n := node{config: cfg} - // Now start Yggdrasil - this starts the DHT, router, switch and other core - // components needed for Yggdrasil to operate - if err = n.core.Start(cfg, logger); err != nil { - logger.Errorln("An error occurred during startup") + n.core, err = core.New(sk[:], options...) + if err != nil { panic(err) } // Register the session firewall gatekeeper function @@ -340,21 +361,21 @@ func run(args yggArgs, ctx context.Context, done chan struct{}) { n.multicast = &multicast.Multicast{} n.tuntap = &tuntap.TunAdapter{} // Start the admin socket - if err := n.admin.Init(&n.core, cfg, logger, nil); err != nil { + if err := n.admin.Init(n.core, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising admin socket:", err) } else if err := n.admin.Start(); err != nil { logger.Errorln("An error occurred starting admin socket:", err) } n.admin.SetupAdminHandlers(n.admin) // Start the multicast interface - if err := n.multicast.Init(&n.core, cfg, logger, nil); err != nil { + if err := n.multicast.Init(n.core, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising multicast:", err) } else if err := n.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) } n.multicast.SetupAdminHandlers(n.admin) // Start the TUN/TAP interface - rwc := ipv6rwc.NewReadWriteCloser(&n.core) + rwc := ipv6rwc.NewReadWriteCloser(n.core) if err := n.tuntap.Init(rwc, cfg, logger, nil); err != nil { logger.Errorln("An error occurred initialising TUN/TAP:", err) } else if err := n.tuntap.Start(); err != nil { diff --git a/src/core/api.go b/src/core/api.go index fabd743..30e7e0f 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -2,8 +2,6 @@ package core import ( "crypto/ed25519" - "sync/atomic" - "time" //"encoding/hex" "encoding/json" @@ -27,6 +25,7 @@ type Self struct { Coords []uint64 } +/* type Peer struct { Key ed25519.PublicKey Root ed25519.PublicKey @@ -37,6 +36,7 @@ type Peer struct { TXBytes uint64 Uptime time.Duration } +*/ type DHTEntry struct { Key ed25519.PublicKey @@ -62,6 +62,7 @@ func (c *Core) GetSelf() Self { return self } +/* func (c *Core) GetPeers() []Peer { var peers []Peer names := make(map[net.Conn]string) @@ -90,6 +91,7 @@ func (c *Core) GetPeers() []Peer { } return peers } +*/ func (c *Core) GetDHT() []DHTEntry { var dhts []DHTEntry diff --git a/src/core/core.go b/src/core/core.go index 0332980..df3888a 100644 --- a/src/core/core.go +++ b/src/core/core.go @@ -3,12 +3,11 @@ package core import ( "context" "crypto/ed25519" - "encoding/hex" - "errors" "fmt" "io/ioutil" "net" "net/url" + "os" "time" iwe "github.com/Arceliar/ironwood/encrypted" @@ -16,9 +15,9 @@ import ( "github.com/Arceliar/phony" "github.com/gologme/log" - "github.com/yggdrasil-network/yggdrasil-go/src/config" - //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" + "github.com/yggdrasil-network/yggdrasil-go/src/crypto" "github.com/yggdrasil-network/yggdrasil-go/src/version" + //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) // The Core object represents the Yggdrasil node. You should create a Core @@ -29,62 +28,95 @@ type Core struct { // guarantee that it will be covered by the mutex phony.Inbox *iwe.PacketConn - config *config.NodeConfig // Config + ctx context.Context + cancel context.CancelFunc secret ed25519.PrivateKey public ed25519.PublicKey links links proto protoHandler log *log.Logger addPeerTimer *time.Timer - ctx context.Context - ctxCancel context.CancelFunc + config struct { + _peers map[Peer]struct{} // configurable after startup + _listeners map[ListenAddress]struct{} // configurable after startup + nodeinfo NodeInfo // immutable after startup + nodeinfoPrivacy NodeInfoPrivacy // immutable after startup + ifname IfName // immutable after startup + ifmtu IfMTU // immutable after startup + _allowedPublicKeys map[[32]byte]struct{} // configurable after startup + } } -func (c *Core) _init() error { - // TODO separate init and start functions - // Init sets up structs - // Start launches goroutines that depend on structs being set up - // This is pretty much required to completely avoid race conditions - c.config.RLock() - defer c.config.RUnlock() +func New(secret ed25519.PrivateKey, opts ...SetupOption) (*Core, error) { + if len(secret) != ed25519.PrivateKeySize { + return nil, fmt.Errorf("private key is incorrect length") + } + c := &Core{ + secret: secret, + public: secret.Public().(ed25519.PublicKey), + log: log.New(os.Stdout, "", 0), // TODO: not this + } + c.ctx, c.cancel = context.WithCancel(context.Background()) + var err error + if c.PacketConn, err = iwe.NewPacketConn(c.secret); err != nil { + return nil, fmt.Errorf("error creating encryption: %w", err) + } + for _, opt := range opts { + c._applyOption(opt) + } if c.log == nil { c.log = log.New(ioutil.Discard, "", 0) } - - sigPriv, err := hex.DecodeString(c.config.PrivateKey) - if err != nil { - return err - } - if len(sigPriv) < ed25519.PrivateKeySize { - return errors.New("PrivateKey is incorrect length") - } - - c.secret = ed25519.PrivateKey(sigPriv) - c.public = c.secret.Public().(ed25519.PublicKey) - // TODO check public against current.PublicKey, error if they don't match - - c.PacketConn, err = iwe.NewPacketConn(c.secret) - c.ctx, c.ctxCancel = context.WithCancel(context.Background()) c.proto.init(c) - if err := c.proto.nodeinfo.setNodeInfo(c.config.NodeInfo, c.config.NodeInfoPrivacy); err != nil { - return fmt.Errorf("setNodeInfo: %w", err) + if err := c.links.init(c); err != nil { + return nil, fmt.Errorf("error initialising links: %w", err) + } + if err := c.proto.nodeinfo.setNodeInfo(c.config.nodeinfo, bool(c.config.nodeinfoPrivacy)); err != nil { + return nil, fmt.Errorf("error setting node info: %w", err) + } + c.addPeerTimer = time.AfterFunc(time.Minute, func() { + c.Act(nil, c._addPeerLoop) + }) + if name := version.BuildName(); name != "unknown" { + c.log.Infoln("Build name:", name) + } + if version := version.BuildVersion(); version != "unknown" { + c.log.Infoln("Build version:", version) + } + return c, nil +} + +func (c *Core) _applyOption(opt SetupOption) { + switch v := opt.(type) { + case Peer: + c.config._peers[v] = struct{}{} + case ListenAddress: + c.config._listeners[v] = struct{}{} + case NodeInfo: + c.config.nodeinfo = v + case NodeInfoPrivacy: + c.config.nodeinfoPrivacy = v + case IfName: + c.config.ifname = v + case IfMTU: + c.config.ifmtu = v + case AllowedPublicKey: + pk := crypto.SigPubKey{} + copy(pk[:], v) + c.config._allowedPublicKeys[pk] = struct{}{} } - return err } // If any static peers were provided in the configuration above then we should // configure them. The loop ensures that disconnected peers will eventually // be reconnected with. func (c *Core) _addPeerLoop() { - c.config.RLock() - defer c.config.RUnlock() - if c.addPeerTimer == nil { return } // Add peers from the Peers section - for _, peer := range c.config.Peers { + for peer := range c.config._peers { go func(peer string, intf string) { u, err := url.Parse(peer) if err != nil { @@ -93,22 +125,7 @@ func (c *Core) _addPeerLoop() { if err := c.CallPeer(u, intf); err != nil { c.log.Errorln("Failed to add peer:", err) } - }(peer, "") // TODO: this should be acted and not in a goroutine? - } - - // Add peers from the InterfacePeers section - for intf, intfpeers := range c.config.InterfacePeers { - for _, peer := range intfpeers { - go func(peer string, intf string) { - u, err := url.Parse(peer) - if err != nil { - c.log.Errorln("Failed to parse peer url:", peer, err) - } - if err := c.CallPeer(u, intf); err != nil { - c.log.Errorln("Failed to add peer:", err) - } - }(peer, intf) // TODO: this should be acted and not in a goroutine? - } + }(peer.URI, peer.SourceInterface) // TODO: this should be acted and not in a goroutine? } c.addPeerTimer = time.AfterFunc(time.Minute, func() { @@ -116,49 +133,6 @@ func (c *Core) _addPeerLoop() { }) } -// Start starts up Yggdrasil using the provided config.NodeConfig, and outputs -// debug logging through the provided log.Logger. The started stack will include -// 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) (err error) { - phony.Block(c, func() { - 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) error { - c.log = log - c.config = nc - - if name := version.BuildName(); name != "unknown" { - c.log.Infoln("Build name:", name) - } - if version := version.BuildVersion(); version != "unknown" { - c.log.Infoln("Build version:", version) - } - - c.log.Infoln("Starting up...") - if err := c._init(); err != nil { - c.log.Errorln("Failed to initialize core") - return err - } - - if err := c.links.init(c); err != nil { - c.log.Errorln("Failed to start link interfaces") - return err - } - - c.addPeerTimer = time.AfterFunc(0, func() { - c.Act(nil, c._addPeerLoop) - }) - - c.log.Infoln("Startup complete") - return nil -} - // Stop shuts down the Yggdrasil node. func (c *Core) Stop() { phony.Block(c, func() { @@ -168,17 +142,9 @@ func (c *Core) Stop() { }) } -func (c *Core) Close() error { - var err error - phony.Block(c, func() { - err = c._close() - }) - return err -} - // This function is unsafe and should only be ran by the core actor. func (c *Core) _close() error { - c.ctxCancel() + c.cancel() err := c.PacketConn.Close() if c.addPeerTimer != nil { c.addPeerTimer.Stop() diff --git a/src/core/link.go b/src/core/link.go index f96c9be..0b7e50a 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -1,6 +1,7 @@ package core import ( + "bytes" "crypto/ed25519" "encoding/hex" "errors" @@ -215,12 +216,10 @@ func (intf *link) handler() (chan struct{}, error) { } } // Check if we're authorized to connect to this key / IP - intf.links.core.config.RLock() - allowed := intf.links.core.config.AllowedPublicKeys - intf.links.core.config.RUnlock() + allowed := intf.links.core.config._allowedPublicKeys isallowed := len(allowed) == 0 - for _, k := range allowed { - if k == hex.EncodeToString(meta.key) { // TODO: this is yuck + for k := range allowed { + if bytes.Equal(k[:], meta.key) { isallowed = true break } diff --git a/src/core/options.go b/src/core/options.go new file mode 100644 index 0000000..0210a4e --- /dev/null +++ b/src/core/options.go @@ -0,0 +1,30 @@ +package core + +import ( + "crypto/ed25519" +) + +type SetupOption interface { + isSetupOption() +} + +type ListenAddress string +type AdminListenAddress string +type Peer struct { + URI string + SourceInterface string +} +type NodeInfo map[string]interface{} +type NodeInfoPrivacy bool +type IfName string +type IfMTU uint16 +type AllowedPublicKey ed25519.PublicKey + +func (a ListenAddress) isSetupOption() {} +func (a AdminListenAddress) isSetupOption() {} +func (a Peer) isSetupOption() {} +func (a NodeInfo) isSetupOption() {} +func (a NodeInfoPrivacy) isSetupOption() {} +func (a IfName) isSetupOption() {} +func (a IfMTU) isSetupOption() {} +func (a AllowedPublicKey) isSetupOption() {} diff --git a/src/core/tcp.go b/src/core/tcp.go index 7b1773b..8e1435e 100644 --- a/src/core/tcp.go +++ b/src/core/tcp.go @@ -96,7 +96,7 @@ func (t *tcp) getAddr() *net.TCPAddr { } // Initializes the struct. -func (t *tcp) init(l *links) error { +func (t *tcp) init(l *links, listeners []ListenAddress) error { t.links = l t.tls.init(t) t.mutex.Lock() @@ -105,10 +105,8 @@ func (t *tcp) init(l *links) error { t.listeners = make(map[string]*TcpListener) t.mutex.Unlock() - t.links.core.config.RLock() - defer t.links.core.config.RUnlock() - for _, listenaddr := range t.links.core.config.Listen { - u, err := url.Parse(listenaddr) + for _, listenaddr := range listeners { + u, err := url.Parse(string(listenaddr)) if err != nil { t.links.core.log.Errorln("Failed to parse listener: listener", listenaddr, "is not correctly formatted, ignoring") }