diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 30cdd5e5..5d1c76ac 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/contrib/mobile/mobile.go b/contrib/mobile/mobile.go index d8c2d6b5..85535ee8 100644 --- a/contrib/mobile/mobile.go +++ b/contrib/mobile/mobile.go @@ -25,7 +25,7 @@ import ( // functions. Note that in the case of iOS we handle reading/writing to/from TUN // in Swift therefore we use the "dummy" TUN interface instead. type Yggdrasil struct { - core core.Core + core *core.Core iprwc *ipv6rwc.ReadWriteCloser config *config.NodeConfig multicast multicast.Multicast @@ -48,19 +48,42 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error { if err := json.Unmarshal(configjson, &m.config); err != nil { return err } - m.config.IfName = "none" - if err := m.core.Start(m.config, logger); err != nil { - logger.Errorln("An error occured starting Yggdrasil:", err) - return err + // Setup the Yggdrasil node itself. + sk, err := hex.DecodeString(m.config.PrivateKey) + if err != nil { + panic(err) + } + options := []core.SetupOption{ + core.IfName("none"), + core.IfMTU(m.config.IfMTU), + } + for _, peer := range m.config.Peers { + options = append(options, core.Peer{URI: peer}) + } + for intf, peers := range m.config.InterfacePeers { + for _, peer := range peers { + options = append(options, core.Peer{URI: peer, SourceInterface: intf}) + } + } + for _, allowed := range m.config.AllowedPublicKeys { + k, err := hex.DecodeString(allowed) + if err != nil { + panic(err) + } + options = append(options, core.AllowedPublicKey(k[:])) + } + m.core, err = core.New(sk[:], options...) + if err != nil { + panic(err) } mtu := m.config.IfMTU - m.iprwc = ipv6rwc.NewReadWriteCloser(&m.core) + m.iprwc = ipv6rwc.NewReadWriteCloser(m.core) if m.iprwc.MaxMTU() < mtu { mtu = m.iprwc.MaxMTU() } m.iprwc.SetMTU(mtu) if len(m.config.MulticastInterfaces) > 0 { - if err := m.multicast.Init(&m.core, m.config, logger, nil); err != nil { + if err := m.multicast.Init(m.core, m.config, logger, nil); err != nil { logger.Errorln("An error occurred initialising multicast:", err) } else if err := m.multicast.Start(); err != nil { logger.Errorln("An error occurred starting multicast:", err) @@ -136,18 +159,18 @@ func (m *Yggdrasil) GetCoordsString() string { func (m *Yggdrasil) GetPeersJSON() (result string) { peers := []struct { - core.Peer + core.PeerInfo IP string }{} for _, v := range m.core.GetPeers() { a := address.AddrForKey(v.Key) ip := net.IP(a[:]).String() peers = append(peers, struct { - core.Peer + core.PeerInfo IP string }{ - Peer: v, - IP: ip, + PeerInfo: v, + IP: ip, }) } if res, err := json.Marshal(peers); err == nil { diff --git a/go.mod b/go.mod index c93b8a0b..11170a1b 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/kardianos/minwinsvc v1.0.0 github.com/mitchellh/mapstructure v1.4.1 github.com/vishvananda/netlink v1.1.0 - golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 + golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b diff --git a/go.sum b/go.sum index c87112f7..0fc5cadd 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 h1:jhDgkcu3yQ4tasBZ+1YwDmK7eFmuVf1w1k+NGGGxfmE= golang.org/x/mobile v0.0.0-20220112015953-858099ff7816/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 h1:3vUV5x5+3LfQbgk7paCM6INOaJG9xXQbn79xoNkwfIk= +golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= diff --git a/src/core/api.go b/src/core/api.go index fabd7439..3ab26ee5 100644 --- a/src/core/api.go +++ b/src/core/api.go @@ -21,13 +21,13 @@ import ( //"github.com/Arceliar/phony" ) -type Self struct { +type SelfInfo struct { Key ed25519.PublicKey Root ed25519.PublicKey Coords []uint64 } -type Peer struct { +type PeerInfo struct { Key ed25519.PublicKey Root ed25519.PublicKey Coords []uint64 @@ -38,23 +38,23 @@ type Peer struct { Uptime time.Duration } -type DHTEntry struct { +type DHTEntryInfo struct { Key ed25519.PublicKey Port uint64 Rest uint64 } -type PathEntry struct { +type PathEntryInfo struct { Key ed25519.PublicKey Path []uint64 } -type Session struct { +type SessionInfo struct { Key ed25519.PublicKey } -func (c *Core) GetSelf() Self { - var self Self +func (c *Core) GetSelf() SelfInfo { + var self SelfInfo s := c.PacketConn.PacketConn.Debug.GetSelf() self.Key = s.Key self.Root = s.Root @@ -62,8 +62,8 @@ func (c *Core) GetSelf() Self { return self } -func (c *Core) GetPeers() []Peer { - var peers []Peer +func (c *Core) GetPeers() []PeerInfo { + var peers []PeerInfo names := make(map[net.Conn]string) c.links.mutex.Lock() for _, info := range c.links.links { @@ -72,7 +72,7 @@ func (c *Core) GetPeers() []Peer { c.links.mutex.Unlock() ps := c.PacketConn.PacketConn.Debug.GetPeers() for _, p := range ps { - var info Peer + var info PeerInfo info.Key = p.Key info.Root = p.Root info.Coords = p.Coords @@ -91,11 +91,11 @@ func (c *Core) GetPeers() []Peer { return peers } -func (c *Core) GetDHT() []DHTEntry { - var dhts []DHTEntry +func (c *Core) GetDHT() []DHTEntryInfo { + var dhts []DHTEntryInfo ds := c.PacketConn.PacketConn.Debug.GetDHT() for _, d := range ds { - var info DHTEntry + var info DHTEntryInfo info.Key = d.Key info.Port = d.Port info.Rest = d.Rest @@ -104,11 +104,11 @@ func (c *Core) GetDHT() []DHTEntry { return dhts } -func (c *Core) GetPaths() []PathEntry { - var paths []PathEntry +func (c *Core) GetPaths() []PathEntryInfo { + var paths []PathEntryInfo ps := c.PacketConn.PacketConn.Debug.GetPaths() for _, p := range ps { - var info PathEntry + var info PathEntryInfo info.Key = p.Key info.Path = p.Path paths = append(paths, info) @@ -116,11 +116,11 @@ func (c *Core) GetPaths() []PathEntry { return paths } -func (c *Core) GetSessions() []Session { - var sessions []Session +func (c *Core) GetSessions() []SessionInfo { + var sessions []SessionInfo ss := c.PacketConn.Debug.GetSessions() for _, s := range ss { - var info Session + var info SessionInfo info.Key = s.Key sessions = append(sessions, info) } diff --git a/src/core/core.go b/src/core/core.go index f77648b2..37a1d841 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" "net" "net/url" + "os" "time" iwe "github.com/Arceliar/ironwood/encrypted" @@ -16,9 +15,8 @@ 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/version" + //"github.com/yggdrasil-network/yggdrasil-go/src/crypto" ) // The Core object represents the Yggdrasil node. You should create a Core @@ -29,62 +27,98 @@ 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) + } + c.config._peers = map[Peer]struct{}{} + c.config._listeners = map[ListenAddress]struct{}{} + c.config._allowedPublicKeys = map[[32]byte]struct{}{} + for _, opt := range opts { + c._applyOption(opt) + } if c.log == nil { c.log = log.New(io.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 := [32]byte{} + 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 +127,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 +135,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 +144,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/core_test.go b/src/core/core_test.go index fcfe2e31..823e5e95 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -2,6 +2,7 @@ package core import ( "bytes" + "crypto/ed25519" "math/rand" "net/url" "os" @@ -9,21 +10,8 @@ import ( "time" "github.com/gologme/log" - - "github.com/yggdrasil-network/yggdrasil-go/src/config" - "github.com/yggdrasil-network/yggdrasil-go/src/defaults" ) -// GenerateConfig produces default configuration with suitable modifications for tests. -func GenerateConfig() *config.NodeConfig { - cfg := defaults.GenerateConfig() - cfg.AdminListen = "none" - cfg.Listen = []string{"tcp://127.0.0.1:0"} - cfg.IfName = "none" - - return cfg -} - // GetLoggerWithPrefix creates a new logger instance with prefix. // If verbose is set to true, three log levels are enabled: "info", "warn", "error". func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger { @@ -40,13 +28,18 @@ func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger { // CreateAndConnectTwo creates two nodes. nodeB connects to nodeA. // Verbosity flag is passed to logger. func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) { - nodeA = new(Core) - if err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ", verbose)); err != nil { + var err error + var skA, skB ed25519.PrivateKey + if _, skA, err = ed25519.GenerateKey(nil); err != nil { t.Fatal(err) } - - nodeB = new(Core) - if err := nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose)); err != nil { + if _, skB, err = ed25519.GenerateKey(nil); err != nil { + t.Fatal(err) + } + if nodeA, err = New(skA, ListenAddress("tcp://127.0.0.1:0"), IfName("none")); err != nil { + t.Fatal(err) + } + if nodeB, err = New(skB, ListenAddress("tcp://127.0.0.1:0"), IfName("none")); err != nil { t.Fatal(err) } diff --git a/src/core/link.go b/src/core/link.go index f96c9be9..099a8af0 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -1,6 +1,7 @@ package core import ( + "bytes" "crypto/ed25519" "encoding/hex" "errors" @@ -16,6 +17,7 @@ import ( "sync/atomic" + "github.com/Arceliar/phony" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/util" "golang.org/x/net/proxy" @@ -61,7 +63,14 @@ func (l *links) init(c *Core) error { l.mutex.Unlock() l.stopped = make(chan struct{}) - if err := l.tcp.init(l); err != nil { + var listeners []ListenAddress + phony.Block(c, func() { + listeners = make([]ListenAddress, 0, len(c.config._listeners)) + for listener := range c.config._listeners { + listeners = append(listeners, listener) + } + }) + if err := l.tcp.init(l, listeners); err != nil { c.log.Errorln("Failed to start TCP interface") return err } @@ -70,10 +79,6 @@ func (l *links) init(c *Core) error { } func (l *links) call(u *url.URL, sintf string) error { - //u, err := url.Parse(uri) - //if err != nil { - // return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err) - //} tcpOpts := tcpOptions{} if pubkeys, ok := u.Query()["key"]; ok && len(pubkeys) > 0 { tcpOpts.pinnedEd25519Keys = make(map[keyArray]struct{}) @@ -215,12 +220,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 00000000..0210a4ef --- /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 7b1773b8..8e1435ef 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") }