This commit is contained in:
Adam Ierymenko 2019-09-26 09:34:31 -07:00
parent 5175636d36
commit 7061f13b24
No known key found for this signature in database
GPG Key ID: C8877CF2D7A5D7F3
10 changed files with 229 additions and 43 deletions

View File

@ -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"
}

View File

@ -13,6 +13,51 @@
package cli
import (
"encoding/json"
"fmt"
"os"
"zerotier/pkg/zerotier"
)
// 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)
}

View File

@ -16,7 +16,6 @@ package cli
import (
"encoding/json"
"fmt"
"net/http"
"os"
"zerotier/pkg/zerotier"
)
@ -24,25 +23,50 @@ import (
// Status shows service status info
func Status(basePath, authToken string, args []string, jsonOutput bool) {
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 {
j, _ := json.MarshalIndent(&status, "", " ")
fmt.Println(string(j))
} 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)

View File

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

View File

@ -96,6 +96,7 @@ type APIStatus struct {
// APINetwork is the object returned by API network inquiries
type APINetwork struct {
ID NetworkID
Config *NetworkConfig
Settings *NetworkLocalSettings
MulticastSubscriptions []*MulticastGroup
@ -104,6 +105,20 @@ type APINetwork struct {
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) {
now := time.Now().UTC()
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 {
var c LocalConfig
if apiReadObj(out, req, &c) == nil {
node.SetLocalConfig(&c)
apiSendObj(out, req, http.StatusOK, node.LocalConfig())
}
} 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 queriedID == 0 {
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 {
networks := node.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 {
for _, nw := range networks {
if nw.ID() == queriedID {
apiSendObj(out, req, http.StatusOK, apiNetworkFromNetwork(nw))
break
}
}
}
} else {
out.Header().Set("Allow", "GET, HEAD, PUT, POST")

View File

@ -194,6 +194,13 @@ func (n *Network) Config() NetworkConfig {
// SetLocalSettings modifies this network's local settings
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
func (n *Network) MulticastSubscribe(mg *MulticastGroup) {
n.node.log.Printf("%.16x joined multicast group %s", mg.String())

View File

@ -464,10 +464,13 @@ func (n *Node) SetLocalConfig(lc *LocalConfig) (restartRequired bool, err error)
// Join joins a network
// 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()
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)
if settings != nil {
nw.SetLocalSettings(settings)
}
return nw, nil
}
n.networksLock.RUnlock()
@ -488,8 +491,11 @@ func (n *Node) Join(nwid uint64, tap Tap) (*Network, error) {
return nil, err
}
n.networksLock.Lock()
n.networks[NetworkID(nwid)] = nw
n.networks[nwid] = nw
n.networksLock.Unlock()
if settings != nil {
nw.SetLocalSettings(settings)
}
return nw, nil
}
@ -504,6 +510,14 @@ func (n *Node) Leave(nwid uint64) error {
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
func (n *Node) Networks() []*Network {
var nws []*Network
@ -613,30 +627,35 @@ func (n *Node) Peers() []*Peer {
p2.Paths = make([]Path, 0, int(p.pathCount))
for j := uintptr(0); j < uintptr(p.pathCount); 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)
}
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
}

View File

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

View File

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

View File

@ -537,6 +537,7 @@ ZT_PeerList *Node::peers() const
p->latency = -1;
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));
SharedPtr<Path> bestp((*pi)->getAppropriatePath(_now,false));
p->hadAggregateLink |= (*pi)->hasAggregateLink();
@ -546,7 +547,7 @@ ZT_PeerList *Node::peers() const
p->paths[p->pathCount].lastSend = (*path)->lastOut();
p->paths[p->pathCount].lastReceive = (*path)->lastIn();
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].latency = (float)(*path)->latency();
p->paths[p->pathCount].packetDelayVariance = (*path)->packetDelayVariance();