diff --git a/go/cmd/zerotier/cli/addroot.go b/go/cmd/zerotier/cli/addroot.go deleted file mode 100644 index fae7a249a..000000000 --- a/go/cmd/zerotier/cli/addroot.go +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c)2013-2020 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: 2024-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 - -// AddRoot CLI command -func AddRoot(basePath, authToken string, args []string) { -} diff --git a/go/cmd/zerotier/cli/help.go b/go/cmd/zerotier/cli/help.go index 3a24b7962..f3a3bb2cd 100644 --- a/go/cmd/zerotier/cli/help.go +++ b/go/cmd/zerotier/cli/help.go @@ -41,13 +41,13 @@ Commands: service Start as service status Show ZeroTier status and config peers Show VL1 peers and link information + roots Show only root peers + addroot [IP/port] Add root with optional bootstrap IP + removeroot Remove root join Join a virtual network leave Leave a virtual network networks List joined VL2 virtual networks network Show verbose network info - addroot [IP/port] Add root with optional bootstrap IP - removeroot Remove root - roots Show configured VL1 root servers set [option] [value] Get or set a network config option manageips Is IP management allowed? manageroutes Is route management allowed? diff --git a/go/cmd/zerotier/cli/peers.go b/go/cmd/zerotier/cli/peers.go index 5aa4c8e98..dccd8d377 100644 --- a/go/cmd/zerotier/cli/peers.go +++ b/go/cmd/zerotier/cli/peers.go @@ -16,45 +16,52 @@ package cli import ( "fmt" "os" + "strings" "zerotier/pkg/zerotier" ) -// Peers CLI command -func Peers(basePath, authToken string, args []string, jsonOutput bool) { +// Peers CLI command (also used for 'roots' command with rootsOnly set to true) +func Peers(basePath, authToken string, args []string, jsonOutput bool, rootsOnly bool) { var peers []zerotier.Peer - clock := apiGet(basePath, authToken, "/peer", &peers) + apiGet(basePath, authToken, "/peer", &peers) + + if rootsOnly { + roots := make([]zerotier.Peer, 0, len(peers)) + for i := range peers { + if peers[i].Root { + roots = append(roots, peers[i]) + } + } + peers = roots + } if jsonOutput { fmt.Println(jsonDump(&peers)) } else { - fmt.Printf("
\n") + fmt.Printf("
\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 = clock-peer.Paths[0].LastSend, 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) + root := "" + if peer.Root { + root = " *" } - fmt.Printf("%.10x %-7s %-6s %-5d %-6s %-8d %-8d %s\n", + + var paths strings.Builder + if len(peer.Paths) > 0 { + if paths.Len() > 0 { + paths.WriteRune(' ') + } + paths.WriteString(fmt.Sprintf("%s/%d", peer.Paths[0].IP.String(), peer.Paths[0].Port)) + } else { + paths.WriteString("(relayed)") + } + + fmt.Printf("%.10x %-7s %-6s %-9d %s\n", uint64(peer.Address), fmt.Sprintf("%d.%d.%d", peer.Version[0], peer.Version[1], peer.Version[2]), - role, + root, peer.Latency, - link, - lastTX, - lastRX, - address, - ) + paths.String()) } } diff --git a/go/cmd/zerotier/cli/removeroot.go b/go/cmd/zerotier/cli/removeroot.go deleted file mode 100644 index 3573271b4..000000000 --- a/go/cmd/zerotier/cli/removeroot.go +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c)2013-2020 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: 2024-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 - -// RemoveRoot CLI command -func RemoveRoot(basePath, authToken string, args []string) { -} diff --git a/go/cmd/zerotier/cli/roots.go b/go/cmd/zerotier/cli/roots.go deleted file mode 100644 index edacddadf..000000000 --- a/go/cmd/zerotier/cli/roots.go +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c)2013-2020 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: 2024-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 - -// Roots CLI command -func Roots(basePath, authToken string, args []string, jsonOutput bool) { -} diff --git a/go/cmd/zerotier/cli/setroot.go b/go/cmd/zerotier/cli/setroot.go new file mode 100644 index 000000000..281ef7b4f --- /dev/null +++ b/go/cmd/zerotier/cli/setroot.go @@ -0,0 +1,52 @@ +/* + * Copyright (c)2013-2020 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: 2024-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" + "os" + + "zerotier/pkg/zerotier" +) + +// SetRoot CLI command, used for addroot and removeroot. +func SetRoot(basePath, authToken string, args []string, root bool) { + if len(args) < 1 || len(args) > 2 { + Help() + os.Exit(1) + } + + id := readIdentity(args[0]) + if id == nil { + fmt.Printf("ERROR: invalid identity '%s' (tried literal or reading as file)\n",args[0]) + os.Exit(1) + } + + var bootstrap *zerotier.InetAddress + if len(args) == 2 { + bootstrap = zerotier.NewInetAddressFromString(args[1]) + if bootstrap == nil || bootstrap.Nil() { + fmt.Printf("ERROR: invalid bootstrap address '%s'\n",args[1]) + os.Exit(1) + } + } + + var peer zerotier.PeerMutableFields + peer.Identity = id + peer.Bootstrap = bootstrap + peer.Root = &root + apiPost(basePath, authToken, "/peer/"+id.Address().String(), &peer, nil) + fmt.Printf("OK %s", id.String()) + os.Exit(0) +} diff --git a/go/cmd/zerotier/zerotier.go b/go/cmd/zerotier/zerotier.go index 720c2f27d..293bbffd2 100644 --- a/go/cmd/zerotier/zerotier.go +++ b/go/cmd/zerotier/zerotier.go @@ -133,16 +133,16 @@ func main() { cli.Status(basePath, authToken, cmdArgs, *jflag) case "peers", "listpeers": authTokenRequired(authToken) - cli.Peers(basePath, authToken, cmdArgs, *jflag) + cli.Peers(basePath, authToken, cmdArgs, *jflag, false) case "roots", "listroots": authTokenRequired(authToken) - cli.Roots(basePath, authToken, cmdArgs, *jflag) + cli.Peers(basePath, authToken, cmdArgs, *jflag, true) case "addroot": authTokenRequired(authToken) - cli.AddRoot(basePath, authToken, cmdArgs) + cli.SetRoot(basePath, authToken, cmdArgs, true) case "removeroot": authTokenRequired(authToken) - cli.RemoveRoot(basePath, authToken, cmdArgs) + cli.SetRoot(basePath, authToken, cmdArgs, false) case "identity": cli.Identity(cmdArgs) case "networks", "listnetworks": diff --git a/go/pkg/zerotier/api.go b/go/pkg/zerotier/api.go index 32400317a..074a3b541 100644 --- a/go/pkg/zerotier/api.go +++ b/go/pkg/zerotier/api.go @@ -226,12 +226,6 @@ func apiCheckAuth(out http.ResponseWriter, req *http.Request, token string) bool return false } -type peerMutableFields struct { - Identity *Identity `json:"identity"` - Role *int `json:"role"` - Bootstrap *InetAddress `json:"bootstrap,omitempty"` -} - // createAPIServer creates and starts an HTTP server for a given node func createAPIServer(basePath string, node *Node) (*http.Server, *http.Server, error) { // Read authorization token, automatically generating one if it's missing @@ -372,14 +366,14 @@ func createAPIServer(basePath string, node *Node) (*http.Server, *http.Server, e _ = apiSendObj(out, req, http.StatusNotFound, &APIErr{"peer not found"}) return } - var peerChanges peerMutableFields + var peerChanges PeerMutableFields if apiReadObj(out, req, &peerChanges) == nil { - if peerChanges.Role != nil || peerChanges.Bootstrap != nil { + if peerChanges.Root != nil || peerChanges.Bootstrap != nil { peers := node.Peers() for _, p := range peers { if p.Address == queriedID && (peerChanges.Identity == nil || peerChanges.Identity.Equals(p.Identity)) { - if peerChanges.Role != nil && *peerChanges.Role != p.Role { - if *peerChanges.Role == PeerRoleRoot { + if peerChanges.Root != nil && *peerChanges.Root != p.Root { + if *peerChanges.Root { _ = node.AddRoot(p.Identity, peerChanges.Bootstrap) } else { node.RemoveRoot(p.Identity) diff --git a/go/pkg/zerotier/inetaddress.go b/go/pkg/zerotier/inetaddress.go index 88e77fa94..a33a4891a 100644 --- a/go/pkg/zerotier/inetaddress.go +++ b/go/pkg/zerotier/inetaddress.go @@ -105,6 +105,11 @@ type InetAddress struct { Port int } +// Nil returns true if this InetAddress is empty. +func (ina *InetAddress) Nil() bool { + return len(ina.IP) == 0 +} + // Less returns true if this IP/port is lexicographically less than another func (ina *InetAddress) Less(i2 *InetAddress) bool { c := bytes.Compare(ina.IP, i2.IP) diff --git a/go/pkg/zerotier/node.go b/go/pkg/zerotier/node.go index 0e61511d7..e1da9b2aa 100644 --- a/go/pkg/zerotier/node.go +++ b/go/pkg/zerotier/node.go @@ -80,6 +80,7 @@ var ( // This map is used to get the Go Node object from a pointer passed back in via C callbacks nodesByUserPtr = make(map[uintptr]*Node) + nodesByUserPtrCtr = uintptr(0) nodesByUserPtrLock sync.RWMutex ) @@ -130,6 +131,9 @@ type Node struct { // runWaitGroup is used to wait for all node goroutines on shutdown runWaitGroup sync.WaitGroup + + // an arbitrary uintptr given to the core as its pointer back to Go's Node instance + cPtr uintptr } // NewNode creates and initializes a new instance of the ZeroTier node service @@ -242,17 +246,18 @@ func NewNode(basePath string) (n *Node, err error) { } nodesByUserPtrLock.Lock() - nodesByUserPtr[uintptr(unsafe.Pointer(n))] = n + nodesByUserPtrCtr++ + n.cPtr = nodesByUserPtrCtr + nodesByUserPtr[n.cPtr] = n nodesByUserPtrLock.Unlock() - // Instantiate GoNode and friends from the land of C/C++ cPath := C.CString(basePath) - n.gn = C.ZT_GoNode_new(cPath, C.uintptr_t(uintptr(unsafe.Pointer(n)))) + n.gn = C.ZT_GoNode_new(cPath, C.uintptr_t(n.cPtr)) C.free(unsafe.Pointer(cPath)) if n.gn == nil { n.infoLog.Println("FATAL: node initialization failed") nodesByUserPtrLock.Lock() - delete(nodesByUserPtr, uintptr(unsafe.Pointer(n))) + delete(nodesByUserPtr, n.cPtr) nodesByUserPtrLock.Unlock() return nil, ErrNodeInitFailed } @@ -261,7 +266,7 @@ func NewNode(basePath string) (n *Node, err error) { if err != nil { n.infoLog.Printf("FATAL: error obtaining node's identity") nodesByUserPtrLock.Lock() - delete(nodesByUserPtr, uintptr(unsafe.Pointer(n))) + delete(nodesByUserPtr, n.cPtr) nodesByUserPtrLock.Unlock() C.ZT_GoNode_delete(n.gn) return nil, err @@ -600,7 +605,7 @@ func (n *Node) Peers() []*Peer { p2.IdentityHash = hex.EncodeToString((*[48]byte)(unsafe.Pointer(&p.identityHash[0]))[:]) p2.Version = [3]int{int(p.versionMajor), int(p.versionMinor), int(p.versionRev)} p2.Latency = int(p.latency) - p2.Role = int(p.role) + p2.Root = p.root != 0 p2.Bootstrap = NewInetAddressFromSockaddr(unsafe.Pointer(&p.bootstrap)) p2.Paths = make([]Path, 0, int(p.pathCount)) diff --git a/go/pkg/zerotier/peer.go b/go/pkg/zerotier/peer.go index ebce9c621..c2a18a805 100644 --- a/go/pkg/zerotier/peer.go +++ b/go/pkg/zerotier/peer.go @@ -13,14 +13,6 @@ package zerotier -// Peer roles must be the same as in ZeroTierCore.h. - -// PeerRoleLeaf indicates a normal leaf node. -const PeerRoleLeaf = 0 - -// PeerRoleRoot indicates a root peer. -const PeerRoleRoot = 1 - // Peer is another ZeroTier node type Peer struct { Address Address `json:"address"` @@ -28,7 +20,14 @@ type Peer struct { IdentityHash string `json:"identityHash"` Version [3]int `json:"version"` Latency int `json:"latency"` - Role int `json:"role"` + Root bool `json:"root"` Bootstrap *InetAddress `json:"bootstrap,omitempty"` Paths []Path `json:"paths,omitempty"` } + +// PeerMutableFields contains only the mutable fields of Peer as nullable pointers. +type PeerMutableFields struct { + Identity *Identity `json:"identity"` + Root *bool `json:"root"` + Bootstrap *InetAddress `json:"bootstrap,omitempty"` +} diff --git a/include/ZeroTierCore.h b/include/ZeroTierCore.h index 4dadb7e4d..85bc402d0 100644 --- a/include/ZeroTierCore.h +++ b/include/ZeroTierCore.h @@ -1323,15 +1323,6 @@ typedef struct int preferred; } ZT_PeerPhysicalPath; -/** - * What trust hierarchy role does this peer have? - */ -enum ZT_PeerRole -{ - ZT_PEER_ROLE_LEAF = 0, - ZT_PEER_ROLE_ROOT = 1 -}; - /** * Peer status result buffer */ @@ -1373,9 +1364,9 @@ typedef struct int latency; /** - * What trust hierarchy role does this device have? + * If non-zero this peer is a root */ - enum ZT_PeerRole role; + int root; /** * Bootstrap address diff --git a/node/Node.cpp b/node/Node.cpp index ee311bbca..6e44302fc 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -446,7 +446,7 @@ ZT_PeerList *Node::peers() const p->latency = (int)(*pi)->latency(); if (p->latency >= 0xffff) p->latency = -1; - p->role = RR->topology->isRoot((*pi)->identity()) ? ZT_PEER_ROLE_ROOT : ZT_PEER_ROLE_LEAF; + p->root = RR->topology->isRoot((*pi)->identity()) ? 1 : 0; memcpy(&p->bootstrap,&((*pi)->bootstrap()),sizeof(sockaddr_storage)); std::vector< SharedPtr > paths;