Adam Ierymenko 5 years ago
parent
commit
7061f13b24

+ 45 - 0
go/cmd/zerotier/cli/common.go

@@ -0,0 +1,45 @@
+/*
+ * Copyright (c)2019 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2023-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+package cli
+
+import (
+	"fmt"
+	"net/http"
+	"os"
+	"zerotier/pkg/zerotier"
+)
+
+func apiGet(basePath, authToken, urlPath string, result interface{}) {
+	statusCode, err := zerotier.APIGet(basePath, zerotier.APISocketName, authToken, urlPath, result)
+	if err != nil {
+		fmt.Printf("FATAL: API response code %d: %s\n", statusCode, err.Error())
+		os.Exit(1)
+		return
+	}
+	if statusCode != http.StatusOK {
+		if statusCode == http.StatusUnauthorized {
+			fmt.Printf("FATAL: API response code %d: unauthorized (authorization token incorrect)\n", statusCode)
+		}
+		fmt.Printf("FATAL: API response code %d\n", statusCode)
+		os.Exit(1)
+		return
+	}
+}
+
+func enabledDisabled(f bool) string {
+	if f {
+		return "ENABLED"
+	}
+	return "DISABLED"
+}

+ 46 - 1
go/cmd/zerotier/cli/peers.go

@@ -13,6 +13,51 @@
 
 
 package cli
 package cli
 
 
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"zerotier/pkg/zerotier"
+)
+
 // Peers CLI command
 // Peers CLI command
-func Peers(basePath, authToken string, args []string) {
+func Peers(basePath, authToken string, args []string, jsonOutput bool) {
+	var peers []zerotier.Peer
+	apiGet(basePath, authToken, "/peer", &peers)
+
+	if jsonOutput {
+		j, _ := json.MarshalIndent(&peers, "", "  ")
+		fmt.Println(string(j))
+	} else {
+		fmt.Printf("<ztaddr>   <ver>   <role> <lat> <link> <lastTX> <lastRX> <path(s)>\n")
+		for _, peer := range peers {
+			role := "LEAF"
+			link := "RELAY"
+			lastTX, lastRX := int64(0), int64(0)
+			address := ""
+			if len(peer.Paths) > 0 {
+				link = "DIRECT"
+				lastTX, lastRX = peer.Clock-peer.Paths[0].LastSend, peer.Clock-peer.Paths[0].LastReceive
+				if lastTX < 0 {
+					lastTX = 0
+				}
+				if lastRX < 0 {
+					lastRX = 0
+				}
+				address = fmt.Sprintf("%s/%d", peer.Paths[0].IP.String(), peer.Paths[0].Port)
+			}
+			fmt.Printf("%.10x %-7s %-6s %-5d %-6s %-8d %-8d %s\n",
+				uint64(peer.Address),
+				fmt.Sprintf("%d.%d.%d", peer.Version[0], peer.Version[1], peer.Version[2]),
+				role,
+				peer.Latency,
+				link,
+				lastTX,
+				lastRX,
+				address,
+			)
+		}
+	}
+
+	os.Exit(0)
 }
 }

+ 39 - 15
go/cmd/zerotier/cli/status.go

@@ -16,7 +16,6 @@ package cli
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
-	"net/http"
 	"os"
 	"os"
 	"zerotier/pkg/zerotier"
 	"zerotier/pkg/zerotier"
 )
 )
@@ -24,25 +23,50 @@ import (
 // Status shows service status info
 // Status shows service status info
 func Status(basePath, authToken string, args []string, jsonOutput bool) {
 func Status(basePath, authToken string, args []string, jsonOutput bool) {
 	var status zerotier.APIStatus
 	var status zerotier.APIStatus
-	statusCode, err := zerotier.APIGet(basePath, zerotier.APISocketName, authToken, "/status", &status)
-	if err != nil {
-		fmt.Printf("FATAL: API response code %d: %s\n", statusCode, err.Error())
-		os.Exit(1)
-		return
-	}
-	if statusCode != http.StatusOK {
-		if statusCode == http.StatusUnauthorized {
-			fmt.Printf("FATAL: API response code %d: unauthorized (authorization token incorrect)\n", statusCode)
-		}
-		fmt.Printf("FATAL: API response code %d\n", statusCode)
-		os.Exit(1)
-		return
-	}
+	apiGet(basePath, authToken, "/status", &status)
 
 
 	if jsonOutput {
 	if jsonOutput {
 		j, _ := json.MarshalIndent(&status, "", "  ")
 		j, _ := json.MarshalIndent(&status, "", "  ")
 		fmt.Println(string(j))
 		fmt.Println(string(j))
 	} else {
 	} else {
+		online := "ONLINE"
+		if !status.Online {
+			online = "OFFLINE"
+		}
+		fmt.Printf("%.10x: %s %s\n", uint64(status.Address), online, status.Version)
+		fmt.Printf("\tports: %d %d %d\n", status.Config.Settings.PrimaryPort, status.Config.Settings.SecondaryPort, status.Config.Settings.TertiaryPort)
+		fmt.Printf("\tport search: %s\n", enabledDisabled(status.Config.Settings.PortSearch))
+		fmt.Printf("\tport mapping (uPnP/NAT-PMP): %s\n", enabledDisabled(status.Config.Settings.PortMapping))
+		fmt.Printf("\tmultipath mode: %d\n", status.Config.Settings.MuiltipathMode)
+		fmt.Printf("\tblacklisted interface prefixes: ")
+		for i, bl := range status.Config.Settings.InterfacePrefixBlacklist {
+			if i > 0 {
+				fmt.Print(',')
+			}
+			fmt.Print(bl)
+		}
+		fmt.Printf("\n\texplicit external addresses: ")
+		for i, ea := range status.Config.Settings.ExplicitAddresses {
+			if i > 0 {
+				fmt.Print(',')
+			}
+			fmt.Print(ea.String())
+		}
+		fmt.Printf("\n\tsystem interface addresses: ")
+		for i, a := range status.InterfaceAddresses {
+			if i > 0 {
+				fmt.Print(',')
+			}
+			fmt.Print(a.String())
+		}
+		fmt.Printf("\n\tmapped external addresses: ")
+		for i, a := range status.MappedExternalAddresses {
+			if i > 0 {
+				fmt.Print(',')
+			}
+			fmt.Print(a.String())
+		}
+		fmt.Printf("\n\tidentity: %s\n", status.Identity.String())
 	}
 	}
 
 
 	os.Exit(0)
 	os.Exit(0)

+ 1 - 1
go/cmd/zerotier/zerotier.go

@@ -111,7 +111,7 @@ func main() {
 		cli.Status(basePath, authToken, cmdArgs, *jflag)
 		cli.Status(basePath, authToken, cmdArgs, *jflag)
 	case "peers", "listpeers":
 	case "peers", "listpeers":
 		authTokenRequired(authToken)
 		authTokenRequired(authToken)
-		cli.Peers(basePath, authToken, cmdArgs)
+		cli.Peers(basePath, authToken, cmdArgs, *jflag)
 	case "roots":
 	case "roots":
 		authTokenRequired(authToken)
 		authTokenRequired(authToken)
 		cli.Roots(basePath, authToken, cmdArgs)
 		cli.Roots(basePath, authToken, cmdArgs)

+ 46 - 2
go/pkg/zerotier/api.go

@@ -96,6 +96,7 @@ type APIStatus struct {
 
 
 // APINetwork is the object returned by API network inquiries
 // APINetwork is the object returned by API network inquiries
 type APINetwork struct {
 type APINetwork struct {
+	ID                     NetworkID
 	Config                 *NetworkConfig
 	Config                 *NetworkConfig
 	Settings               *NetworkLocalSettings
 	Settings               *NetworkLocalSettings
 	MulticastSubscriptions []*MulticastGroup
 	MulticastSubscriptions []*MulticastGroup
@@ -104,6 +105,20 @@ type APINetwork struct {
 	TapDeviceEnabled       bool
 	TapDeviceEnabled       bool
 }
 }
 
 
+func apiNetworkFromNetwork(n *Network) *APINetwork {
+	var nn APINetwork
+	nn.ID = n.ID()
+	c := n.Config()
+	nn.Config = &c
+	ls := n.LocalSettings()
+	nn.Settings = &ls
+	nn.MulticastSubscriptions = n.MulticastSubscriptions()
+	nn.TapDeviceType = n.Tap().Type()
+	nn.TapDeviceName = n.Tap().DeviceName()
+	nn.TapDeviceEnabled = n.Tap().Enabled()
+	return &nn
+}
+
 func apiSetStandardHeaders(out http.ResponseWriter) {
 func apiSetStandardHeaders(out http.ResponseWriter) {
 	now := time.Now().UTC()
 	now := time.Now().UTC()
 	h := out.Header()
 	h := out.Header()
@@ -215,6 +230,7 @@ func createAPIServer(basePath string, node *Node) (*http.Server, error) {
 		if req.Method == http.MethodPost || req.Method == http.MethodPut {
 		if req.Method == http.MethodPost || req.Method == http.MethodPut {
 			var c LocalConfig
 			var c LocalConfig
 			if apiReadObj(out, req, &c) == nil {
 			if apiReadObj(out, req, &c) == nil {
+				node.SetLocalConfig(&c)
 				apiSendObj(out, req, http.StatusOK, node.LocalConfig())
 				apiSendObj(out, req, http.StatusOK, node.LocalConfig())
 			}
 			}
 		} else if req.Method == http.MethodGet || req.Method == http.MethodHead {
 		} else if req.Method == http.MethodGet || req.Method == http.MethodHead {
@@ -279,12 +295,40 @@ func createAPIServer(basePath string, node *Node) (*http.Server, error) {
 		if req.Method == http.MethodPost || req.Method == http.MethodPut {
 		if req.Method == http.MethodPost || req.Method == http.MethodPut {
 			if queriedID == 0 {
 			if queriedID == 0 {
 				apiSendObj(out, req, http.StatusBadRequest, nil)
 				apiSendObj(out, req, http.StatusBadRequest, nil)
+			} else {
+				var nw APINetwork
+				if apiReadObj(out, req, &nw) == nil {
+					n := node.GetNetwork(nw.ID)
+					if n == nil {
+						n, err := node.Join(nw.ID, nw.Settings, nil)
+						if err != nil {
+							apiSendObj(out, req, http.StatusBadRequest, nil)
+						} else {
+							apiSendObj(out, req, http.StatusOK, apiNetworkFromNetwork(n))
+						}
+					} else {
+						if nw.Settings != nil {
+							n.SetLocalSettings(nw.Settings)
+						}
+						apiSendObj(out, req, http.StatusOK, apiNetworkFromNetwork(n))
+					}
+				}
 			}
 			}
 		} else if req.Method == http.MethodGet || req.Method == http.MethodHead {
 		} else if req.Method == http.MethodGet || req.Method == http.MethodHead {
+			networks := node.Networks()
 			if queriedID == 0 { // no queried ID lists all networks
 			if queriedID == 0 { // no queried ID lists all networks
-				networks := node.Networks()
-				apiSendObj(out, req, http.StatusOK, networks)
+				nws := make([]*APINetwork, 0, len(networks))
+				for _, nw := range networks {
+					nws = append(nws, apiNetworkFromNetwork(nw))
+				}
+				apiSendObj(out, req, http.StatusOK, nws)
 			} else {
 			} else {
+				for _, nw := range networks {
+					if nw.ID() == queriedID {
+						apiSendObj(out, req, http.StatusOK, apiNetworkFromNetwork(nw))
+						break
+					}
+				}
 			}
 			}
 		} else {
 		} else {
 			out.Header().Set("Allow", "GET, HEAD, PUT, POST")
 			out.Header().Set("Allow", "GET, HEAD, PUT, POST")

+ 7 - 0
go/pkg/zerotier/network.go

@@ -194,6 +194,13 @@ func (n *Network) Config() NetworkConfig {
 // SetLocalSettings modifies this network's local settings
 // SetLocalSettings modifies this network's local settings
 func (n *Network) SetLocalSettings(ls *NetworkLocalSettings) { n.updateConfig(nil, ls) }
 func (n *Network) SetLocalSettings(ls *NetworkLocalSettings) { n.updateConfig(nil, ls) }
 
 
+// LocalSettings gets this network's current local settings
+func (n *Network) LocalSettings() NetworkLocalSettings {
+	n.configLock.RLock()
+	defer n.configLock.RUnlock()
+	return n.settings
+}
+
 // MulticastSubscribe subscribes to a multicast group
 // MulticastSubscribe subscribes to a multicast group
 func (n *Network) MulticastSubscribe(mg *MulticastGroup) {
 func (n *Network) MulticastSubscribe(mg *MulticastGroup) {
 	n.node.log.Printf("%.16x joined multicast group %s", mg.String())
 	n.node.log.Printf("%.16x joined multicast group %s", mg.String())

+ 40 - 21
go/pkg/zerotier/node.go

@@ -464,10 +464,13 @@ func (n *Node) SetLocalConfig(lc *LocalConfig) (restartRequired bool, err error)
 
 
 // Join joins a network
 // Join joins a network
 // If tap is nil, the default system tap for this OS/platform is used (if available).
 // If tap is nil, the default system tap for this OS/platform is used (if available).
-func (n *Node) Join(nwid uint64, tap Tap) (*Network, error) {
+func (n *Node) Join(nwid NetworkID, settings *NetworkLocalSettings, tap Tap) (*Network, error) {
 	n.networksLock.RLock()
 	n.networksLock.RLock()
-	if nw, have := n.networks[NetworkID(nwid)]; have {
+	if nw, have := n.networks[nwid]; have {
 		n.log.Printf("join network %.16x ignored: already a member", nwid)
 		n.log.Printf("join network %.16x ignored: already a member", nwid)
+		if settings != nil {
+			nw.SetLocalSettings(settings)
+		}
 		return nw, nil
 		return nw, nil
 	}
 	}
 	n.networksLock.RUnlock()
 	n.networksLock.RUnlock()
@@ -488,8 +491,11 @@ func (n *Node) Join(nwid uint64, tap Tap) (*Network, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 	n.networksLock.Lock()
 	n.networksLock.Lock()
-	n.networks[NetworkID(nwid)] = nw
+	n.networks[nwid] = nw
 	n.networksLock.Unlock()
 	n.networksLock.Unlock()
+	if settings != nil {
+		nw.SetLocalSettings(settings)
+	}
 
 
 	return nw, nil
 	return nw, nil
 }
 }
@@ -504,6 +510,14 @@ func (n *Node) Leave(nwid uint64) error {
 	return nil
 	return nil
 }
 }
 
 
+// GetNetwork looks up a network by ID or returns nil if not joined
+func (n *Node) GetNetwork(nwid NetworkID) *Network {
+	n.networksLock.RLock()
+	nw := n.networks[nwid]
+	n.networksLock.RUnlock()
+	return nw
+}
+
 // Networks returns a list of networks that this node has joined
 // Networks returns a list of networks that this node has joined
 func (n *Node) Networks() []*Network {
 func (n *Node) Networks() []*Network {
 	var nws []*Network
 	var nws []*Network
@@ -613,30 +627,35 @@ func (n *Node) Peers() []*Peer {
 			p2.Paths = make([]Path, 0, int(p.pathCount))
 			p2.Paths = make([]Path, 0, int(p.pathCount))
 			for j := uintptr(0); j < uintptr(p.pathCount); j++ {
 			for j := uintptr(0); j < uintptr(p.pathCount); j++ {
 				pt := &p.paths[j]
 				pt := &p.paths[j]
-				a := sockaddrStorageToUDPAddr(&pt.address)
-				if a != nil {
-					p2.Paths = append(p2.Paths, Path{
-						IP:                     a.IP,
-						Port:                   a.Port,
-						LastSend:               int64(pt.lastSend),
-						LastReceive:            int64(pt.lastReceive),
-						TrustedPathID:          uint64(pt.trustedPathId),
-						Latency:                float32(pt.latency),
-						PacketDelayVariance:    float32(pt.packetDelayVariance),
-						ThroughputDisturbCoeff: float32(pt.throughputDisturbCoeff),
-						PacketErrorRatio:       float32(pt.packetErrorRatio),
-						PacketLossRatio:        float32(pt.packetLossRatio),
-						Stability:              float32(pt.stability),
-						Throughput:             uint64(pt.throughput),
-						MaxThroughput:          uint64(pt.maxThroughput),
-						Allocation:             float32(pt.allocation),
-					})
+				if pt.alive != 0 {
+					a := sockaddrStorageToUDPAddr(&pt.address)
+					if a != nil {
+						p2.Paths = append(p2.Paths, Path{
+							IP:                     a.IP,
+							Port:                   a.Port,
+							LastSend:               int64(pt.lastSend),
+							LastReceive:            int64(pt.lastReceive),
+							TrustedPathID:          uint64(pt.trustedPathId),
+							Latency:                float32(pt.latency),
+							PacketDelayVariance:    float32(pt.packetDelayVariance),
+							ThroughputDisturbCoeff: float32(pt.throughputDisturbCoeff),
+							PacketErrorRatio:       float32(pt.packetErrorRatio),
+							PacketLossRatio:        float32(pt.packetLossRatio),
+							Stability:              float32(pt.stability),
+							Throughput:             uint64(pt.throughput),
+							MaxThroughput:          uint64(pt.maxThroughput),
+							Allocation:             float32(pt.allocation),
+						})
+					}
 				}
 				}
 			}
 			}
+			sort.Slice(p2.Paths, func(a, b int) bool { return p2.Paths[a].LastReceive < p2.Paths[b].LastReceive })
+			p2.Clock = TimeMs()
 			peers = append(peers, p2)
 			peers = append(peers, p2)
 		}
 		}
 		C.ZT_Node_freeQueryResult(unsafe.Pointer(n.zn), unsafe.Pointer(pl))
 		C.ZT_Node_freeQueryResult(unsafe.Pointer(n.zn), unsafe.Pointer(pl))
 	}
 	}
+	sort.Slice(peers, func(a, b int) bool { return peers[a].Address < peers[b].Address })
 	return peers
 	return peers
 }
 }
 
 

+ 1 - 0
go/pkg/zerotier/peer.go

@@ -20,4 +20,5 @@ type Peer struct {
 	Latency int
 	Latency int
 	Role    int
 	Role    int
 	Paths   []Path
 	Paths   []Path
+	Clock   int64
 }
 }

+ 2 - 2
include/ZeroTierCore.h

@@ -1234,9 +1234,9 @@ typedef struct
 	char *ifname;
 	char *ifname;
 
 
 	/**
 	/**
-	 * Is path expired?
+	 * Is path alive?
 	 */
 	 */
