A bunch of CLI work

This commit is contained in:
Adam Ierymenko 2019-09-30 16:12:08 -07:00
parent c4504fd3ff
commit 7fc78129f4
No known key found for this signature in database
GPG Key ID: C8877CF2D7A5D7F3
19 changed files with 225 additions and 153 deletions

View File

@ -14,6 +14,7 @@
package cli package cli
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
@ -43,3 +44,8 @@ func enabledDisabled(f bool) string {
} }
return "DISABLED" return "DISABLED"
} }
func jsonDump(obj interface{}) string {
j, _ := json.MarshalIndent(obj, "", " ")
return string(j)
}

View File

@ -15,6 +15,7 @@ package cli
import ( import (
"fmt" "fmt"
"zerotier/pkg/zerotier" "zerotier/pkg/zerotier"
) )
@ -26,7 +27,7 @@ Licensed under the ZeroTier BSL (see LICENSE.txt)`, zerotier.CoreVersionMajor, z
func Help() { func Help() {
fmt.Println(copyrightText) fmt.Println(copyrightText)
fmt.Println(` fmt.Println(`
Usage: zerotier [-options] <command> [-options] [command args] Usage: zerotier [-options] <command> [command args]
Global Options: Global Options:
-j Output raw JSON where applicable -j Output raw JSON where applicable
@ -40,11 +41,18 @@ Commands:
status Show ZeroTier service status and config status Show ZeroTier service status and config
peers Show VL1 peers peers Show VL1 peers
roots Show VL1 root servers roots Show VL1 root servers
addroot <locator> [<name>] Add a VL1 root addroot <locator> [name] Add a VL1 root
removeroot <name> Remove a VL1 root removeroot <name> Remove a VL1 root
makelocator <secret> <address> [...] Make and sign a locator locator <command> [args] Locator management commands
makelocatordnskey Create a new secure DNS name and key new <identity> <address> [...] Create and sign a locator
makelocatordns <key> <locator> Make DNS TXT records for a locator newdnskey Create a secure DNS name and secret
getdns <key> <locator> Create secure DNS TXT records
identity <command> [args] Identity management commands
new Create new identity (including secret)
getpublic <identity> Extract only public part of identity
validate <identity> Locally validate an identity
sign <identity> <file> Sign a file with an identity's key
verify <identity> <file> <sig> Verify a signature
networks Show joined VL2 virtual networks networks Show joined VL2 virtual networks
join <network ID> Join a virtual network join <network ID> Join a virtual network
leave <network ID> Leave a virtual network leave <network ID> Leave a virtual network

View File

@ -13,6 +13,6 @@
package cli package cli
// MakeLocator CLI command // Identity command
func MakeLocator(args []string) { func Identity(args []string) {
} }

View File

@ -0,0 +1,135 @@
/*
* 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 (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"zerotier/pkg/zerotier"
)
func locatorNew(args []string) {
if len(args) < 2 {
Help()
os.Exit(1)
}
identityData, err := ioutil.ReadFile(args[0])
if err != nil {
fmt.Printf("FATAL: unable to read identity: %s\n", err.Error())
os.Exit(1)
}
identity, err := zerotier.NewIdentityFromString(string(identityData))
if err != nil {
fmt.Printf("FATAL: invalid identity: %s\n", err.Error())
os.Exit(1)
}
if !identity.HasPrivate() {
fmt.Println("FATAL: identity does not contain secret key")
os.Exit(1)
}
var virt []*zerotier.Identity
var phys []*zerotier.InetAddress
for i := 1; i < len(args); i++ {
if strings.Contains(args[i], "/") {
a := zerotier.NewInetAddressFromString(args[i])
if a == nil {
fmt.Printf("FATAL: IP/port address '%s' is not valid\n", args[i])
os.Exit(1)
}
phys = append(phys, a)
} else {
a, err := zerotier.NewIdentityFromString(args[i])
if err != nil {
fmt.Printf("FATAL: identity (virtual address) '%s' is not valid: %s\n", args[i], err.Error())
os.Exit(1)
}
virt = append(virt, a)
}
}
loc, err := zerotier.NewLocator(identity, virt, phys)
if err != nil {
fmt.Printf("FATAL: internal error creating locator: %s\n", err.Error())
os.Exit(1)
}
fmt.Println(jsonDump(loc))
}
func locatorNewDNSKey(args []string) {
if len(args) != 0 {
Help()
os.Exit(0)
}
sk, err := zerotier.NewLocatorDNSSigningKey()
if err != nil {
fmt.Printf("FATAL: error creating secure DNS signing key: %s", err.Error())
os.Exit(1)
}
fmt.Println(jsonDump(sk))
os.Exit(0)
}
func locatorGetDNS(args []string) {
if len(args) < 2 {
Help()
os.Exit(1)
}
keyData, err := ioutil.ReadFile(args[0])
if err != nil {
fmt.Printf("FATAL: unable to read secure DNS key file: %s\n", err.Error())
os.Exit(1)
}
var sk zerotier.LocatorDNSSigningKey
err = json.Unmarshal(keyData, &sk)
if err != nil {
fmt.Printf("FATAL: DNS key file invalid: %s", err.Error())
os.Exit(1)
}
locData, err := ioutil.ReadFile(args[1])
if err != nil {
fmt.Printf("FATAL: unable to read locator: %s\n", err.Error())
os.Exit(1)
}
var loc zerotier.Locator
err = json.Unmarshal(locData, &loc)
if err != nil {
fmt.Printf("FATAL: locator invalid: %s", err.Error())
os.Exit(1)
}
}
// Locator CLI command
func Locator(args []string) {
if len(args) > 0 {
switch args[0] {
case "new":
locatorNew(args[1:])
case "newdnskey":
locatorNewDNSKey(args[1:])
case "getdns":
locatorGetDNS(args[1:])
}
}
Help()
os.Exit(1)
}

View File

@ -1,18 +0,0 @@
/*
* 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
// MakeLocatorDNS CLI command
func MakeLocatorDNS(args []string) {
}

View File

@ -1,18 +0,0 @@
/*
* 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
// MakeLocatorDNSKey CLI command
func MakeLocatorDNSKey(args []string) {
}

View File

@ -14,9 +14,9 @@
package cli package cli
import ( import (
"encoding/json"
"fmt" "fmt"
"os" "os"
"zerotier/pkg/zerotier" "zerotier/pkg/zerotier"
) )
@ -26,8 +26,7 @@ func Peers(basePath, authToken string, args []string, jsonOutput bool) {
apiGet(basePath, authToken, "/peer", &peers) apiGet(basePath, authToken, "/peer", &peers)
if jsonOutput { if jsonOutput {
j, _ := json.MarshalIndent(&peers, "", " ") fmt.Println(jsonDump(&peers))
fmt.Println(string(j))
} else { } else {
fmt.Printf("<ztaddr> <ver> <role> <lat> <link> <lastTX> <lastRX> <path(s)>\n") fmt.Printf("<ztaddr> <ver> <role> <lat> <link> <lastTX> <lastRX> <path(s)>\n")
for _, peer := range peers { for _, peer := range peers {

View File

@ -14,9 +14,9 @@
package cli package cli
import ( import (
"encoding/json"
"fmt" "fmt"
"os" "os"
"zerotier/pkg/zerotier" "zerotier/pkg/zerotier"
) )
@ -26,8 +26,7 @@ func Status(basePath, authToken string, args []string, jsonOutput bool) {
apiGet(basePath, authToken, "/status", &status) apiGet(basePath, authToken, "/status", &status)
if jsonOutput { if jsonOutput {
j, _ := json.MarshalIndent(&status, "", " ") fmt.Println(jsonDump(&status))
fmt.Println(string(j))
} else { } else {
online := "ONLINE" online := "ONLINE"
if !status.Online { if !status.Online {

View File

@ -21,6 +21,7 @@ import (
"path" "path"
"runtime" "runtime"
"strings" "strings"
"zerotier/cmd/zerotier/cli" "zerotier/cmd/zerotier/cli"
"zerotier/pkg/zerotier" "zerotier/pkg/zerotier"
) )
@ -112,7 +113,7 @@ func main() {
case "peers", "listpeers": case "peers", "listpeers":
authTokenRequired(authToken) authTokenRequired(authToken)
cli.Peers(basePath, authToken, cmdArgs, *jflag) cli.Peers(basePath, authToken, cmdArgs, *jflag)
case "roots": case "roots", "listroots", "listmoons":
authTokenRequired(authToken) authTokenRequired(authToken)
cli.Roots(basePath, authToken, cmdArgs) cli.Roots(basePath, authToken, cmdArgs)
case "addroot": case "addroot":
@ -121,12 +122,10 @@ func main() {
case "removeroot": case "removeroot":
authTokenRequired(authToken) authTokenRequired(authToken)
cli.RemoveRoot(basePath, authToken, cmdArgs) cli.RemoveRoot(basePath, authToken, cmdArgs)
case "makelocator": case "locator":
cli.MakeLocator(cmdArgs) cli.Locator(cmdArgs)
case "makelocatordnskey": case "identity":
cli.MakeLocatorDNSKey(cmdArgs) cli.Identity(cmdArgs)
case "makelocatordns":
cli.MakeLocatorDNS(cmdArgs)
case "networks", "listnetworks": case "networks", "listnetworks":
authTokenRequired(authToken) authTokenRequired(authToken)
cli.Networks(basePath, authToken, cmdArgs) cli.Networks(basePath, authToken, cmdArgs)

View File

@ -732,7 +732,7 @@ extern "C" int ZT_GoLocator_makeSecureDNSName(char *name,unsigned int nameBufSiz
const Str n(Locator::makeSecureDnsName(pub)); const Str n(Locator::makeSecureDnsName(pub));
if (n.length() >= nameBufSize) if (n.length() >= nameBufSize)
return -1; return -1;
Utils::scopy(name,sizeof(name),n.c_str()); Utils::scopy(name,nameBufSize,n.c_str());
return ZT_ECC384_PRIVATE_KEY_SIZE; return ZT_ECC384_PRIVATE_KEY_SIZE;
} }

View File

@ -132,7 +132,6 @@ int ZT_GoLocator_decodeLocator(const uint8_t *locatorBytes,unsigned int locatorS
int ZT_GoLocator_makeSignedTxtRecords( int ZT_GoLocator_makeSignedTxtRecords(
const uint8_t *locator, const uint8_t *locator,
unsigned int locatorSize, unsigned int locatorSize,
int64_t ts,
const char *name, const char *name,
const uint8_t *privateKey, const uint8_t *privateKey,
unsigned int privateKeySize, unsigned int privateKeySize,

View File

@ -45,7 +45,7 @@ type Identity struct {
// NewIdentityFromString generates a new identity from its string representation. // NewIdentityFromString generates a new identity from its string representation.
// The private key is imported as well if it is present. // The private key is imported as well if it is present.
func NewIdentityFromString(s string) (*Identity, error) { func NewIdentityFromString(s string) (*Identity, error) {
ss := strings.Split(s, ":") ss := strings.Split(strings.TrimSpace(s), ":")
if len(ss) < 3 { if len(ss) < 3 {
return nil, ErrInvalidParameter return nil, ErrInvalidParameter
} }

View File

@ -43,7 +43,7 @@ func (i *InetAddress) Less(i2 *InetAddress) bool {
// NewInetAddressFromString parses an IP[/port] format address // NewInetAddressFromString parses an IP[/port] format address
func NewInetAddressFromString(s string) *InetAddress { func NewInetAddressFromString(s string) *InetAddress {
i := new(InetAddress) i := new(InetAddress)
ss := strings.Split(s, "/") ss := strings.Split(strings.TrimSpace(s), "/")
if len(ss) > 0 { if len(ss) > 0 {
i.IP = net.ParseIP(ss[0]) i.IP = net.ParseIP(ss[0])
i4 := i.IP.To4() i4 := i.IP.To4()

View File

@ -151,6 +151,25 @@ func NewLocatorFromBytes(b []byte) (*Locator, error) {
return &loc, nil return &loc, nil
} }
// MakeTXTRecords creates secure DNS TXT records for this locator
func (l *Locator) MakeTXTRecords(key *LocatorDNSSigningKey) ([]string, error) {
if key == nil || len(l.Bytes) == 0 || len(key.PrivateKey) == 0 {
return nil, ErrInvalidParameter
}
var results [256][256]C.char
cName := C.CString(key.SecureDNSName)
defer C.free(unsafe.Pointer(cName))
count := int(C.ZT_GoLocator_makeSignedTxtRecords((*C.uint8_t)(&l.Bytes[0]), C.uint(len(l.Bytes)), cName, (*C.uint8_t)(&key.PrivateKey[0]), C.uint(len(key.PrivateKey)), &results[0]))
if count > 0 {
var t []string
for i := 0; i < int(count); i++ {
t = append(t, C.GoString(&results[i][0]))
}
return t, nil
}
return nil, ErrInternal
}
// MarshalJSON marshals this Locator as its byte encoding // MarshalJSON marshals this Locator as its byte encoding
func (l *Locator) MarshalJSON() ([]byte, error) { func (l *Locator) MarshalJSON() ([]byte, error) {
return json.Marshal(l) return json.Marshal(l)

View File

@ -71,6 +71,9 @@ const (
// AFInet6 is the address family for IPv6 // AFInet6 is the address family for IPv6
AFInet6 = C.AF_INET6 AFInet6 = C.AF_INET6
networkConfigOpUp int = C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP
networkConfigOpUpdate int = C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE
defaultVirtualNetworkMTU = C.ZT_DEFAULT_MTU defaultVirtualNetworkMTU = C.ZT_DEFAULT_MTU
) )
@ -548,21 +551,11 @@ func (n *Node) Roots() []*Root {
if rl != nil { if rl != nil {
for i := 0; i < int(rl.count); i++ { for i := 0; i < int(rl.count); i++ {
root := (*C.ZT_Root)(unsafe.Pointer(uintptr(unsafe.Pointer(rl)) + C.sizeof_ZT_RootList)) root := (*C.ZT_Root)(unsafe.Pointer(uintptr(unsafe.Pointer(rl)) + C.sizeof_ZT_RootList))
id, err := NewIdentityFromString(C.GoString(root.identity)) loc, _ := NewLocatorFromBytes(C.GoBytes(root.locator, C.int(root.locatorSize)))
if err == nil { if loc != nil {
var addrs []InetAddress
for j := uintptr(0); j < uintptr(root.addressCount); j++ {
a := NewInetAddressFromSockaddr(unsafe.Pointer(uintptr(unsafe.Pointer(root.addresses)) + (j * C.sizeof_struct_sockaddr_storage)))
if a != nil && a.Valid() {
addrs = append(addrs, *a)
}
}
roots = append(roots, &Root{ roots = append(roots, &Root{
Name: C.GoString(root.name), Name: C.GoString(root.name),
Identity: id, Locator: loc,
Addresses: addrs,
Preferred: root.preferred != 0,
Online: root.online != 0,
}) })
} }
} }
@ -584,11 +577,11 @@ func (n *Node) SetRoot(name string, locator *Locator) error {
} }
var lb []byte var lb []byte
if locator != nil { if locator != nil {
lb = locator.Bytes() lb = locator.Bytes
} }
var lbp unsafe.Pointer var lbp unsafe.Pointer
if len(lb) > 0 { if len(lb) > 0 {
lbp = &lb[0] lbp = unsafe.Pointer(&lb[0])
} }
cn := C.CString(name) cn := C.CString(name)
defer C.free(unsafe.Pointer(cn)) defer C.free(unsafe.Pointer(cn))
@ -925,8 +918,8 @@ func goVirtualNetworkConfigFunc(gn, _ unsafe.Pointer, nwid C.uint64_t, op C.int,
node.networksLock.RUnlock() node.networksLock.RUnlock()
if network != nil { if network != nil {
switch op { switch int(op) {
case C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP, C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP: case networkConfigOpUp, networkConfigOpUpdate:
ncc := (*C.ZT_VirtualNetworkConfig)(conf) ncc := (*C.ZT_VirtualNetworkConfig)(conf)
if network.networkConfigRevision() > uint64(ncc.netconfRevision) { if network.networkConfigRevision() > uint64(ncc.netconfRevision) {
return return

View File

@ -17,6 +17,4 @@ package zerotier
type Root struct { type Root struct {
Name string Name string
Locator *Locator Locator *Locator
Preferred bool
Online bool
} }

View File

@ -532,29 +532,14 @@ typedef struct {
const char *name; const char *name;
/** /**
* Current public identity or NULL if not known (only possible with dynamic roots) * Serialized locator
*/ */
const char *identity; const void *locator;
/** /**
* Current physical address(es) of this root * The size of locator in bytes
*/ */
const struct sockaddr_storage *addresses; unsigned int locatorSize;
/**
* Number of physical addresses
*/
unsigned int addressCount;
/**
* If true, this is the current preferred root
*/
int preferred;
/**
* If true, this root server appears online
*/
int online;
} ZT_Root; } ZT_Root;
/** /**

View File

@ -104,8 +104,7 @@ public:
_l = ZT_STR_CAPACITY; _l = ZT_STR_CAPACITY;
throw ZT_EXCEPTION_OUT_OF_BOUNDS; throw ZT_EXCEPTION_OUT_OF_BOUNDS;
} }
_s[l++] = *s; _s[l++] = *(s++);
++s;
} }
_s[l] = 0; _s[l] = 0;
_l = (uint8_t)l; _l = (uint8_t)l;

View File

@ -360,63 +360,32 @@ public:
*/ */
inline ZT_RootList *apiRoots(const int64_t now) const inline ZT_RootList *apiRoots(const int64_t now) const
{ {
ScopedPtr< Buffer<65536> > lbuf(new Buffer<65536>());
Mutex::Lock l2(_roots_l); Mutex::Lock l2(_roots_l);
ZT_RootList *rl = (ZT_RootList *)malloc(sizeof(ZT_RootList) + (sizeof(ZT_Root) * _roots.size()) + (256 * _roots.size()) + (65536 * _roots.size()));
// The memory allocated here has room for all roots plus the maximum size if (!rl)
// of their DNS names, identities, and up to 16 physical addresses. Most
// roots will have two: one V4 and one V6.
const unsigned int totalRoots = _roots.size();
ZT_RootList *rl = reinterpret_cast<ZT_RootList *>(malloc(sizeof(ZT_RootList) + (sizeof(ZT_Root) * totalRoots) + ((sizeof(struct sockaddr_storage) * ZT_MAX_PEER_NETWORK_PATHS) * totalRoots) + ((ZT_IDENTITY_STRING_BUFFER_LENGTH + 1024) * totalRoots)));
if (!rl) {
return nullptr; return nullptr;
} char *nptr = ((char *)rl) + sizeof(ZT_RootList) + (sizeof(ZT_Root) * _roots.size());
uint8_t *lptr = ((uint8_t *)nptr) + (256 * _roots.size());
unsigned int c = 0; unsigned int c = 0;
char *nameBufPtr = reinterpret_cast<char *>(rl) + sizeof(ZT_RootList) + (sizeof(ZT_Root) * totalRoots);
struct sockaddr_storage *addrBuf = reinterpret_cast<struct sockaddr_storage *>(nameBufPtr);
nameBufPtr += (sizeof(struct sockaddr_storage) * ZT_MAX_PEER_NETWORK_PATHS) * totalRoots;
_bestRoot_l.lock();
const Peer *const bestRootPtr = _bestRoot.ptr();
_bestRoot_l.unlock();
{
Str *k = (Str *)0; Str *k = (Str *)0;
Locator *v = (Locator *)0; Locator *v = (Locator *)0;
Hashtable< Str,Locator >::Iterator i(const_cast<Topology *>(this)->_roots); Hashtable< Str,Locator >::Iterator i(const_cast<Topology *>(this)->_roots);
while (i.next(k,v)) { while (i.next(k,v)) {
rl->roots[c].name = nameBufPtr; Utils::scopy(nptr,256,k->c_str());
const char *p = k->c_str(); rl->roots[c].name = nptr;
while (*p) nptr += 256;
*(nameBufPtr++) = *(p++); lbuf->clear();
*(nameBufPtr++) = (char)0; v->serialize(*lbuf);
memcpy(lptr,lbuf->unsafeData(),lbuf->size());
if (v->id()) { rl->roots[c].locator = lptr;
rl->roots[c].identity = nameBufPtr; rl->roots[c].locatorSize = lbuf->size();
v->id().toString(false,nameBufPtr); lptr += 65536;
nameBufPtr += strlen(nameBufPtr) + 1;
}
rl->roots[c].addresses = addrBuf;
unsigned int ac = 0;
for(unsigned int j=(unsigned int)v->phy().size();(ac<j)&&(ac<16);++ac)
*(addrBuf++) = v->phy()[ac];
rl->roots[c].addressCount = ac;
_peers_l.lock();
const SharedPtr<Peer> *psptr = _peers.get(v->id().address());
if (psptr) {
rl->roots[c].preferred = (psptr->ptr() == bestRootPtr) ? 1 : 0;
rl->roots[c].online = (*psptr)->alive(now) ? 1 : 0;
}
_peers_l.unlock();
++c; ++c;
} }
}
rl->count = c; rl->count = c;
return rl; return rl;
} }