From 6a408ad7fe901ec04c9aa092922eca15de7c9d86 Mon Sep 17 00:00:00 2001
From: Wolfgang Welz <welzwo@gmail.com>
Date: Fri, 24 Jan 2020 11:55:59 +0100
Subject: [PATCH] Feat: Add config keys to make bind addresses configurable
 (#172)

* configure bind and external address

* Move bind and external to network

* Ignore empty seed

* Update default config

* Add bind address config for analysis http server

* Make graph config consistent

* Add bind address to dashboard

* Do not print config on startup

* Apply suggestions from code review

Co-Authored-By: Luca Moser <moser.luca@gmail.com>

* Apply suggestions from code review

Co-Authored-By: Luca Moser <moser.luca@gmail.com>

Co-authored-by: Luca Moser <moser.luca@gmail.com>
---
 config.json                                   | 69 +++++++++------
 packages/autopeering/discover/manager_test.go |  4 +-
 .../autopeering/discover/protocol_test.go     |  7 +-
 packages/autopeering/peer/local.go            |  6 +-
 packages/autopeering/peer/local_test.go       | 39 +++++----
 packages/autopeering/peer/mapdb_test.go       |  7 +-
 .../autopeering/selection/manager_test.go     |  5 +-
 .../autopeering/selection/protocol_test.go    | 15 +++-
 packages/autopeering/server/server.go         |  9 +-
 packages/autopeering/server/server_test.go    | 13 ++-
 packages/gossip/manager_test.go               | 72 +++++++---------
 packages/gossip/server/server.go              | 31 ++-----
 packages/gossip/server/server_test.go         | 84 +++++++++----------
 packages/parameter/parameter.go               | 21 ++++-
 plugins/analysis/client/parameters.go         |  2 +-
 plugins/analysis/server/parameters.go         |  2 +-
 .../webinterface/httpserver/parameters.go     | 13 +++
 .../webinterface/httpserver/plugin.go         | 36 +++++---
 plugins/autopeering/autopeering.go            | 35 ++++----
 plugins/autopeering/local/local.go            | 39 +++++----
 plugins/autopeering/local/parameters.go       | 12 +--
 plugins/dashboard/parameters.go               | 13 +++
 plugins/dashboard/plugin.go                   | 66 ++++++++++-----
 plugins/gossip/gossip.go                      | 41 ++++++---
 plugins/graph/graph.go                        |  2 +-
 plugins/graph/parameters.go                   | 34 ++++----
 plugins/graph/plugin.go                       | 40 +++++----
 27 files changed, 420 insertions(+), 297 deletions(-)
 create mode 100644 plugins/analysis/webinterface/httpserver/parameters.go
 create mode 100644 plugins/dashboard/parameters.go

diff --git a/config.json b/config.json
index 9b8fe5db..b702dcb0 100644
--- a/config.json
+++ b/config.json
@@ -1,49 +1,64 @@
 {
   "analysis": {
-    "serveraddress": "ressims.iota.cafe:188",
-    "serverport": 0
+    "client": {
+      "serverAddress": "ressims.iota.cafe:188"
+    },
+    "server": {
+      "port": 0
+    },
+    "httpServer": {
+      "bindAddress": "0.0.0.0:80"
+    }
   },
   "autopeering": {
-    "address": "0.0.0.0",
-    "entrynodes": [
+    "entryNodes": [
       "V8LYtWWcPYYDTTXLeIEFjJEuWlsjDiI0+Pq/Cx9ai6g=@116.202.49.178:14626"
     ],
     "port": 14626
   },
+  "dashboard": {
+    "bindAddress": "0.0.0.0:8081"
+  },
   "database": {
     "directory": "mainnetdb"
   },
   "gossip": {
     "port": 14666
   },
-  "webapi": {
-    "bindAddress": "0.0.0.0:8080",
-    "auth": {
-      "username": "goshimmer",
-      "password": "goshimmer",
-      "privateKey": "uUUavNbdr32jE9CqnSMCKt4HMu9AZ2K4rKekUSPx9jk83eyeM7xewv5CqUKYMC9"
-    }
-  },
   "graph": {
-    "webrootPath": "./IOTAtangle/webroot",
-    "socketioPath": "./socket.io-client/dist/socket.io.js",
+    "bindAddress": "127.0.0.1:8083",
     "domain": "",
-    "host": "127.0.0.1",
-    "port": 8083,
-    "networkName": "GoShimmer"
+    "networkName": "GoShimmer",
+    "socketIOPath": "socket.io-client/dist/socket.io.js",
+    "webrootPath": "IOTAtangle/webroot"
   },
   "logger": {
-    "Level": "info",
-    "DisableCaller": false,
-    "DisableStacktrace": false,
-    "Encoding": "console",
-    "OutputPaths": [
+    "level": "info",
+    "disableCaller": false,
+    "disableStacktrace": false,
+    "encoding": "console",
+    "outputPaths": [
       "goshimmer.log"
-    ]
+    ],
+    "disableEvents": false
+  },
+  "network": {
+    "bindAddress": "0.0.0.0",
+    "externalAddress": "auto"
   },
   "node": {
-    "disableplugins": "",
-    "enableplugins": [],
-    "loglevel": 0
+    "disablePlugins": [],
+    "enablePlugins": []
+  },
+  "webapi": {
+    "auth": {
+      "password": "goshimmer",
+      "privateKey": "",
+      "username": "goshimmer"
+    },
+    "bindAddress": "0.0.0.0:8080"
+  },
+  "zeromq": {
+    "port": 5556
   }
-}
\ No newline at end of file
+}
diff --git a/packages/autopeering/discover/manager_test.go b/packages/autopeering/discover/manager_test.go
index afe6a389..fb0132a1 100644
--- a/packages/autopeering/discover/manager_test.go
+++ b/packages/autopeering/discover/manager_test.go
@@ -33,7 +33,9 @@ func (m *NetworkMock) DiscoveryRequest(p *peer.Peer) ([]*peer.Peer, error) {
 }
 
 func newNetworkMock() *NetworkMock {
-	local, _ := peer.NewLocal("mock", "0", peer.NewMemoryDB(log))
+	services := service.New()
+	services.Update(service.PeeringKey, "mock", "local")
+	local, _ := peer.NewLocal(services, peer.NewMemoryDB(log))
 	return &NetworkMock{
 		// no database needed
 		loc: local,
diff --git a/packages/autopeering/discover/protocol_test.go b/packages/autopeering/discover/protocol_test.go
index fd294f7b..92a5740d 100644
--- a/packages/autopeering/discover/protocol_test.go
+++ b/packages/autopeering/discover/protocol_test.go
@@ -31,8 +31,11 @@ func init() {
 // newTest creates a new discovery server and also returns the teardown.
 func newTest(t require.TestingT, trans transport.Transport, logger *logger.Logger, masters ...*peer.Peer) (*server.Server, *Protocol, func()) {
 	l := logger.Named(trans.LocalAddr().String())
+
+	services := service.New()
+	services.Update(service.PeeringKey, trans.LocalAddr().Network(), trans.LocalAddr().String())
 	db := peer.NewMemoryDB(l.Named("db"))
-	local, err := peer.NewLocal(trans.LocalAddr().Network(), trans.LocalAddr().String(), db)
+	local, err := peer.NewLocal(services, db)
 	require.NoError(t, err)
 
 	cfg := Config{
@@ -40,7 +43,7 @@ func newTest(t require.TestingT, trans transport.Transport, logger *logger.Logge
 		MasterPeers: masters,
 	}
 	prot := New(local, cfg)
-	srv := server.Listen(local, trans, l.Named("srv"), prot)
+	srv := server.Serve(local, trans, l.Named("srv"), prot)
 	prot.Start(srv)
 
 	teardown := func() {
diff --git a/packages/autopeering/peer/local.go b/packages/autopeering/peer/local.go
index 19595a32..989d0ff8 100644
--- a/packages/autopeering/peer/local.go
+++ b/packages/autopeering/peer/local.go
@@ -44,7 +44,7 @@ func newLocal(key PrivateKey, serviceRecord *service.Record, db DB) *Local {
 // NewLocal creates a new local peer linked to the provided db.
 // If an optional seed is provided, the seed is used to generate the private key. Without a seed,
 // the provided key is loaded from the provided database and generated if not stored there.
-func NewLocal(network string, address string, db DB, seed ...[]byte) (*Local, error) {
+func NewLocal(serviceRecord *service.Record, db DB, seed ...[]byte) (*Local, error) {
 	var key PrivateKey
 	if len(seed) > 0 {
 		key = PrivateKey(ed25519.NewKeyFromSeed(seed[0]))
@@ -62,10 +62,6 @@ func NewLocal(network string, address string, db DB, seed ...[]byte) (*Local, er
 	if l := len(key); l != ed25519.PrivateKeySize {
 		return nil, fmt.Errorf("invalid key length: %d, need %d", l, ed25519.PrivateKeySize)
 	}
-	// update the external address used for the peering
-	serviceRecord := service.New()
-	serviceRecord.Update(service.PeeringKey, network, address)
-
 	return newLocal(key, serviceRecord, db), nil
 }
 
diff --git a/packages/autopeering/peer/local_test.go b/packages/autopeering/peer/local_test.go
index aa50862c..469ef80f 100644
--- a/packages/autopeering/peer/local_test.go
+++ b/packages/autopeering/peer/local_test.go
@@ -28,36 +28,45 @@ func TestPublicKey(t *testing.T) {
 	assert.EqualValues(t, pub, local.PublicKey())
 }
 
-func newTestLocal(t require.TestingT) *Local {
-	priv, err := generatePrivateKey()
-	require.NoError(t, err)
-	return newLocal(priv, newTestServiceRecord(), nil)
-}
-
 func TestAddress(t *testing.T) {
-	local := newTestLocal(t)
+	local := newTestLocal(t, nil)
 
 	address := local.Services().Get(service.PeeringKey).String()
 	assert.EqualValues(t, address, local.Address())
 }
 
 func TestPrivateSalt(t *testing.T) {
-	p := newTestLocal(t)
+	p := newTestLocal(t, nil)
 
-	salt, _ := salt.NewSalt(time.Second * 10)
-	p.SetPrivateSalt(salt)
+	s, _ := salt.NewSalt(time.Second * 10)
+	p.SetPrivateSalt(s)
 
 	got := p.GetPrivateSalt()
-	assert.Equal(t, salt, got, "Private salt")
+	assert.Equal(t, s, got, "Private salt")
 }
 
 func TestPublicSalt(t *testing.T) {
-	p := newTestLocal(t)
+	p := newTestLocal(t, nil)
 
-	salt, _ := salt.NewSalt(time.Second * 10)
-	p.SetPublicSalt(salt)
+	s, _ := salt.NewSalt(time.Second * 10)
+	p.SetPublicSalt(s)
 
 	got := p.GetPublicSalt()
 
-	assert.Equal(t, salt, got, "Public salt")
+	assert.Equal(t, s, got, "Public salt")
+}
+
+func newTestLocal(t require.TestingT, db DB) *Local {
+	var priv PrivateKey
+	var err error
+	if db == nil {
+		priv, err = generatePrivateKey()
+		require.NoError(t, err)
+	} else {
+		priv, err = db.LocalPrivateKey()
+		require.NoError(t, err)
+	}
+	services := service.New()
+	services.Update(service.PeeringKey, testNetwork, testAddress)
+	return newLocal(priv, services, db)
 }
diff --git a/packages/autopeering/peer/mapdb_test.go b/packages/autopeering/peer/mapdb_test.go
index 5c7580b7..494f6691 100644
--- a/packages/autopeering/peer/mapdb_test.go
+++ b/packages/autopeering/peer/mapdb_test.go
@@ -58,12 +58,9 @@ func TestMapDBSeedPeers(t *testing.T) {
 func TestMapDBLocal(t *testing.T) {
 	db := NewMemoryDB(log)
 
-	l1, err := NewLocal(testNetwork, testAddress, db)
-	require.NoError(t, err)
+	l1 := newTestLocal(t, db)
 	assert.Equal(t, len(l1.PublicKey()), ed25519.PublicKeySize)
 
-	l2, err := NewLocal(testNetwork, testAddress, db)
-	require.NoError(t, err)
-
+	l2 := newTestLocal(t, db)
 	assert.Equal(t, l1, l2)
 }
diff --git a/packages/autopeering/selection/manager_test.go b/packages/autopeering/selection/manager_test.go
index af24ee41..cf5a961f 100644
--- a/packages/autopeering/selection/manager_test.go
+++ b/packages/autopeering/selection/manager_test.go
@@ -7,6 +7,7 @@ import (
 	"time"
 
 	"github.com/iotaledger/goshimmer/packages/autopeering/peer"
+	"github.com/iotaledger/goshimmer/packages/autopeering/peer/service"
 	"github.com/iotaledger/goshimmer/packages/autopeering/salt"
 	"github.com/iotaledger/hive.go/events"
 	"github.com/iotaledger/hive.go/logger"
@@ -186,7 +187,9 @@ type networkMock struct {
 }
 
 func newNetworkMock(name string, mgrMap map[peer.ID]*manager, log *logger.Logger) *networkMock {
-	local, _ := peer.NewLocal("mock", name, peer.NewMemoryDB(log))
+	services := service.New()
+	services.Update(service.PeeringKey, "mock", name)
+	local, _ := peer.NewLocal(services, peer.NewMemoryDB(log))
 	return &networkMock{
 		loc: local,
 		mgr: mgrMap,
diff --git a/packages/autopeering/selection/protocol_test.go b/packages/autopeering/selection/protocol_test.go
index ce6e59e1..c2e99035 100644
--- a/packages/autopeering/selection/protocol_test.go
+++ b/packages/autopeering/selection/protocol_test.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/iotaledger/goshimmer/packages/autopeering/discover"
 	"github.com/iotaledger/goshimmer/packages/autopeering/peer"
+	"github.com/iotaledger/goshimmer/packages/autopeering/peer/service"
 	"github.com/iotaledger/goshimmer/packages/autopeering/salt"
 	"github.com/iotaledger/goshimmer/packages/autopeering/server"
 	"github.com/iotaledger/goshimmer/packages/autopeering/transport"
@@ -31,15 +32,18 @@ func (d dummyDiscovery) GetVerifiedPeers() []*peer.Peer                  { retur
 // newTest creates a new neighborhood server and also returns the teardown.
 func newTest(t require.TestingT, trans transport.Transport) (*server.Server, *Protocol, func()) {
 	l := log.Named(trans.LocalAddr().String())
+
+	services := service.New()
+	services.Update(service.PeeringKey, trans.LocalAddr().Network(), trans.LocalAddr().String())
 	db := peer.NewMemoryDB(l.Named("db"))
-	local, err := peer.NewLocal(trans.LocalAddr().Network(), trans.LocalAddr().String(), db)
+	local, err := peer.NewLocal(services, db)
 	require.NoError(t, err)
 
 	// add the new peer to the global map for dummyDiscovery
 	peerMap[local.ID()] = &local.Peer
 
 	prot := New(local, dummyDiscovery{}, Config{Log: l})
-	srv := server.Listen(local, trans, l.Named("srv"), prot)
+	srv := server.Serve(local, trans, l.Named("srv"), prot)
 	prot.Start(srv)
 
 	teardown := func() {
@@ -155,8 +159,11 @@ func TestProtocol(t *testing.T) {
 // newTest creates a new server handling discover as well as neighborhood and also returns the teardown.
 func newFullTest(t require.TestingT, trans transport.Transport, masterPeers ...*peer.Peer) (*server.Server, *Protocol, func()) {
 	l := log.Named(trans.LocalAddr().String())
+
+	services := service.New()
+	services.Update(service.PeeringKey, trans.LocalAddr().Network(), trans.LocalAddr().String())
 	db := peer.NewMemoryDB(l.Named("db"))
-	local, err := peer.NewLocal(trans.LocalAddr().Network(), trans.LocalAddr().String(), db)
+	local, err := peer.NewLocal(services, db)
 	require.NoError(t, err)
 
 	discovery := discover.New(local, discover.Config{
@@ -167,7 +174,7 @@ func newFullTest(t require.TestingT, trans transport.Transport, masterPeers ...*
 		Log: l.Named("sel"),
 	})
 
-	srv := server.Listen(local, trans, l.Named("srv"), discovery, selection)
+	srv := server.Serve(local, trans, l.Named("srv"), discovery, selection)
 
 	discovery.Start(srv)
 	selection.Start(srv)
diff --git a/packages/autopeering/server/server.go b/packages/autopeering/server/server.go
index 88ca6cc5..d1bca6aa 100644
--- a/packages/autopeering/server/server.go
+++ b/packages/autopeering/server/server.go
@@ -68,10 +68,10 @@ type reply struct {
 	matchedRequest chan<- bool // a matching request is indicated via this channel
 }
 
-// Listen starts a new peer server using the given transport layer for communication.
+// Serve starts a new peer server using the given transport layer for communication.
 // Sent data is signed using the identity of the local peer,
 // received data with a valid peer signature is handled according to the provided Handler.
-func Listen(local *peer.Local, t transport.Transport, log *logger.Logger, h ...Handler) *Server {
+func Serve(local *peer.Local, t transport.Transport, log *logger.Logger, h ...Handler) *Server {
 	srv := &Server{
 		local:           local,
 		trans:           t,
@@ -88,7 +88,10 @@ func Listen(local *peer.Local, t transport.Transport, log *logger.Logger, h ...H
 	go srv.replyLoop()
 	go srv.readLoop()
 
-	log.Debugw("server started", "addr", srv.LocalAddr(), "#handlers", len(h))
+	log.Debugw("server started",
+		"network", srv.LocalAddr().Network(),
+		"address", srv.LocalAddr().String(),
+		"#handlers", len(h))
 
 	return srv
 }
diff --git a/packages/autopeering/server/server_test.go b/packages/autopeering/server/server_test.go
index 58bc5a60..e6725fc0 100644
--- a/packages/autopeering/server/server_test.go
+++ b/packages/autopeering/server/server_test.go
@@ -5,6 +5,7 @@ import (
 	"time"
 
 	"github.com/iotaledger/goshimmer/packages/autopeering/peer"
+	"github.com/iotaledger/goshimmer/packages/autopeering/peer/service"
 	"github.com/iotaledger/goshimmer/packages/autopeering/salt"
 	"github.com/iotaledger/goshimmer/packages/autopeering/transport"
 	"github.com/iotaledger/hive.go/logger"
@@ -96,8 +97,9 @@ func unmarshal(data []byte) (Message, error) {
 }
 
 func TestSrvEncodeDecodePing(t *testing.T) {
-	// create minimal server just containing the local peer
-	local, err := peer.NewLocal("dummy", "local", peer.NewMemoryDB(log))
+	services := service.New()
+	services.Update(service.PeeringKey, "dummy", "local")
+	local, err := peer.NewLocal(services, peer.NewMemoryDB(log))
 	require.NoError(t, err)
 	s := &Server{local: local}
 
@@ -114,8 +116,11 @@ func TestSrvEncodeDecodePing(t *testing.T) {
 
 func newTestServer(t require.TestingT, name string, trans transport.Transport) (*Server, func()) {
 	l := log.Named(name)
+
+	services := service.New()
+	services.Update(service.PeeringKey, trans.LocalAddr().Network(), trans.LocalAddr().String())
 	db := peer.NewMemoryDB(l.Named("db"))
-	local, err := peer.NewLocal(trans.LocalAddr().Network(), trans.LocalAddr().String(), db)
+	local, err := peer.NewLocal(services, db)
 	require.NoError(t, err)
 
 	s, _ := salt.NewSalt(100 * time.Second)
@@ -123,7 +128,7 @@ func newTestServer(t require.TestingT, name string, trans transport.Transport) (
 	s, _ = salt.NewSalt(100 * time.Second)
 	local.SetPublicSalt(s)
 
-	srv := Listen(local, trans, l, HandlerFunc(handle))
+	srv := Serve(local, trans, l, HandlerFunc(handle))
 
 	teardown := func() {
 		srv.Close()
diff --git a/packages/gossip/manager_test.go b/packages/gossip/manager_test.go
index eb86bf01..ea19d480 100644
--- a/packages/gossip/manager_test.go
+++ b/packages/gossip/manager_test.go
@@ -27,46 +27,6 @@ var (
 
 func getTestTransaction([]byte) ([]byte, error) { return testTxData, nil }
 
-func getTCPAddress(t require.TestingT) string {
-	tcpAddr, err := net.ResolveTCPAddr("tcp", "localhost:0")
-	require.NoError(t, err)
-	lis, err := net.ListenTCP("tcp", tcpAddr)
-	require.NoError(t, err)
-
-	addr := lis.Addr().String()
-	require.NoError(t, lis.Close())
-
-	return addr
-}
-
-func newTestManager(t require.TestingT, name string) (*Manager, func(), *peer.Peer) {
-	l := log.Named(name)
-	db := peer.NewMemoryDB(l.Named("db"))
-	local, err := peer.NewLocal("peering", name, db)
-	require.NoError(t, err)
-
-	// enable TCP gossipping
-	require.NoError(t, local.UpdateService(service.GossipKey, "tcp", getTCPAddress(t)))
-
-	mgr := NewManager(local, getTestTransaction, l)
-
-	srv, err := server.ListenTCP(local, l)
-	require.NoError(t, err)
-
-	// update the service with the actual address
-	require.NoError(t, local.UpdateService(service.GossipKey, srv.LocalAddr().Network(), srv.LocalAddr().String()))
-
-	// start the actual gossipping
-	mgr.Start(srv)
-
-	detach := func() {
-		mgr.Close()
-		srv.Close()
-		db.Close()
-	}
-	return mgr, detach, &local.Peer
-}
-
 func TestClose(t *testing.T) {
 	_, detach := newEventMock(t)
 	defer detach()
@@ -409,6 +369,38 @@ func TestTxRequest(t *testing.T) {
 	e.AssertExpectations(t)
 }
 
+func newTestManager(t require.TestingT, name string) (*Manager, func(), *peer.Peer) {
+	l := log.Named(name)
+
+	services := service.New()
+	services.Update(service.PeeringKey, "peering", name)
+	db := peer.NewMemoryDB(l.Named("db"))
+	local, err := peer.NewLocal(services, db)
+	require.NoError(t, err)
+
+	laddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0")
+	require.NoError(t, err)
+	lis, err := net.ListenTCP("tcp", laddr)
+	require.NoError(t, err)
+
+	// enable TCP gossipping
+	require.NoError(t, local.UpdateService(service.GossipKey, lis.Addr().Network(), lis.Addr().String()))
+
+	srv := server.ServeTCP(local, lis, l)
+
+	// start the actual gossipping
+	mgr := NewManager(local, getTestTransaction, l)
+	mgr.Start(srv)
+
+	detach := func() {
+		mgr.Close()
+		srv.Close()
+		_ = lis.Close()
+		db.Close()
+	}
+	return mgr, detach, &local.Peer
+}
+
 func newEventMock(t mock.TestingT) (*eventMock, func()) {
 	e := &eventMock{}
 	e.Test(t)
diff --git a/packages/gossip/server/server.go b/packages/gossip/server/server.go
index bb52798c..c3853ac0 100644
--- a/packages/gossip/server/server.go
+++ b/packages/gossip/server/server.go
@@ -77,39 +77,22 @@ type accept struct {
 	conn   net.Conn // the actual network connection
 }
 
-// ListenTCP creates the object and starts listening for incoming connections.
-func ListenTCP(local *peer.Local, log *zap.SugaredLogger) (*TCP, error) {
+// ServeTCP creates the object and starts listening for incoming connections.
+func ServeTCP(local *peer.Local, listener *net.TCPListener, log *zap.SugaredLogger) *TCP {
 	t := &TCP{
 		local:            local,
+		publicAddr:       local.Services().Get(service.GossipKey),
+		listener:         listener,
 		log:              log,
 		addAcceptMatcher: make(chan *acceptMatcher),
 		acceptReceived:   make(chan accept),
 		closing:          make(chan struct{}),
 	}
-
-	t.publicAddr = local.Services().Get(service.GossipKey)
 	if t.publicAddr == nil {
-		return nil, ErrNoGossip
-	}
-	tcpAddr, err := net.ResolveTCPAddr(t.publicAddr.Network(), t.publicAddr.String())
-	if err != nil {
-		return nil, err
-	}
-	// if the ip is an external ip, set it to unspecified
-	if tcpAddr.IP.IsGlobalUnicast() {
-		if tcpAddr.IP.To4() != nil {
-			tcpAddr.IP = net.IPv4zero
-		} else {
-			tcpAddr.IP = net.IPv6unspecified
-		}
+		panic(ErrNoGossip)
 	}
 
-	listener, err := net.ListenTCP(t.publicAddr.Network(), tcpAddr)
-	if err != nil {
-		return nil, err
-	}
-	t.listener = listener
-	t.log.Debugw("listening started",
+	t.log.Debugw("server started",
 		"network", listener.Addr().Network(),
 		"address", listener.Addr().String(),
 	)
@@ -118,7 +101,7 @@ func ListenTCP(local *peer.Local, log *zap.SugaredLogger) (*TCP, error) {
 	go t.run()
 	go t.listenLoop()
 
-	return t, nil
+	return t
 }
 
 // Close stops listening on the gossip address.
diff --git a/packages/gossip/server/server_test.go b/packages/gossip/server/server_test.go
index 9aa398fb..5f2778a1 100644
--- a/packages/gossip/server/server_test.go
+++ b/packages/gossip/server/server_test.go
@@ -17,48 +17,17 @@ const graceTime = 5 * time.Millisecond
 
 var log = logger.NewExampleLogger("server")
 
-func getTCPAddress(t require.TestingT) string {
-	laddr, err := net.ResolveTCPAddr("tcp", "localhost:0")
-	require.NoError(t, err)
-	lis, err := net.ListenTCP("tcp", laddr)
-	require.NoError(t, err)
-
-	addr := lis.Addr().String()
-	require.NoError(t, lis.Close())
-
-	return addr
-}
-
-func newTest(t require.TestingT, name string) (*TCP, func()) {
-	l := log.Named(name)
-	db := peer.NewMemoryDB(l.Named("db"))
-	local, err := peer.NewLocal("peering", name, db)
-	require.NoError(t, err)
-
-	// enable TCP gossipping
-	require.NoError(t, local.UpdateService(service.GossipKey, "tcp", getTCPAddress(t)))
-
-	trans, err := ListenTCP(local, l)
-	require.NoError(t, err)
-
-	teardown := func() {
-		trans.Close()
-		db.Close()
-	}
-	return trans, teardown
-}
-
 func getPeer(t *TCP) *peer.Peer {
 	return &t.local.Peer
 }
 
 func TestClose(t *testing.T) {
-	_, teardown := newTest(t, "A")
+	_, teardown := newTestServer(t, "A")
 	teardown()
 }
 
 func TestUnansweredAccept(t *testing.T) {
-	transA, closeA := newTest(t, "A")
+	transA, closeA := newTestServer(t, "A")
 	defer closeA()
 
 	_, err := transA.AcceptPeer(getPeer(transA))
@@ -66,7 +35,7 @@ func TestUnansweredAccept(t *testing.T) {
 }
 
 func TestCloseWhileAccepting(t *testing.T) {
-	transA, closeA := newTest(t, "A")
+	transA, closeA := newTestServer(t, "A")
 
 	var wg sync.WaitGroup
 	wg.Add(1)
@@ -82,7 +51,7 @@ func TestCloseWhileAccepting(t *testing.T) {
 }
 
 func TestUnansweredDial(t *testing.T) {
-	transA, closeA := newTest(t, "A")
+	transA, closeA := newTestServer(t, "A")
 	defer closeA()
 
 	// create peer with invalid gossip address
@@ -95,7 +64,7 @@ func TestUnansweredDial(t *testing.T) {
 }
 
 func TestNoHandshakeResponse(t *testing.T) {
-	transA, closeA := newTest(t, "A")
+	transA, closeA := newTestServer(t, "A")
 	defer closeA()
 
 	// accept and read incoming connections
@@ -119,7 +88,7 @@ func TestNoHandshakeResponse(t *testing.T) {
 }
 
 func TestNoHandshakeRequest(t *testing.T) {
-	transA, closeA := newTest(t, "A")
+	transA, closeA := newTestServer(t, "A")
 	defer closeA()
 
 	var wg sync.WaitGroup
@@ -140,9 +109,9 @@ func TestNoHandshakeRequest(t *testing.T) {
 }
 
 func TestConnect(t *testing.T) {
-	transA, closeA := newTest(t, "A")
+	transA, closeA := newTestServer(t, "A")
 	defer closeA()
-	transB, closeB := newTest(t, "B")
+	transB, closeB := newTestServer(t, "B")
 	defer closeB()
 
 	var wg sync.WaitGroup
@@ -153,7 +122,7 @@ func TestConnect(t *testing.T) {
 		c, err := transA.AcceptPeer(getPeer(transB))
 		assert.NoError(t, err)
 		if assert.NotNil(t, c) {
-			c.Close()
+			_ = c.Close()
 		}
 	}()
 	time.Sleep(graceTime)
@@ -162,7 +131,7 @@ func TestConnect(t *testing.T) {
 		c, err := transB.DialPeer(getPeer(transA))
 		assert.NoError(t, err)
 		if assert.NotNil(t, c) {
-			c.Close()
+			_ = c.Close()
 		}
 	}()
 
@@ -170,11 +139,11 @@ func TestConnect(t *testing.T) {
 }
 
 func TestWrongConnect(t *testing.T) {
-	transA, closeA := newTest(t, "A")
+	transA, closeA := newTestServer(t, "A")
 	defer closeA()
-	transB, closeB := newTest(t, "B")
+	transB, closeB := newTestServer(t, "B")
 	defer closeB()
-	transC, closeC := newTest(t, "C")
+	transC, closeC := newTestServer(t, "C")
 	defer closeC()
 
 	var wg sync.WaitGroup
@@ -194,3 +163,30 @@ func TestWrongConnect(t *testing.T) {
 
 	wg.Wait()
 }
+
+func newTestServer(t require.TestingT, name string) (*TCP, func()) {
+	l := log.Named(name)
+
+	services := service.New()
+	services.Update(service.PeeringKey, "peering", name)
+	db := peer.NewMemoryDB(l.Named("db"))
+	local, err := peer.NewLocal(services, db)
+	require.NoError(t, err)
+
+	laddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0")
+	require.NoError(t, err)
+	lis, err := net.ListenTCP("tcp", laddr)
+	require.NoError(t, err)
+
+	// enable TCP gossipping
+	require.NoError(t, local.UpdateService(service.GossipKey, lis.Addr().Network(), lis.Addr().String()))
+
+	srv := ServeTCP(local, lis, l)
+
+	teardown := func() {
+		srv.Close()
+		_ = lis.Close()
+		db.Close()
+	}
+	return srv, teardown
+}
diff --git a/packages/parameter/parameter.go b/packages/parameter/parameter.go
index fa1adfee..7d390f1f 100644
--- a/packages/parameter/parameter.go
+++ b/packages/parameter/parameter.go
@@ -1,6 +1,7 @@
 package parameter
 
 import (
+	"github.com/iotaledger/hive.go/logger"
 	"github.com/iotaledger/hive.go/parameter"
 	flag "github.com/spf13/pflag"
 	"github.com/spf13/viper"
@@ -11,10 +12,26 @@ var (
 	configName    = flag.StringP("config", "c", "config", "Filename of the config file without the file extension")
 	configDirPath = flag.StringP("config-dir", "d", ".", "Path to the directory containing the config file")
 
-	// Viper
-	NodeConfig = viper.New()
+	// viper
+	NodeConfig *viper.Viper
+
+	// logger
+	defaultLoggerConfig = logger.Config{
+		Level:             "info",
+		DisableCaller:     false,
+		DisableStacktrace: false,
+		Encoding:          "console",
+		OutputPaths:       []string{"goshimmer.log"},
+		DisableEvents:     false,
+	}
 )
 
+func init() {
+	// set the default logger config
+	NodeConfig = viper.New()
+	NodeConfig.SetDefault(logger.ViperKey, defaultLoggerConfig)
+}
+
 // FetchConfig fetches config values from a dir defined via CLI flag --config-dir (or the current working dir if not set).
 //
 // It automatically reads in a single config file starting with "config" (can be changed via the --config CLI flag)
diff --git a/plugins/analysis/client/parameters.go b/plugins/analysis/client/parameters.go
index a60d1432..b875fadf 100644
--- a/plugins/analysis/client/parameters.go
+++ b/plugins/analysis/client/parameters.go
@@ -5,7 +5,7 @@ import (
 )
 
 const (
-	CFG_SERVER_ADDRESS = "analysis.serverAddress"
+	CFG_SERVER_ADDRESS = "analysis.client.serverAddress"
 )
 
 func init() {
diff --git a/plugins/analysis/server/parameters.go b/plugins/analysis/server/parameters.go
index e27b4a9e..a8d66903 100644
--- a/plugins/analysis/server/parameters.go
+++ b/plugins/analysis/server/parameters.go
@@ -5,7 +5,7 @@ import (
 )
 
 const (
-	CFG_SERVER_PORT = "analysis.serverPort"
+	CFG_SERVER_PORT = "analysis.server.port"
 )
 
 func init() {
diff --git a/plugins/analysis/webinterface/httpserver/parameters.go b/plugins/analysis/webinterface/httpserver/parameters.go
new file mode 100644
index 00000000..a32ab36f
--- /dev/null
+++ b/plugins/analysis/webinterface/httpserver/parameters.go
@@ -0,0 +1,13 @@
+package httpserver
+
+import (
+	flag "github.com/spf13/pflag"
+)
+
+const (
+	CFG_BIND_ADDRESS = "analysis.httpServer.bindAddress"
+)
+
+func init() {
+	flag.String(CFG_BIND_ADDRESS, "0.0.0.0:80", "the bind address for the web API")
+}
diff --git a/plugins/analysis/webinterface/httpserver/plugin.go b/plugins/analysis/webinterface/httpserver/plugin.go
index d8330000..c84fabe5 100644
--- a/plugins/analysis/webinterface/httpserver/plugin.go
+++ b/plugins/analysis/webinterface/httpserver/plugin.go
@@ -3,9 +3,9 @@ package httpserver
 import (
 	"errors"
 	"net/http"
-	"sync"
 	"time"
 
+	"github.com/iotaledger/goshimmer/packages/parameter"
 	"github.com/iotaledger/goshimmer/packages/shutdown"
 	"github.com/iotaledger/hive.go/daemon"
 	"github.com/iotaledger/hive.go/logger"
@@ -25,36 +25,46 @@ func Configure() {
 	log = logger.NewLogger(name)
 
 	router = http.NewServeMux()
-	httpServer = &http.Server{Addr: ":80", Handler: router}
+
+	httpServer = &http.Server{
+		Addr:    parameter.NodeConfig.GetString(CFG_BIND_ADDRESS),
+		Handler: router,
+	}
 
 	router.Handle("/datastream", websocket.Handler(dataStream))
 	router.HandleFunc("/", index)
 }
 
 func Run() {
+	log.Infof("Starting %s ...", name)
 	if err := daemon.BackgroundWorker(name, start, shutdown.ShutdownPriorityAnalysis); err != nil {
 		log.Errorf("Error starting as daemon: %s", err)
 	}
 }
 
 func start(shutdownSignal <-chan struct{}) {
-	var wg sync.WaitGroup
-	wg.Add(1)
+	stopped := make(chan struct{})
 	go func() {
-		defer wg.Done()
-		log.Infof(name+" started: address=%s", httpServer.Addr)
-		if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
-			log.Warnf("Error listening: %s", err)
+		log.Infof("Started %s: http://%s", name, httpServer.Addr)
+		if err := httpServer.ListenAndServe(); err != nil {
+			if !errors.Is(err, http.ErrServerClosed) {
+				log.Errorf("Error serving: %s", err)
+			}
+			close(stopped)
 		}
 	}()
-	<-shutdownSignal
-	log.Info("Stopping " + name + " ...")
+
+	select {
+	case <-shutdownSignal:
+	case <-stopped:
+	}
+
+	log.Infof("Stopping %s ...", name)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
 	defer cancel()
 
 	if err := httpServer.Shutdown(ctx); err != nil {
-		log.Errorf("Error closing: %s", err)
+		log.Errorf("Error stopping: %s", err)
 	}
-	wg.Wait()
-	log.Info("Stopping " + name + " ... done")
+	log.Info("Stopping %s ... done", name)
 }
diff --git a/plugins/autopeering/autopeering.go b/plugins/autopeering/autopeering.go
index 76c8122a..18d04fc0 100644
--- a/plugins/autopeering/autopeering.go
+++ b/plugins/autopeering/autopeering.go
@@ -57,31 +57,30 @@ func configureAP() {
 func start(shutdownSignal <-chan struct{}) {
 	defer log.Info("Stopping " + name + " ... done")
 
-	loc := local.GetInstance()
-	peeringAddr := loc.Services().Get(service.PeeringKey)
-	udpAddr, err := net.ResolveUDPAddr(peeringAddr.Network(), peeringAddr.String())
+	lPeer := local.GetInstance()
+	// use the port of the peering service
+	peeringAddr := lPeer.Services().Get(service.PeeringKey)
+	_, peeringPort, err := net.SplitHostPort(peeringAddr.String())
 	if err != nil {
-		log.Fatalf("ResolveUDPAddr: %v", err)
+		panic(err)
 	}
-
-	// if the ip is an external ip, set it to unspecified
-	if udpAddr.IP.IsGlobalUnicast() {
-		if udpAddr.IP.To4() != nil {
-			udpAddr.IP = net.IPv4zero
-		} else {
-			udpAddr.IP = net.IPv6unspecified
-		}
+	// resolve the bind address
+	address := net.JoinHostPort(parameter.NodeConfig.GetString(local.CFG_BIND), peeringPort)
+	localAddr, err := net.ResolveUDPAddr(peeringAddr.Network(), address)
+	if err != nil {
+		log.Fatalf("Error resolving %s: %v", local.CFG_BIND, err)
 	}
 
 	// check that discovery is working and the port is open
 	log.Info("Testing service ...")
-	checkConnection(udpAddr, &loc.Peer)
+	checkConnection(localAddr, &lPeer.Peer)
 	log.Info("Testing service ... done")
 
-	conn, err := net.ListenUDP(peeringAddr.Network(), udpAddr)
+	conn, err := net.ListenUDP(peeringAddr.Network(), localAddr)
 	if err != nil {
-		log.Fatalf("ListenUDP: %v", err)
+		log.Fatalf("Error listening: %v", err)
 	}
+	defer conn.Close()
 
 	// use the UDP connection for transport
 	trans := transport.Conn(conn, func(network, address string) (net.Addr, error) { return net.ResolveUDPAddr(network, address) })
@@ -93,7 +92,7 @@ func start(shutdownSignal <-chan struct{}) {
 	}
 
 	// start a server doing discovery and peering
-	srv := server.Listen(loc, trans, log.Named("srv"), handlers...)
+	srv := server.Serve(lPeer, trans, log.Named("srv"), handlers...)
 	defer srv.Close()
 
 	// start the discovery on that connection
@@ -106,8 +105,8 @@ func start(shutdownSignal <-chan struct{}) {
 		defer Selection.Close()
 	}
 
-	log.Infof(name+" started: address=%s/%s", peeringAddr.String(), peeringAddr.Network())
-	log.Debugf(name+" server started: PubKey=%s", base64.StdEncoding.EncodeToString(loc.PublicKey()))
+	log.Infof(name+" started: Address=%s/%s", peeringAddr.String(), peeringAddr.Network())
+	log.Infof(name+" started: PubKey=%s", base64.StdEncoding.EncodeToString(lPeer.PublicKey()))
 
 	<-shutdownSignal
 	log.Info("Stopping " + name + " ...")
diff --git a/plugins/autopeering/local/local.go b/plugins/autopeering/local/local.go
index c5ca19a1..6b694b72 100644
--- a/plugins/autopeering/local/local.go
+++ b/plugins/autopeering/local/local.go
@@ -5,9 +5,11 @@ import (
 	"encoding/base64"
 	"net"
 	"strconv"
+	"strings"
 	"sync"
 
 	"github.com/iotaledger/goshimmer/packages/autopeering/peer"
+	"github.com/iotaledger/goshimmer/packages/autopeering/peer/service"
 	"github.com/iotaledger/goshimmer/packages/netutil"
 	"github.com/iotaledger/goshimmer/packages/parameter"
 	"github.com/iotaledger/hive.go/logger"
@@ -21,24 +23,30 @@ var (
 func configureLocal() *peer.Local {
 	log := logger.NewLogger("Local")
 
-	ip := net.ParseIP(parameter.NodeConfig.GetString(CFG_ADDRESS))
-	if ip == nil {
-		log.Fatalf("Invalid %s address: %s", CFG_ADDRESS, parameter.NodeConfig.GetString(CFG_ADDRESS))
-	}
-	if ip.IsUnspecified() {
-		log.Info("Querying public IP ...")
-		myIp, err := netutil.GetPublicIP(!netutil.IsIPv4(ip))
+	var externalIP net.IP
+	if str := parameter.NodeConfig.GetString(CFG_EXTERNAL); strings.ToLower(str) == "auto" {
+		log.Info("Querying external IP ...")
+		ip, err := netutil.GetPublicIP(false)
 		if err != nil {
-			log.Fatalf("Error querying public IP: %s", err)
+			log.Fatalf("Error querying external IP: %s", err)
+		}
+		log.Infof("External IP queried: address=%s", ip.String())
+		externalIP = ip
+	} else {
+		externalIP = net.ParseIP(str)
+		if externalIP == nil {
+			log.Fatalf("Invalid IP address (%s): %s", CFG_EXTERNAL, str)
 		}
-		ip = myIp
-		log.Infof("Public IP queried: address=%s", ip.String())
+	}
+	if !externalIP.IsGlobalUnicast() {
+		log.Fatalf("IP is not a global unicast address: %s", externalIP.String())
 	}
 
-	port := strconv.Itoa(parameter.NodeConfig.GetInt(CFG_PORT))
+	peeringPort := strconv.Itoa(parameter.NodeConfig.GetInt(CFG_PORT))
 
-	// create a new local node
-	db := peer.NewPersistentDB(log)
+	// announce the peering service
+	services := service.New()
+	services.Update(service.PeeringKey, "udp", net.JoinHostPort(externalIP.String(), peeringPort))
 
 	// the private key seed of the current local can be returned the following way:
 	// key, _ := db.LocalPrivateKey()
@@ -46,8 +54,7 @@ func configureLocal() *peer.Local {
 
 	// set the private key from the seed provided in the config
 	var seed [][]byte
-	if parameter.NodeConfig.IsSet(CFG_SEED) {
-		str := parameter.NodeConfig.GetString(CFG_SEED)
+	if str := parameter.NodeConfig.GetString(CFG_SEED); str != "" {
 		bytes, err := base64.StdEncoding.DecodeString(str)
 		if err != nil {
 			log.Fatalf("Invalid %s: %s", CFG_SEED, err)
@@ -58,7 +65,7 @@ func configureLocal() *peer.Local {
 		seed = append(seed, bytes)
 	}
 
-	local, err := peer.NewLocal("udp", net.JoinHostPort(ip.String(), port), db, seed...)
+	local, err := peer.NewLocal(services, peer.NewPersistentDB(log), seed...)
 	if err != nil {
 		log.Fatalf("Error creating local: %s", err)
 	}
diff --git a/plugins/autopeering/local/parameters.go b/plugins/autopeering/local/parameters.go
index ab7629e8..9ffa15c8 100644
--- a/plugins/autopeering/local/parameters.go
+++ b/plugins/autopeering/local/parameters.go
@@ -5,13 +5,15 @@ import (
 )
 
 const (
-	CFG_ADDRESS = "autopeering.address"
-	CFG_PORT    = "autopeering.port"
-	CFG_SEED    = "autopeering.seed"
+	CFG_BIND     = "network.bindAddress"
+	CFG_EXTERNAL = "network.externalAddress"
+	CFG_PORT     = "autopeering.port"
+	CFG_SEED     = "autopeering.seed"
 )
 
 func init() {
-	flag.String(CFG_ADDRESS, "0.0.0.0", "address to bind for incoming peering requests")
-	flag.Int(CFG_PORT, 14626, "udp port for incoming peering requests")
+	flag.String(CFG_BIND, "0.0.0.0", "bind address for global services such as autopeering and gossip")
+	flag.String(CFG_EXTERNAL, "auto", "external IP address under which the node is reachable; or 'auto' to determine it automatically")
+	flag.Int(CFG_PORT, 14626, "UDP port for incoming peering requests")
 	flag.BytesBase64(CFG_SEED, nil, "private key seed used to derive the node identity; optional Base64 encoded 256-bit string")
 }
diff --git a/plugins/dashboard/parameters.go b/plugins/dashboard/parameters.go
new file mode 100644
index 00000000..1031a273
--- /dev/null
+++ b/plugins/dashboard/parameters.go
@@ -0,0 +1,13 @@
+package dashboard
+
+import (
+	flag "github.com/spf13/pflag"
+)
+
+const (
+	CFG_BIND_ADDRESS = "dashboard.bindAddress"
+)
+
+func init() {
+	flag.String(CFG_BIND_ADDRESS, "0.0.0.0:8081", "the bind address for the dashboard")
+}
diff --git a/plugins/dashboard/plugin.go b/plugins/dashboard/plugin.go
index 5fa26f69..bf820c55 100644
--- a/plugins/dashboard/plugin.go
+++ b/plugins/dashboard/plugin.go
@@ -1,9 +1,11 @@
 package dashboard
 
 import (
+	"errors"
 	"net/http"
 	"time"
 
+	"github.com/iotaledger/goshimmer/packages/parameter"
 	"github.com/iotaledger/goshimmer/packages/shutdown"
 	"github.com/iotaledger/goshimmer/plugins/metrics"
 	"github.com/iotaledger/hive.go/daemon"
@@ -13,17 +15,24 @@ import (
 	"golang.org/x/net/context"
 )
 
-var server *http.Server
+var (
+	log        *logger.Logger
+	httpServer *http.Server
+)
+
+const name = "Dashboard"
 
-var router *http.ServeMux
+var PLUGIN = node.NewPlugin(name, node.Disabled, configure, run)
 
-var PLUGIN = node.NewPlugin("Dashboard", node.Disabled, configure, run)
-var log *logger.Logger
+func configure(*node.Plugin) {
+	log = logger.NewLogger(name)
 
-func configure(plugin *node.Plugin) {
-	log = logger.NewLogger("Dashboard")
-	router = http.NewServeMux()
-	server = &http.Server{Addr: ":8081", Handler: router}
+	router := http.NewServeMux()
+
+	httpServer = &http.Server{
+		Addr:    parameter.NodeConfig.GetString(CFG_BIND_ADDRESS),
+		Handler: router,
+	}
 
 	router.HandleFunc("/dashboard", ServeHome)
 	router.HandleFunc("/ws", ServeWs)
@@ -37,17 +46,36 @@ func configure(plugin *node.Plugin) {
 	}))
 }
 
-func run(plugin *node.Plugin) {
-	daemon.BackgroundWorker("Dashboard Updater", func(shutdownSignal <-chan struct{}) {
-		go func() {
-			if err := server.ListenAndServe(); err != nil {
-				log.Error(err.Error())
+func run(*node.Plugin) {
+	log.Infof("Starting %s ...", name)
+	if err := daemon.BackgroundWorker(name, workerFunc, shutdown.ShutdownPriorityDashboard); err != nil {
+		log.Errorf("Error starting as daemon: %s", err)
+	}
+}
+
+func workerFunc(shutdownSignal <-chan struct{}) {
+	stopped := make(chan struct{})
+	go func() {
+		log.Infof("Started %s: http://%s/dashboard", name, httpServer.Addr)
+		if err := httpServer.ListenAndServe(); err != nil {
+			if !errors.Is(err, http.ErrServerClosed) {
+				log.Errorf("Error serving: %s", err)
 			}
-		}()
+			close(stopped)
+		}
+	}()
+
+	select {
+	case <-shutdownSignal:
+	case <-stopped:
+	}
+
+	log.Infof("Stopping %s ...", name)
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	defer cancel()
 
-		<-shutdownSignal
-		ctx, cancel := context.WithTimeout(context.Background(), 0*time.Second)
-		defer cancel()
-		_ = server.Shutdown(ctx)
-	}, shutdown.ShutdownPriorityDashboard)
+	if err := httpServer.Shutdown(ctx); err != nil {
+		log.Errorf("Error stopping: %s", err)
+	}
+	log.Infof("Stopping %s ... done", name)
 }
diff --git a/plugins/gossip/gossip.go b/plugins/gossip/gossip.go
index 5a6904c5..63536632 100644
--- a/plugins/gossip/gossip.go
+++ b/plugins/gossip/gossip.go
@@ -27,15 +27,17 @@ var (
 func configureGossip() {
 	lPeer := local.GetInstance()
 
-	port := strconv.Itoa(parameter.NodeConfig.GetInt(GOSSIP_PORT))
-
-	host, _, err := net.SplitHostPort(lPeer.Address())
+	peeringAddr := lPeer.Services().Get(service.PeeringKey)
+	external, _, err := net.SplitHostPort(peeringAddr.String())
 	if err != nil {
-		log.Fatalf("invalid peering address: %v", err)
+		panic(err)
 	}
-	err = lPeer.UpdateService(service.GossipKey, "tcp", net.JoinHostPort(host, port))
+
+	// announce the gossip service
+	gossipPort := strconv.Itoa(parameter.NodeConfig.GetInt(GOSSIP_PORT))
+	err = lPeer.UpdateService(service.GossipKey, "tcp", net.JoinHostPort(external, gossipPort))
 	if err != nil {
-		log.Fatalf("could not update services: %v", err)
+		log.Fatalf("could not update services: %s", err)
 	}
 
 	mgr = gp.NewManager(lPeer, loadTransaction, log)
@@ -44,23 +46,38 @@ func configureGossip() {
 func start(shutdownSignal <-chan struct{}) {
 	defer log.Info("Stopping " + name + " ... done")
 
-	loc := local.GetInstance()
-	srv, err := server.ListenTCP(loc, log)
+	lPeer := local.GetInstance()
+	// use the port of the gossip service
+	gossipAddr := lPeer.Services().Get(service.GossipKey)
+	_, gossipPort, err := net.SplitHostPort(gossipAddr.String())
+	if err != nil {
+		panic(err)
+	}
+	// resolve the bind address
+	address := net.JoinHostPort(parameter.NodeConfig.GetString(local.CFG_BIND), gossipPort)
+	localAddr, err := net.ResolveTCPAddr(gossipAddr.Network(), address)
 	if err != nil {
-		log.Fatalf("ListenTCP: %v", err)
+		log.Fatalf("Error resolving %s: %v", local.CFG_BIND, err)
 	}
+
+	listener, err := net.ListenTCP(gossipAddr.Network(), localAddr)
+	if err != nil {
+		log.Fatalf("Error listening: %v", err)
+	}
+	defer listener.Close()
+
+	srv := server.ServeTCP(lPeer, listener, log)
 	defer srv.Close()
 
 	//check that the server is working and the port is open
 	log.Info("Testing service ...")
-	checkConnection(srv, &loc.Peer)
+	checkConnection(srv, &lPeer.Peer)
 	log.Info("Testing service ... done")
 
 	mgr.Start(srv)
 	defer mgr.Close()
 
-	gossipAddr := loc.Services().Get(service.GossipKey)
-	log.Infof(name+" started: address=%s/%s", gossipAddr.String(), gossipAddr.Network())
+	log.Infof("%s started: Address=%s/%s", name, gossipAddr.String(), gossipAddr.Network())
 
 	<-shutdownSignal
 	log.Info("Stopping " + name + " ...")
diff --git a/plugins/graph/graph.go b/plugins/graph/graph.go
index 6b5442e8..e0946cc3 100644
--- a/plugins/graph/graph.go
+++ b/plugins/graph/graph.go
@@ -66,7 +66,7 @@ func onConnectHandler(s socketio.Conn) error {
 	log.Info(infoMsg)
 	socketioServer.JoinRoom("broadcast", s)
 
-	config := &wsConfig{NetworkName: parameter.NodeConfig.GetString("graph.networkName")}
+	config := &wsConfig{NetworkName: parameter.NodeConfig.GetString(CFG_NETWORK)}
 
 	var initTxs []*wsTransaction
 	txRingBuffer.Do(func(tx interface{}) {
diff --git a/plugins/graph/parameters.go b/plugins/graph/parameters.go
index 5bc63869..5fbef46f 100644
--- a/plugins/graph/parameters.go
+++ b/plugins/graph/parameters.go
@@ -1,26 +1,22 @@
 package graph
 
 import (
-	"github.com/iotaledger/goshimmer/packages/parameter"
+	"github.com/iotaledger/goshimmer/plugins/cli"
+	flag "github.com/spf13/pflag"
 )
 
-func init() {
-
-	// "Path to IOTA Tangle Visualiser webroot files"
-	parameter.NodeConfig.SetDefault("graph.webrootPath", "IOTAtangle/webroot")
-
-	// "Path to socket.io.js"
-	parameter.NodeConfig.SetDefault("graph.socketioPath", "socket.io-client/dist/socket.io.js")
-
-	// "Set the domain on which IOTA Tangle Visualiser is served"
-	parameter.NodeConfig.SetDefault("graph.domain", "")
-
-	// "Set the host to which the IOTA Tangle Visualiser listens"
-	parameter.NodeConfig.SetDefault("graph.host", "127.0.0.1")
-
-	// "IOTA Tangle Visualiser webserver port"
-	parameter.NodeConfig.SetDefault("graph.port", 8083)
+const (
+	CFG_WEBROOT      = "graph.webrootPath"
+	CFG_SOCKET_IO    = "graph.socketioPath"
+	CFG_DOMAIN       = "graph.domain"
+	CFG_BIND_ADDRESS = "graph.bindAddress"
+	CFG_NETWORK      = "graph.networkName"
+)
 
-	// "Name of the network shown in IOTA Tangle Visualiser"
-	parameter.NodeConfig.SetDefault("graph.networkName", "meets HORNET")
+func init() {
+	flag.String(CFG_WEBROOT, "IOTAtangle/webroot", "Path to IOTA Tangle Visualiser webroot files")
+	flag.String(CFG_SOCKET_IO, "socket.io-client/dist/socket.io.js", "Path to socket.io.js")
+	flag.String(CFG_DOMAIN, "", "Set the domain on which IOTA Tangle Visualiser is served")
+	flag.String(CFG_BIND_ADDRESS, "127.0.0.1:8083", "the bind address for the IOTA Tangle Visualizer")
+	flag.String(CFG_NETWORK, cli.AppName, "Name of the network shown in IOTA Tangle Visualiser")
 }
diff --git a/plugins/graph/plugin.go b/plugins/graph/plugin.go
index f6dcc3ee..af586165 100644
--- a/plugins/graph/plugin.go
+++ b/plugins/graph/plugin.go
@@ -1,7 +1,7 @@
 package graph
 
 import (
-	"fmt"
+	"errors"
 	"net/http"
 	"time"
 
@@ -39,7 +39,7 @@ var (
 )
 
 func downloadSocketIOHandler(w http.ResponseWriter, r *http.Request) {
-	http.ServeFile(w, r, parameter.NodeConfig.GetString("graph.socketioPath"))
+	http.ServeFile(w, r, parameter.NodeConfig.GetString(CFG_SOCKET_IO))
 }
 
 func configureSocketIOServer() error {
@@ -72,11 +72,11 @@ func configure(plugin *node.Plugin) {
 
 	// socket.io and web server
 	server = &http.Server{
-		Addr:    fmt.Sprintf("%s:%d", parameter.NodeConfig.GetString("graph.host"), parameter.NodeConfig.GetInt("graph.port")),
+		Addr:    parameter.NodeConfig.GetString(CFG_BIND_ADDRESS),
 		Handler: router,
 	}
 
-	fs := http.FileServer(http.Dir(parameter.NodeConfig.GetString("graph.webrootPath")))
+	fs := http.FileServer(http.Dir(parameter.NodeConfig.GetString(CFG_WEBROOT)))
 
 	if err := configureSocketIOServer(); err != nil {
 		log.Panicf("Graph: %v", err.Error())
@@ -92,7 +92,7 @@ func configure(plugin *node.Plugin) {
 	}, workerpool.WorkerCount(newTxWorkerCount), workerpool.QueueSize(newTxWorkerQueueSize))
 }
 
-func run(plugin *node.Plugin) {
+func run(*node.Plugin) {
 
 	notifyNewTx := events.NewClosure(func(transaction *value_transaction.ValueTransaction) {
 		newTxWorkerPool.TrySubmit(transaction)
@@ -111,23 +111,33 @@ func run(plugin *node.Plugin) {
 	daemon.BackgroundWorker("Graph Webserver", func(shutdownSignal <-chan struct{}) {
 		go socketioServer.Serve()
 
+		stopped := make(chan struct{})
 		go func() {
+			log.Infof("You can now access IOTA Tangle Visualiser using: http://%s", parameter.NodeConfig.GetString(CFG_BIND_ADDRESS))
 			if err := server.ListenAndServe(); err != nil {
-				log.Error(err.Error())
+				if !errors.Is(err, http.ErrServerClosed) {
+					log.Errorf("Error serving: %s", err)
+				}
 			}
+			close(stopped)
 		}()
 
-		log.Infof("You can now access IOTA Tangle Visualiser using: http://%s:%d", parameter.NodeConfig.GetString("graph.host"), parameter.NodeConfig.GetInt("graph.port"))
+		select {
+		case <-shutdownSignal:
+		case <-stopped:
+		}
 
-		<-shutdownSignal
-		log.Info("Stopping Graph ...")
-
-		socketioServer.Close()
-
-		ctx, cancel := context.WithTimeout(context.Background(), 0*time.Second)
+		log.Info("Stopping Graph Webserver ...")
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
 		defer cancel()
 
-		_ = server.Shutdown(ctx)
-		log.Info("Stopping Graph ... done")
+		if err := server.Shutdown(ctx); err != nil {
+			log.Errorf("Error stopping: %s", err)
+		}
+
+		if err := socketioServer.Close(); err != nil {
+			log.Errorf("Error closing Socket.IO server: %s", err)
+		}
+		log.Info("Stopping Graph Webserver ... done")
 	}, shutdown.ShutdownPriorityGraph)
 }
-- 
GitLab