-	int expired;
+	int alive;
 
 
 	/**
 	/**
 	 * Is path preferred?
 	 * Is path preferred?

+ 2 - 1
node/Node.cpp

@@ -537,6 +537,7 @@ ZT_PeerList *Node::peers() const
 			p->latency = -1;
 			p->latency = -1;
 		p->role = RR->topology->isRoot((*pi)->identity()) ? ZT_PEER_ROLE_PLANET : ZT_PEER_ROLE_LEAF;
 		p->role = RR->topology->isRoot((*pi)->identity()) ? ZT_PEER_ROLE_PLANET : ZT_PEER_ROLE_LEAF;
 
 
+		const int64_t now = _now;
 		std::vector< SharedPtr<Path> > paths((*pi)->paths(_now));
 		std::vector< SharedPtr<Path> > paths((*pi)->paths(_now));
 		SharedPtr<Path> bestp((*pi)->getAppropriatePath(_now,false));
 		SharedPtr<Path> bestp((*pi)->getAppropriatePath(_now,false));
 		p->hadAggregateLink |= (*pi)->hasAggregateLink();
 		p->hadAggregateLink |= (*pi)->hasAggregateLink();
@@ -546,7 +547,7 @@ ZT_PeerList *Node::peers() const
 			p->paths[p->pathCount].lastSend = (*path)->lastOut();
 			p->paths[p->pathCount].lastSend = (*path)->lastOut();
 			p->paths[p->pathCount].lastReceive = (*path)->lastIn();
 			p->paths[p->pathCount].lastReceive = (*path)->lastIn();
 			p->paths[p->pathCount].trustedPathId = RR->topology->getOutboundPathTrust((*path)->address());
 			p->paths[p->pathCount].trustedPathId = RR->topology->getOutboundPathTrust((*path)->address());
-			p->paths[p->pathCount].expired = 0;
+			p->paths[p->pathCount].alive = (*path)->alive(now) ? 1 : 0;
 			p->paths[p->pathCount].preferred = ((*path) == bestp) ? 1 : 0;
 			p->paths[p->pathCount].preferred = ((*path) == bestp) ? 1 : 0;
 			p->paths[p->pathCount].latency = (float)(*path)->latency();
 			p->paths[p->pathCount].latency = (float)(*path)->latency();
 			p->paths[p->pathCount].packetDelayVariance = (*path)->packetDelayVariance();
 			p->paths[p->pathCount].packetDelayVariance = (*path)->packetDelayVariance();