micro/network/default.go

903 lines
23 KiB
Go
Raw Normal View History

package network
import (
"errors"
"fmt"
"hash/fnv"
"sync"
"time"
2019-08-20 23:15:02 +03:00
"github.com/golang/protobuf/proto"
"github.com/micro/go-micro/client"
rtr "github.com/micro/go-micro/client/selector/router"
pbNet "github.com/micro/go-micro/network/proto"
"github.com/micro/go-micro/proxy"
"github.com/micro/go-micro/router"
pbRtr "github.com/micro/go-micro/router/proto"
"github.com/micro/go-micro/server"
"github.com/micro/go-micro/transport"
"github.com/micro/go-micro/tunnel"
tun "github.com/micro/go-micro/tunnel/transport"
"github.com/micro/go-micro/util/log"
)
var (
// NetworkChannel is the name of the tunnel channel for passing network messages
NetworkChannel = "network"
// ControlChannel is the name of the tunnel channel for passing control message
ControlChannel = "control"
// DefaultLink is default network link
DefaultLink = "network"
2019-09-05 19:44:47 +03:00
)
var (
2019-09-05 19:43:59 +03:00
// ErrClientNotFound is returned when client for tunnel channel could not be found
ErrClientNotFound = errors.New("client not found")
)
// network implements Network interface
type network struct {
// node is network node
*node
// options configure the network
options Options
// rtr is network router
2019-09-25 14:56:52 +03:00
router router.Router
// prx is network proxy
2019-09-25 14:56:52 +03:00
proxy proxy.Proxy
// tun is network tunnel
2019-09-25 14:56:52 +03:00
tunnel tunnel.Tunnel
// server is network server
server server.Server
// client is network client
client client.Client
// tunClient is a map of tunnel clients keyed over tunnel channel names
tunClient map[string]transport.Client
sync.RWMutex
// connected marks the network as connected
connected bool
// closed closes the network
closed chan bool
}
// newNetwork returns a new network node
func newNetwork(opts ...Option) Network {
options := DefaultOptions()
for _, o := range opts {
o(&options)
}
// init tunnel address to the network bind address
options.Tunnel.Init(
tunnel.Address(options.Address),
2019-09-19 18:32:15 +03:00
tunnel.Nodes(options.Peers...),
)
// init router Id to the network id
options.Router.Init(
router.Id(options.Id),
)
// create tunnel client with tunnel transport
tunTransport := tun.NewTransport(
tun.WithTunnel(options.Tunnel),
)
2019-10-02 17:22:44 +03:00
// set the address to a hashed address
hasher := fnv.New64()
hasher.Write([]byte(options.Address + options.Id))
address := fmt.Sprintf("%d", hasher.Sum64())
2019-09-18 20:56:02 +03:00
// set the address to advertise
2019-10-02 17:22:44 +03:00
var advertise string
var peerAddress string
2019-10-02 17:22:44 +03:00
2019-09-18 20:56:02 +03:00
if len(options.Advertise) > 0 {
2019-10-02 17:22:44 +03:00
advertise = options.Advertise
peerAddress = options.Advertise
2019-10-02 17:22:44 +03:00
} else {
advertise = options.Address
peerAddress = address
2019-09-18 20:56:02 +03:00
}
// server is network server
server := server.NewServer(
server.Id(options.Id),
server.Address(peerAddress),
2019-10-02 17:22:44 +03:00
server.Advertise(advertise),
2019-08-21 21:22:50 +03:00
server.Name(options.Name),
server.Transport(tunTransport),
)
// client is network client
client := client.NewClient(
client.Transport(tunTransport),
client.Selector(
rtr.NewSelector(
rtr.WithRouter(options.Router),
),
),
)
network := &network{
node: &node{
id: options.Id,
address: peerAddress,
peers: make(map[string]*node),
},
options: options,
2019-09-25 14:56:52 +03:00
router: options.Router,
proxy: options.Proxy,
tunnel: options.Tunnel,
server: server,
client: client,
tunClient: make(map[string]transport.Client),
}
network.node.network = network
return network
}
// Options returns network options
func (n *network) Options() Options {
n.RLock()
defer n.RUnlock()
options := n.options
return options
}
// Name returns network name
func (n *network) Name() string {
return n.options.Name
}
// resolveNodes resolves network nodes to addresses
func (n *network) resolveNodes() ([]string, error) {
// resolve the network address to network nodes
records, err := n.options.Resolver.Resolve(n.options.Name)
if err != nil {
return nil, err
}
nodeMap := make(map[string]bool)
// collect network node addresses
var nodes []string
2019-08-29 17:09:01 +03:00
for _, record := range records {
nodes = append(nodes, record.Address)
nodeMap[record.Address] = true
}
// append seed nodes if we have them
2019-09-19 18:32:15 +03:00
for _, node := range n.options.Peers {
if _, ok := nodeMap[node]; !ok {
nodes = append(nodes, node)
}
}
return nodes, nil
}
// resolve continuously resolves network nodes and initializes network tunnel with resolved addresses
func (n *network) resolve() {
resolve := time.NewTicker(ResolveTime)
defer resolve.Stop()
for {
select {
case <-n.closed:
return
case <-resolve.C:
nodes, err := n.resolveNodes()
if err != nil {
log.Debugf("Network failed to resolve nodes: %v", err)
continue
}
// initialize the tunnel
2019-09-25 14:56:52 +03:00
n.tunnel.Init(
tunnel.Nodes(nodes...),
)
}
}
}
// handleNetConn handles network announcement messages
2019-08-30 22:05:00 +03:00
func (n *network) handleNetConn(sess tunnel.Session, msg chan *transport.Message) {
for {
m := new(transport.Message)
2019-08-30 22:05:00 +03:00
if err := sess.Recv(m); err != nil {
log.Debugf("Network tunnel [%s] receive error: %v", NetworkChannel, err)
return
}
select {
case msg <- m:
case <-n.closed:
return
}
}
}
// acceptNetConn accepts connections from NetworkChannel
func (n *network) acceptNetConn(l tunnel.Listener, recv chan *transport.Message) {
for {
// accept a connection
conn, err := l.Accept()
if err != nil {
// TODO: handle this
log.Debugf("Network tunnel [%s] accept error: %v", NetworkChannel, err)
return
}
select {
case <-n.closed:
return
default:
// go handle NetworkChannel connection
go n.handleNetConn(conn, recv)
}
}
}
// processNetChan processes messages received on NetworkChannel
func (n *network) processNetChan(client transport.Client, listener tunnel.Listener) {
// receive network message queue
recv := make(chan *transport.Message, 128)
// accept NetworkChannel connections
go n.acceptNetConn(listener, recv)
for {
select {
case m := <-recv:
// switch on type of message and take action
switch m.Header["Micro-Method"] {
case "connect":
// mark the time the message has been received
now := time.Now()
pbNetConnect := &pbNet.Connect{}
if err := proto.Unmarshal(m.Body, pbNetConnect); err != nil {
log.Debugf("Network tunnel [%s] connect unmarshal error: %v", NetworkChannel, err)
continue
}
// don't process your own messages
if pbNetConnect.Node.Id == n.options.Id {
continue
}
log.Debugf("Network received connect message from: %s", pbNetConnect.Node.Id)
peer := &node{
id: pbNetConnect.Node.Id,
address: pbNetConnect.Node.Address,
peers: make(map[string]*node),
lastSeen: now,
}
if err := n.node.AddPeer(peer); err == ErrPeerExists {
log.Debugf("Network peer exists, refreshing: %s", peer.id)
// update lastSeen time for the existing node
if err := n.RefreshPeer(peer.id, now); err != nil {
log.Debugf("Network failed refreshing peer %s: %v", peer.id, err)
}
continue
}
// get node peers down to MaxDepth encoded in protobuf
msg := PeersToProto(n.node, MaxDepth)
// advertise yourself to the network
if err := n.sendMsg("peer", msg, NetworkChannel); err != nil {
log.Debugf("Network failed to advertise peers: %v", err)
}
// advertise all the routes when a new node has connected
2019-09-25 14:56:52 +03:00
if err := n.router.Solicit(); err != nil {
log.Debugf("Network failed to solicit routes: %s", err)
}
case "peer":
// mark the time the message has been received
now := time.Now()
pbNetPeer := &pbNet.Peer{}
if err := proto.Unmarshal(m.Body, pbNetPeer); err != nil {
log.Debugf("Network tunnel [%s] peer unmarshal error: %v", NetworkChannel, err)
continue
}
// don't process your own messages
if pbNetPeer.Node.Id == n.options.Id {
continue
}
log.Debugf("Network received peer message from: %s", pbNetPeer.Node.Id)
peer := &node{
id: pbNetPeer.Node.Id,
address: pbNetPeer.Node.Address,
peers: make(map[string]*node),
lastSeen: now,
}
if err := n.node.AddPeer(peer); err == nil {
// send a solicit message when discovering new peer
msg := &pbRtr.Solicit{
Id: n.options.Id,
}
if err := n.sendMsg("solicit", msg, ControlChannel); err != nil {
log.Debugf("Network failed to send solicit message: %s", err)
}
continue
// we're expecting any error to be ErrPeerExists
} else if err != ErrPeerExists {
log.Debugf("Network got error adding peer %v", err)
continue
}
log.Debugf("Network peer exists, refreshing: %s", pbNetPeer.Node.Id)
// update lastSeen time for the peer
if err := n.RefreshPeer(pbNetPeer.Node.Id, now); err != nil {
log.Debugf("Network failed refreshing peer %s: %v", pbNetPeer.Node.Id, err)
}
// NOTE: we don't unpack MaxDepth toplogy
peer = UnpackPeerTopology(pbNetPeer, now, MaxDepth-1)
log.Debugf("Network updating topology of node: %s", n.node.id)
if err := n.node.UpdatePeer(peer); err != nil {
log.Debugf("Network failed to update peers: %v", err)
}
case "close":
pbNetClose := &pbNet.Close{}
if err := proto.Unmarshal(m.Body, pbNetClose); err != nil {
log.Debugf("Network tunnel [%s] close unmarshal error: %v", NetworkChannel, err)
continue
}
// don't process your own messages
if pbNetClose.Node.Id == n.options.Id {
continue
}
log.Debugf("Network received close message from: %s", pbNetClose.Node.Id)
2019-09-15 14:16:08 +03:00
peer := &node{
id: pbNetClose.Node.Id,
address: pbNetClose.Node.Address,
}
if err := n.DeletePeerNode(peer.id); err != nil {
log.Debugf("Network failed to delete node %s routes: %v", peer.id, err)
}
if err := n.prunePeerRoutes(peer); err != nil {
log.Debugf("Network failed pruning peer %s routes: %v", peer.id, err)
}
}
case <-n.closed:
return
}
}
}
// sendMsg sends a message to the tunnel channel
func (n *network) sendMsg(method string, msg proto.Message, channel string) error {
body, err := proto.Marshal(msg)
if err != nil {
return err
}
// create transport message and chuck it down the pipe
m := transport.Message{
Header: map[string]string{
"Micro-Method": method,
},
Body: body,
}
// check if the channel client is initialized
n.RLock()
client, ok := n.tunClient[channel]
if !ok || client == nil {
n.RUnlock()
2019-09-05 19:43:59 +03:00
return ErrClientNotFound
}
n.RUnlock()
log.Debugf("Network sending %s message from: %s", method, n.options.Id)
if err := client.Send(&m); err != nil {
return err
}
return nil
}
// announce announces node peers to the network
func (n *network) announce(client transport.Client) {
announce := time.NewTicker(AnnounceTime)
defer announce.Stop()
for {
select {
case <-n.closed:
return
case <-announce.C:
msg := PeersToProto(n.node, MaxDepth)
// advertise yourself to the network
if err := n.sendMsg("peer", msg, NetworkChannel); err != nil {
log.Debugf("Network failed to advertise peers: %v", err)
continue
}
}
}
}
2019-09-15 14:16:08 +03:00
// pruneRoutes prunes routes return by given query
func (n *network) pruneRoutes(q router.Query) error {
2019-09-25 14:56:52 +03:00
routes, err := n.router.Table().Query(q)
if err != nil && err != router.ErrRouteNotFound {
return err
}
2019-09-15 14:16:08 +03:00
for _, route := range routes {
2019-09-25 14:56:52 +03:00
if err := n.router.Table().Delete(route); err != nil && err != router.ErrRouteNotFound {
return err
}
}
return nil
}
2019-09-15 14:16:08 +03:00
// pruneNodeRoutes prunes routes that were either originated by or routable via given node
func (n *network) prunePeerRoutes(peer *node) error {
// lookup all routes originated by router
q := router.NewQuery(
router.QueryRouter(peer.id),
)
if err := n.pruneRoutes(q); err != nil {
return err
}
// lookup all routes routable via gw
q = router.NewQuery(
router.QueryGateway(peer.id),
2019-09-15 14:16:08 +03:00
)
if err := n.pruneRoutes(q); err != nil {
return err
}
return nil
}
// prune deltes node peers that have not been seen for longer than PruneTime seconds
// prune also removes all the routes either originated by or routable by the stale nodes
func (n *network) prune() {
prune := time.NewTicker(PruneTime)
defer prune.Stop()
for {
select {
case <-n.closed:
return
case <-prune.C:
pruned := n.PruneStalePeerNodes(PruneTime)
for id, peer := range pruned {
log.Debugf("Network peer exceeded prune time: %s", id)
if err := n.prunePeerRoutes(peer); err != nil {
log.Debugf("Network failed pruning peer %s routes: %v", id, err)
}
}
}
}
}
// handleCtrlConn handles ControlChannel connections
2019-08-30 22:05:00 +03:00
func (n *network) handleCtrlConn(sess tunnel.Session, msg chan *transport.Message) {
for {
m := new(transport.Message)
2019-08-30 22:05:00 +03:00
if err := sess.Recv(m); err != nil {
// TODO: should we bail here?
log.Debugf("Network tunnel advert receive error: %v", err)
return
}
select {
case msg <- m:
case <-n.closed:
return
}
}
}
// acceptCtrlConn accepts connections from ControlChannel
func (n *network) acceptCtrlConn(l tunnel.Listener, recv chan *transport.Message) {
for {
// accept a connection
conn, err := l.Accept()
if err != nil {
// TODO: handle this
log.Debugf("Network tunnel [%s] accept error: %v", ControlChannel, err)
return
}
select {
case <-n.closed:
return
default:
// go handle ControlChannel connection
go n.handleCtrlConn(conn, recv)
}
}
}
// setRouteMetric calculates metric of the route and updates it in place
// - Local route metric is 1
// - Routes with ID of adjacent nodes are 10
// - Routes by peers of the advertiser are 100
// - Routes beyond your neighbourhood are 1000
func (n *network) setRouteMetric(route *router.Route) {
// we are the origin of the route
if route.Router == n.options.Id {
route.Metric = 1
return
}
// check if the route origin is our peer
if _, ok := n.peers[route.Router]; ok {
route.Metric = 10
return
}
// check if the route origin is the peer of our peer
for _, peer := range n.peers {
for id := range peer.peers {
if route.Router == id {
route.Metric = 100
return
}
}
}
// the origin of the route is beyond our neighbourhood
route.Metric = 1000
}
// processCtrlChan processes messages received on ControlChannel
func (n *network) processCtrlChan(client transport.Client, listener tunnel.Listener) {
// receive control message queue
recv := make(chan *transport.Message, 128)
// accept ControlChannel cconnections
go n.acceptCtrlConn(listener, recv)
for {
select {
case m := <-recv:
// switch on type of message and take action
switch m.Header["Micro-Method"] {
case "advert":
pbRtrAdvert := &pbRtr.Advert{}
if err := proto.Unmarshal(m.Body, pbRtrAdvert); err != nil {
log.Debugf("Network fail to unmarshal advert message: %v", err)
continue
}
// don't process your own messages
if pbRtrAdvert.Id == n.options.Id {
continue
}
log.Debugf("Network received advert message from: %s", pbRtrAdvert.Id)
// loookup advertising node in our peer topology
advertNode := n.node.GetPeerNode(pbRtrAdvert.Id)
if advertNode == nil {
// if we can't find the node in our topology (MaxDepth) we skipp prcessing adverts
log.Debugf("Network skipping advert message from unknown peer: %s", pbRtrAdvert.Id)
continue
}
var events []*router.Event
for _, event := range pbRtrAdvert.Events {
// we know the advertising node is not the origin of the route
if pbRtrAdvert.Id != event.Route.Router {
// if the origin router is not the advertising node peer
// we can't rule out potential routing loops so we bail here
2019-09-13 21:55:53 +03:00
if peer := advertNode.GetPeerNode(event.Route.Router); peer == nil {
log.Debugf("Network skipping advert message from peer: %s", pbRtrAdvert.Id)
continue
}
}
route := router.Route{
Service: event.Route.Service,
Address: event.Route.Address,
Gateway: event.Route.Gateway,
Network: event.Route.Network,
2019-08-28 10:41:19 +03:00
Router: event.Route.Router,
Link: event.Route.Link,
Metric: int(event.Route.Metric),
}
// set the route metric
n.node.RLock()
n.setRouteMetric(&route)
n.node.RUnlock()
// throw away metric bigger than 1000
if route.Metric > 1000 {
2019-09-06 17:12:23 +03:00
log.Debugf("Network route metric %d dropping node: %s", route.Metric, route.Router)
continue
}
// create router event
e := &router.Event{
Type: router.EventType(event.Type),
Timestamp: time.Unix(0, pbRtrAdvert.Timestamp),
Route: route,
}
events = append(events, e)
}
// if no events are eligible for processing continue
if len(events) == 0 {
log.Debugf("Network no events to be processed by router: %s", n.options.Id)
continue
}
// create an advert and process it
advert := &router.Advert{
Id: pbRtrAdvert.Id,
Type: router.AdvertType(pbRtrAdvert.Type),
Timestamp: time.Unix(0, pbRtrAdvert.Timestamp),
TTL: time.Duration(pbRtrAdvert.Ttl),
Events: events,
}
log.Debugf("Network router %s processing advert: %s", n.Id(), advert.Id)
2019-09-25 14:56:52 +03:00
if err := n.router.Process(advert); err != nil {
log.Debugf("Network failed to process advert %s: %v", advert.Id, err)
}
case "solicit":
pbRtrSolicit := &pbRtr.Solicit{}
if err := proto.Unmarshal(m.Body, pbRtrSolicit); err != nil {
log.Debugf("Network fail to unmarshal solicit message: %v", err)
continue
}
log.Debugf("Network received solicit message from: %s", pbRtrSolicit.Id)
// ignore solicitation when requested by you
if pbRtrSolicit.Id == n.options.Id {
continue
}
log.Debugf("Network router flushing routes for: %s", pbRtrSolicit.Id)
// advertise all the routes when a new node has connected
2019-09-25 14:56:52 +03:00
if err := n.router.Solicit(); err != nil {
log.Debugf("Network failed to solicit routes: %s", err)
}
}
case <-n.closed:
return
}
}
}
// advertise advertises routes to the network
func (n *network) advertise(client transport.Client, advertChan <-chan *router.Advert) {
hasher := fnv.New64()
for {
select {
// process local adverts and randomly fire them at other nodes
case advert := <-advertChan:
// create a proto advert
var events []*pbRtr.Event
for _, event := range advert.Events {
2019-09-20 11:40:55 +03:00
// the routes service address
address := event.Route.Address
// only hash the address if we're advertising our own local routes
if event.Route.Router == advert.Id {
// hash the service before advertising it
hasher.Reset()
hasher.Write([]byte(event.Route.Address + n.node.id))
address = fmt.Sprintf("%d", hasher.Sum64())
}
// NOTE: we override Gateway, Link and Address here
// TODO: should we avoid overriding gateway?
route := &pbRtr.Route{
Service: event.Route.Service,
2019-09-20 11:40:55 +03:00
Address: address,
Gateway: n.node.Address(),
Network: event.Route.Network,
2019-08-28 10:41:19 +03:00
Router: event.Route.Router,
Link: DefaultLink,
Metric: int64(event.Route.Metric),
}
e := &pbRtr.Event{
Type: pbRtr.EventType(event.Type),
Timestamp: event.Timestamp.UnixNano(),
Route: route,
}
events = append(events, e)
}
msg := &pbRtr.Advert{
Id: advert.Id,
Type: pbRtr.AdvertType(advert.Type),
Timestamp: advert.Timestamp.UnixNano(),
Events: events,
}
if err := n.sendMsg("advert", msg, ControlChannel); err != nil {
log.Debugf("Network failed to advertise routes: %v", err)
continue
}
case <-n.closed:
return
}
}
}
// Connect connects the network
func (n *network) Connect() error {
n.Lock()
// return if already connected
if n.connected {
n.Unlock()
return nil
}
// try to resolve network nodes
nodes, err := n.resolveNodes()
if err != nil {
log.Debugf("Network failed to resolve nodes: %v", err)
}
// connect network tunnel
2019-09-25 14:56:52 +03:00
if err := n.tunnel.Connect(); err != nil {
2019-09-13 05:31:58 +03:00
n.Unlock()
return err
}
2019-09-17 17:40:00 +03:00
// set our internal node address
// if advertise address is not set
if len(n.options.Advertise) == 0 {
2019-09-25 14:56:52 +03:00
n.server.Init(server.Advertise(n.tunnel.Address()))
}
2019-09-17 17:40:00 +03:00
// initialize the tunnel to resolved nodes
2019-09-25 14:56:52 +03:00
n.tunnel.Init(
tunnel.Nodes(nodes...),
)
// dial into ControlChannel to send route adverts
2019-09-25 14:56:52 +03:00
ctrlClient, err := n.tunnel.Dial(ControlChannel, tunnel.DialMulticast())
if err != nil {
2019-09-13 05:31:58 +03:00
n.Unlock()
return err
}
n.tunClient[ControlChannel] = ctrlClient
// listen on ControlChannel
2019-09-25 14:56:52 +03:00
ctrlListener, err := n.tunnel.Listen(ControlChannel)
if err != nil {
2019-09-13 05:31:58 +03:00
n.Unlock()
return err
}
// dial into NetworkChannel to send network messages
2019-09-25 14:56:52 +03:00
netClient, err := n.tunnel.Dial(NetworkChannel, tunnel.DialMulticast())
if err != nil {
2019-09-13 05:31:58 +03:00
n.Unlock()
return err
}
n.tunClient[NetworkChannel] = netClient
// listen on NetworkChannel
2019-09-25 14:56:52 +03:00
netListener, err := n.tunnel.Listen(NetworkChannel)
if err != nil {
2019-09-13 05:31:58 +03:00
n.Unlock()
return err
}
// create closed channel
n.closed = make(chan bool)
// start the router
if err := n.options.Router.Start(); err != nil {
2019-09-13 05:31:58 +03:00
n.Unlock()
return err
}
// start advertising routes
advertChan, err := n.options.Router.Advertise()
if err != nil {
2019-09-13 05:31:58 +03:00
n.Unlock()
return err
}
// start the server
if err := n.server.Start(); err != nil {
2019-09-13 05:31:58 +03:00
n.Unlock()
return err
}
n.Unlock()
// send connect message to NetworkChannel
// NOTE: in theory we could do this as soon as
// Dial to NetworkChannel succeeds, but instead
// we initialize all other node resources first
msg := &pbNet.Connect{
Node: &pbNet.Node{
2019-09-17 17:40:00 +03:00
Id: n.node.id,
Address: n.node.address,
},
}
if err := n.sendMsg("connect", msg, NetworkChannel); err != nil {
log.Debugf("Network failed to send connect message: %s", err)
}
// go resolving network nodes
go n.resolve()
// broadcast peers
go n.announce(netClient)
// prune stale nodes
go n.prune()
// listen to network messages
go n.processNetChan(netClient, netListener)
// advertise service routes
go n.advertise(ctrlClient, advertChan)
// accept and process routes
go n.processCtrlChan(ctrlClient, ctrlListener)
n.Lock()
n.connected = true
n.Unlock()
return nil
}
func (n *network) close() error {
// stop the server
if err := n.server.Stop(); err != nil {
return err
}
// stop the router
2019-09-25 14:56:52 +03:00
if err := n.router.Stop(); err != nil {
return err
}
// close the tunnel
2019-09-25 14:56:52 +03:00
if err := n.tunnel.Close(); err != nil {
return err
}
return nil
}
// Close closes network connection
func (n *network) Close() error {
n.Lock()
if !n.connected {
n.Unlock()
return nil
}
select {
case <-n.closed:
n.Unlock()
return nil
default:
// TODO: send close message to the network channel
close(n.closed)
// set connected to false
n.connected = false
// unlock the lock otherwise we'll deadlock sending the close
n.Unlock()
msg := &pbNet.Close{
Node: &pbNet.Node{
2019-09-17 17:40:00 +03:00
Id: n.node.id,
Address: n.node.address,
},
}
if err := n.sendMsg("close", msg, NetworkChannel); err != nil {
log.Debugf("Network failed to send close message: %s", err)
}
}
return n.close()
}
// Client returns network client
func (n *network) Client() client.Client {
return n.client
}
// Server returns network server
func (n *network) Server() server.Server {
return n.server
}