mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-07 13:03:45 +02:00
More wiring up of addroot/removeroot etc.
This commit is contained in:
parent
5c6bf9d0a4
commit
e9656ecf11
13 changed files with 122 additions and 123 deletions
|
@ -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) {
|
|
||||||
}
|
|
|
@ -41,13 +41,13 @@ Commands:
|
||||||
service Start as service
|
service Start as service
|
||||||
status Show ZeroTier status and config
|
status Show ZeroTier status and config
|
||||||
peers Show VL1 peers and link information
|
peers Show VL1 peers and link information
|
||||||
|
roots Show only root peers
|
||||||
|
addroot <identity> [IP/port] Add root with optional bootstrap IP
|
||||||
|
removeroot <address|identity> Remove root
|
||||||
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
|
||||||
networks List joined VL2 virtual networks
|
networks List joined VL2 virtual networks
|
||||||
network <network ID> Show verbose network info
|
network <network ID> Show verbose network info
|
||||||
addroot <identity> [IP/port] Add root with optional bootstrap IP
|
|
||||||
removeroot <identity|address> Remove root
|
|
||||||
roots Show configured VL1 root servers
|
|
||||||
set <network ID> [option] [value] Get or set a network config option
|
set <network ID> [option] [value] Get or set a network config option
|
||||||
manageips <boolean> Is IP management allowed?
|
manageips <boolean> Is IP management allowed?
|
||||||
manageroutes <boolean> Is route management allowed?
|
manageroutes <boolean> Is route management allowed?
|
||||||
|
|
|
@ -16,45 +16,52 @@ package cli
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"zerotier/pkg/zerotier"
|
"zerotier/pkg/zerotier"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Peers CLI command
|
// Peers CLI command (also used for 'roots' command with rootsOnly set to true)
|
||||||
func Peers(basePath, authToken string, args []string, jsonOutput bool) {
|
func Peers(basePath, authToken string, args []string, jsonOutput bool, rootsOnly bool) {
|
||||||
var peers []zerotier.Peer
|
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 {
|
if jsonOutput {
|
||||||
fmt.Println(jsonDump(&peers))
|
fmt.Println(jsonDump(&peers))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("<address> <ver> <role> <lat> <link> <lastTX> <lastRX> <path(s)>\n")
|
fmt.Printf("<address> <ver> <root> <lat(ms)> <path(s)>\n")
|
||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
role := "LEAF"
|
root := ""
|
||||||
link := "RELAY"
|
if peer.Root {
|
||||||
lastTX, lastRX := int64(0), int64(0)
|
root = " *"
|
||||||
address := ""
|
}
|
||||||
|
|
||||||
|
var paths strings.Builder
|
||||||
if len(peer.Paths) > 0 {
|
if len(peer.Paths) > 0 {
|
||||||
link = "DIRECT"
|
if paths.Len() > 0 {
|
||||||
lastTX, lastRX = clock-peer.Paths[0].LastSend, clock-peer.Paths[0].LastReceive
|
paths.WriteRune(' ')
|
||||||
if lastTX < 0 {
|
|
||||||
lastTX = 0
|
|
||||||
}
|
}
|
||||||
if lastRX < 0 {
|
paths.WriteString(fmt.Sprintf("%s/%d", peer.Paths[0].IP.String(), peer.Paths[0].Port))
|
||||||
lastRX = 0
|
} else {
|
||||||
|
paths.WriteString("(relayed)")
|
||||||
}
|
}
|
||||||
address = fmt.Sprintf("%s/%d", peer.Paths[0].IP.String(), peer.Paths[0].Port)
|
|
||||||
}
|
fmt.Printf("%.10x %-7s %-6s %-9d %s\n",
|
||||||
fmt.Printf("%.10x %-7s %-6s %-5d %-6s %-8d %-8d %s\n",
|
|
||||||
uint64(peer.Address),
|
uint64(peer.Address),
|
||||||
fmt.Sprintf("%d.%d.%d", peer.Version[0], peer.Version[1], peer.Version[2]),
|
fmt.Sprintf("%d.%d.%d", peer.Version[0], peer.Version[1], peer.Version[2]),
|
||||||
role,
|
root,
|
||||||
peer.Latency,
|
peer.Latency,
|
||||||
link,
|
paths.String())
|
||||||
lastTX,
|
|
||||||
lastRX,
|
|
||||||
address,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
|
||||||
}
|
|
|
@ -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) {
|
|
||||||
}
|
|
52
go/cmd/zerotier/cli/setroot.go
Normal file
52
go/cmd/zerotier/cli/setroot.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -133,16 +133,16 @@ 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, *jflag)
|
cli.Peers(basePath, authToken, cmdArgs, *jflag, false)
|
||||||
case "roots", "listroots":
|
case "roots", "listroots":
|
||||||
authTokenRequired(authToken)
|
authTokenRequired(authToken)
|
||||||
cli.Roots(basePath, authToken, cmdArgs, *jflag)
|
cli.Peers(basePath, authToken, cmdArgs, *jflag, true)
|
||||||
case "addroot":
|
case "addroot":
|
||||||
authTokenRequired(authToken)
|
authTokenRequired(authToken)
|
||||||
cli.AddRoot(basePath, authToken, cmdArgs)
|
cli.SetRoot(basePath, authToken, cmdArgs, true)
|
||||||
case "removeroot":
|
case "removeroot":
|
||||||
authTokenRequired(authToken)
|
authTokenRequired(authToken)
|
||||||
cli.RemoveRoot(basePath, authToken, cmdArgs)
|
cli.SetRoot(basePath, authToken, cmdArgs, false)
|
||||||
case "identity":
|
case "identity":
|
||||||
cli.Identity(cmdArgs)
|
cli.Identity(cmdArgs)
|
||||||
case "networks", "listnetworks":
|
case "networks", "listnetworks":
|
||||||
|
|
|
@ -226,12 +226,6 @@ func apiCheckAuth(out http.ResponseWriter, req *http.Request, token string) bool
|
||||||
return false
|
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
|
// createAPIServer creates and starts an HTTP server for a given node
|
||||||
func createAPIServer(basePath string, node *Node) (*http.Server, *http.Server, error) {
|
func createAPIServer(basePath string, node *Node) (*http.Server, *http.Server, error) {
|
||||||
// Read authorization token, automatically generating one if it's missing
|
// 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"})
|
_ = apiSendObj(out, req, http.StatusNotFound, &APIErr{"peer not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var peerChanges peerMutableFields
|
var peerChanges PeerMutableFields
|
||||||
if apiReadObj(out, req, &peerChanges) == nil {
|
if apiReadObj(out, req, &peerChanges) == nil {
|
||||||
if peerChanges.Role != nil || peerChanges.Bootstrap != nil {
|
if peerChanges.Root != nil || peerChanges.Bootstrap != nil {
|
||||||
peers := node.Peers()
|
peers := node.Peers()
|
||||||
for _, p := range peers {
|
for _, p := range peers {
|
||||||
if p.Address == queriedID && (peerChanges.Identity == nil || peerChanges.Identity.Equals(p.Identity)) {
|
if p.Address == queriedID && (peerChanges.Identity == nil || peerChanges.Identity.Equals(p.Identity)) {
|
||||||
if peerChanges.Role != nil && *peerChanges.Role != p.Role {
|
if peerChanges.Root != nil && *peerChanges.Root != p.Root {
|
||||||
if *peerChanges.Role == PeerRoleRoot {
|
if *peerChanges.Root {
|
||||||
_ = node.AddRoot(p.Identity, peerChanges.Bootstrap)
|
_ = node.AddRoot(p.Identity, peerChanges.Bootstrap)
|
||||||
} else {
|
} else {
|
||||||
node.RemoveRoot(p.Identity)
|
node.RemoveRoot(p.Identity)
|
||||||
|
|
|
@ -105,6 +105,11 @@ type InetAddress struct {
|
||||||
Port int
|
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
|
// Less returns true if this IP/port is lexicographically less than another
|
||||||
func (ina *InetAddress) Less(i2 *InetAddress) bool {
|
func (ina *InetAddress) Less(i2 *InetAddress) bool {
|
||||||
c := bytes.Compare(ina.IP, i2.IP)
|
c := bytes.Compare(ina.IP, i2.IP)
|
||||||
|
|
|
@ -80,6 +80,7 @@ var (
|
||||||
|
|
||||||
// This map is used to get the Go Node object from a pointer passed back in via C callbacks
|
// This map is used to get the Go Node object from a pointer passed back in via C callbacks
|
||||||
nodesByUserPtr = make(map[uintptr]*Node)
|
nodesByUserPtr = make(map[uintptr]*Node)
|
||||||
|
nodesByUserPtrCtr = uintptr(0)
|
||||||
nodesByUserPtrLock sync.RWMutex
|
nodesByUserPtrLock sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -130,6 +131,9 @@ type Node struct {
|
||||||
|
|
||||||
// runWaitGroup is used to wait for all node goroutines on shutdown
|
// runWaitGroup is used to wait for all node goroutines on shutdown
|
||||||
runWaitGroup sync.WaitGroup
|
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
|
// 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()
|
nodesByUserPtrLock.Lock()
|
||||||
nodesByUserPtr[uintptr(unsafe.Pointer(n))] = n
|
nodesByUserPtrCtr++
|
||||||
|
n.cPtr = nodesByUserPtrCtr
|
||||||
|
nodesByUserPtr[n.cPtr] = n
|
||||||
nodesByUserPtrLock.Unlock()
|
nodesByUserPtrLock.Unlock()
|
||||||
|
|
||||||
// Instantiate GoNode and friends from the land of C/C++
|
|
||||||
cPath := C.CString(basePath)
|
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))
|
C.free(unsafe.Pointer(cPath))
|
||||||
if n.gn == nil {
|
if n.gn == nil {
|
||||||
n.infoLog.Println("FATAL: node initialization failed")
|
n.infoLog.Println("FATAL: node initialization failed")
|
||||||
nodesByUserPtrLock.Lock()
|
nodesByUserPtrLock.Lock()
|
||||||
delete(nodesByUserPtr, uintptr(unsafe.Pointer(n)))
|
delete(nodesByUserPtr, n.cPtr)
|
||||||
nodesByUserPtrLock.Unlock()
|
nodesByUserPtrLock.Unlock()
|
||||||
return nil, ErrNodeInitFailed
|
return nil, ErrNodeInitFailed
|
||||||
}
|
}
|
||||||
|
@ -261,7 +266,7 @@ func NewNode(basePath string) (n *Node, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.infoLog.Printf("FATAL: error obtaining node's identity")
|
n.infoLog.Printf("FATAL: error obtaining node's identity")
|
||||||
nodesByUserPtrLock.Lock()
|
nodesByUserPtrLock.Lock()
|
||||||
delete(nodesByUserPtr, uintptr(unsafe.Pointer(n)))
|
delete(nodesByUserPtr, n.cPtr)
|
||||||
nodesByUserPtrLock.Unlock()
|
nodesByUserPtrLock.Unlock()
|
||||||
C.ZT_GoNode_delete(n.gn)
|
C.ZT_GoNode_delete(n.gn)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -600,7 +605,7 @@ func (n *Node) Peers() []*Peer {
|
||||||
p2.IdentityHash = hex.EncodeToString((*[48]byte)(unsafe.Pointer(&p.identityHash[0]))[:])
|
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.Version = [3]int{int(p.versionMajor), int(p.versionMinor), int(p.versionRev)}
|
||||||
p2.Latency = int(p.latency)
|
p2.Latency = int(p.latency)
|
||||||
p2.Role = int(p.role)
|
p2.Root = p.root != 0
|
||||||
p2.Bootstrap = NewInetAddressFromSockaddr(unsafe.Pointer(&p.bootstrap))
|
p2.Bootstrap = NewInetAddressFromSockaddr(unsafe.Pointer(&p.bootstrap))
|
||||||
|
|
||||||
p2.Paths = make([]Path, 0, int(p.pathCount))
|
p2.Paths = make([]Path, 0, int(p.pathCount))
|
||||||
|
|
|
@ -13,14 +13,6 @@
|
||||||
|
|
||||||
package zerotier
|
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
|
// Peer is another ZeroTier node
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
Address Address `json:"address"`
|
Address Address `json:"address"`
|
||||||
|
@ -28,7 +20,14 @@ type Peer struct {
|
||||||
IdentityHash string `json:"identityHash"`
|
IdentityHash string `json:"identityHash"`
|
||||||
Version [3]int `json:"version"`
|
Version [3]int `json:"version"`
|
||||||
Latency int `json:"latency"`
|
Latency int `json:"latency"`
|
||||||
Role int `json:"role"`
|
Root bool `json:"root"`
|
||||||
Bootstrap *InetAddress `json:"bootstrap,omitempty"`
|
Bootstrap *InetAddress `json:"bootstrap,omitempty"`
|
||||||
Paths []Path `json:"paths,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"`
|
||||||
|
}
|
||||||
|
|
|
@ -1323,15 +1323,6 @@ typedef struct
|
||||||
int preferred;
|
int preferred;
|
||||||
} ZT_PeerPhysicalPath;
|
} 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
|
* Peer status result buffer
|
||||||
*/
|
*/
|
||||||
|
@ -1373,9 +1364,9 @@ typedef struct
|
||||||
int latency;
|
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
|
* Bootstrap address
|
||||||
|
|
|
@ -446,7 +446,7 @@ ZT_PeerList *Node::peers() const
|
||||||
p->latency = (int)(*pi)->latency();
|
p->latency = (int)(*pi)->latency();
|
||||||
if (p->latency >= 0xffff)
|
if (p->latency >= 0xffff)
|
||||||
p->latency = -1;
|
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));
|
memcpy(&p->bootstrap,&((*pi)->bootstrap()),sizeof(sockaddr_storage));
|
||||||
|
|
||||||
std::vector< SharedPtr<Path> > paths;
|
std::vector< SharedPtr<Path> > paths;
|
||||||
|
|
Loading…
Add table
Reference in a new issue