ZeroTierOne/go/pkg/zerotier/node.go
2020-01-24 13:27:01 -08:00

923 lines
27 KiB
Go

/*
* 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 zerotier
//#cgo CFLAGS: -O3
//#cgo darwin LDFLAGS: ${SRCDIR}/../../../build/go/native/libzt_go_native.a ${SRCDIR}/../../../build/node/libzt_core.a ${SRCDIR}/../../../build/osdep/libzt_osdep.a -lc++ -lpthread
//#cgo linux android LDFLAGS: ${SRCDIR}/../../../build/go/native/libzt_go_native.a ${SRCDIR}/../../../build/node/libzt_core.a ${SRCDIR}/../../../build/osdep/libzt_osdep.a -lstdc++ -lpthread -lm
//#include "../../native/GoGlue.h"
import "C"
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"net"
"net/http"
"os"
"path"
"reflect"
"sort"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
"unsafe"
"github.com/hectane/go-acl"
)
var nullLogger = log.New(ioutil.Discard, "", 0)
// Network status states
const (
NetworkStatusRequestConfiguration int = C.ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION
NetworkStatusOK int = C.ZT_NETWORK_STATUS_OK
NetworkStatusAccessDenied int = C.ZT_NETWORK_STATUS_ACCESS_DENIED
NetworkStatusNotFound int = C.ZT_NETWORK_STATUS_NOT_FOUND
NetworkTypePrivate int = C.ZT_NETWORK_TYPE_PRIVATE
NetworkTypePublic int = C.ZT_NETWORK_TYPE_PUBLIC
// CoreVersionMajor is the major version of the ZeroTier core
CoreVersionMajor int = C.ZEROTIER_ONE_VERSION_MAJOR
// CoreVersionMinor is the minor version of the ZeroTier core
CoreVersionMinor int = C.ZEROTIER_ONE_VERSION_MINOR
// CoreVersionRevision is the revision of the ZeroTier core
CoreVersionRevision int = C.ZEROTIER_ONE_VERSION_REVISION
// CoreVersionBuild is the build version of the ZeroTier core
CoreVersionBuild int = C.ZEROTIER_ONE_VERSION_BUILD
networkConfigOpUp int = C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP
networkConfigOpUpdate int = C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE
defaultVirtualNetworkMTU = C.ZT_DEFAULT_MTU
)
var (
// PlatformDefaultHomePath is the default location of ZeroTier's working path on this system
PlatformDefaultHomePath string = C.GoString(C.ZT_PLATFORM_DEFAULT_HOMEPATH)
// 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
)
// Node is an instance of a virtual port on the global switch.
type Node struct {
// networks contains networks we have joined, and networksByMAC by their local MAC address
networks map[NetworkID]*Network
networksByMAC map[MAC]*Network // locked by networksLock
networksLock sync.RWMutex
// interfaceAddresses are physical IPs assigned to the local machine (detected, not configured)
interfaceAddresses map[string]net.IP
interfaceAddressesLock sync.Mutex
// online and running are atomic flags set to control and monitor background tasks
online uint32
running uint32
basePath string
peersPath string
networksPath string
localConfigPath string
// localConfig is the current state of local.conf
localConfig LocalConfig
localConfigLock sync.RWMutex
// logs for information, errors, and trace output
infoLogW *sizeLimitWriter
errLogW *sizeLimitWriter
traceLogW io.Writer
infoLog *log.Logger
errLog *log.Logger
traceLog *log.Logger
// gn is the instance of GoNode, the Go wrapper and multithreaded I/O engine
gn *C.ZT_GoNode
// zn is the underlying instance of the ZeroTier core
zn *C.ZT_Node
// id is the identity of this node
id *Identity
// HTTP server instances: one for a named socket (Unix domain or Windows named pipe) and one on a local TCP socket
namedSocketApiServer *http.Server
tcpApiServer *http.Server
// 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
func NewNode(basePath string) (n *Node, err error) {
n = new(Node)
n.networks = make(map[NetworkID]*Network)
n.networksByMAC = make(map[MAC]*Network)
n.interfaceAddresses = make(map[string]net.IP)
n.online = 0
n.running = 1
_ = os.MkdirAll(basePath, 0755)
if _, err = os.Stat(basePath); err != nil {
return
}
n.basePath = basePath
n.peersPath = path.Join(basePath, "peers.d")
_ = os.MkdirAll(n.peersPath, 0700)
_ = acl.Chmod(n.peersPath, 0700)
if _, err = os.Stat(n.peersPath); err != nil {
return
}
n.networksPath = path.Join(basePath, "networks.d")
_ = os.MkdirAll(n.networksPath, 0755)
if _, err = os.Stat(n.networksPath); err != nil {
return
}
n.localConfigPath = path.Join(basePath, "local.conf")
_, identitySecretNotFoundErr := os.Stat(path.Join(basePath, "identity.secret"))
err = n.localConfig.Read(n.localConfigPath, true, identitySecretNotFoundErr != nil)
if err != nil {
return
}
if n.localConfig.Settings.LogSizeMax >= 0 {
n.infoLogW, err = sizeLimitWriterOpen(path.Join(basePath, "info.log"))
if err != nil {
return
}
n.errLogW, err = sizeLimitWriterOpen(path.Join(basePath, "error.log"))
if err != nil {
return
}
n.infoLog = log.New(n.infoLogW, "", log.LstdFlags)
n.errLog = log.New(n.errLogW, "", log.LstdFlags)
} else {
n.infoLog = nullLogger
}
if n.localConfig.Settings.PortSearch {
portsChanged := false
portCheckCount := 0
origPort := n.localConfig.Settings.PrimaryPort
for portCheckCount < 256 {
portCheckCount++
if checkPort(n.localConfig.Settings.PrimaryPort) {
if n.localConfig.Settings.PrimaryPort != origPort {
n.infoLog.Printf("primary port %d unavailable, found port %d and saved in local.conf", origPort, n.localConfig.Settings.PrimaryPort)
}
break
}
n.localConfig.Settings.PrimaryPort = int(4096 + (randomUInt() % 16384))
portsChanged = true
}
if portCheckCount == 256 {
return nil, errors.New("unable to bind to primary port: tried configured port and 256 other random ports")
}
if n.localConfig.Settings.SecondaryPort > 0 {
portCheckCount = 0
origPort = n.localConfig.Settings.SecondaryPort
for portCheckCount < 256 {
portCheckCount++
if checkPort(n.localConfig.Settings.SecondaryPort) {
if n.localConfig.Settings.SecondaryPort != origPort {
n.infoLog.Printf("secondary port %d unavailable, found port %d (port search enabled)", origPort, n.localConfig.Settings.SecondaryPort)
}
break
}
n.infoLog.Printf("secondary port %d unavailable, trying a random port (port search enabled)", n.localConfig.Settings.SecondaryPort)
if portCheckCount <= 64 {
n.localConfig.Settings.SecondaryPort = unassignedPrivilegedPorts[randomUInt()%uint(len(unassignedPrivilegedPorts))]
} else {
n.localConfig.Settings.SecondaryPort = int(16384 + (randomUInt() % 16384))
}
portsChanged = true
}
}
if portsChanged {
_ = n.localConfig.Write(n.localConfigPath)
}
} else {
if !checkPort(n.localConfig.Settings.PrimaryPort) {
return nil, errors.New("unable to bind to primary port")
}
if n.localConfig.Settings.SecondaryPort > 0 && n.localConfig.Settings.SecondaryPort < 65536 {
if !checkPort(n.localConfig.Settings.SecondaryPort) {
n.infoLog.Printf("WARNING: unable to bind secondary port %d", n.localConfig.Settings.SecondaryPort)
}
}
}
n.namedSocketApiServer, n.tcpApiServer, err = createAPIServer(basePath, n)
if err != nil {
n.infoLog.Printf("FATAL: unable to start API server: %s", err.Error())
return nil, err
}
nodesByUserPtrLock.Lock()
nodesByUserPtrCtr++
n.cPtr = nodesByUserPtrCtr
nodesByUserPtr[n.cPtr] = n
nodesByUserPtrLock.Unlock()
cPath := C.CString(basePath)
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, n.cPtr)
nodesByUserPtrLock.Unlock()
return nil, ErrNodeInitFailed
}
n.zn = (*C.ZT_Node)(C.ZT_GoNode_getNode(n.gn))
n.id, err = newIdentityFromCIdentity(C.ZT_Node_identity(unsafe.Pointer(n.zn)))
if err != nil {
n.infoLog.Printf("FATAL: error obtaining node's identity")
nodesByUserPtrLock.Lock()
delete(nodesByUserPtr, n.cPtr)
nodesByUserPtrLock.Unlock()
C.ZT_GoNode_delete(n.gn)
return nil, err
}
// Background maintenance goroutine that handles polling for local network changes, cleaning internal data
// structures, syncing local config changes, and numerous other things that must happen from time to time.
n.runWaitGroup.Add(1)
go func() {
defer n.runWaitGroup.Done()
var previousExplicitExternalAddresses []ExternalAddress // empty at first so these will be configured
lastMaintenanceRun := int64(0)
for atomic.LoadUint32(&n.running) != 0 {
time.Sleep(500 * time.Millisecond)
nowS := time.Now().Unix()
if (nowS - lastMaintenanceRun) >= 30 {
lastMaintenanceRun = nowS
//////////////////////////////////////////////////////////////////////
n.localConfigLock.RLock()
// Get local physical interface addresses, excluding blacklisted and ZeroTier-created interfaces
interfaceAddresses := make(map[string]net.IP)
ifs, _ := net.Interfaces()
if len(ifs) > 0 {
n.networksLock.RLock()
scanInterfaces:
for _, i := range ifs {
for _, bl := range n.localConfig.Settings.InterfacePrefixBlacklist {
if strings.HasPrefix(strings.ToLower(i.Name), strings.ToLower(bl)) {
continue scanInterfaces
}
}
m, _ := NewMACFromBytes(i.HardwareAddr)
if _, isZeroTier := n.networksByMAC[m]; !isZeroTier {
addrs, _ := i.Addrs()
for _, a := range addrs {
ipn, _ := a.(*net.IPNet)
if ipn != nil && len(ipn.IP) > 0 && !ipn.IP.IsLinkLocalUnicast() && !ipn.IP.IsMulticast() {
interfaceAddresses[ipn.IP.String()] = ipn.IP
}
}
}
}
n.networksLock.RUnlock()
}
// Open or close locally bound UDP ports for each local interface address.
// This opens ports if they are not already open and then closes ports if
// they are open but no longer seem to exist.
interfaceAddressesChanged := false
ports := make([]int, 0, 2)
if n.localConfig.Settings.PrimaryPort > 0 && n.localConfig.Settings.PrimaryPort < 65536 {
ports = append(ports, n.localConfig.Settings.PrimaryPort)
}
if n.localConfig.Settings.SecondaryPort > 0 && n.localConfig.Settings.SecondaryPort < 65536 {
ports = append(ports, n.localConfig.Settings.SecondaryPort)
}
n.interfaceAddressesLock.Lock()
for astr, ipn := range interfaceAddresses {
if _, alreadyKnown := n.interfaceAddresses[astr]; !alreadyKnown {
interfaceAddressesChanged = true
ipCstr := C.CString(ipn.String())
for pn, p := range ports {
n.infoLog.Printf("UDP binding to port %d on interface %s", p, astr)
primary := C.int(0)
if pn == 0 {
primary = 1
}
C.ZT_GoNode_phyStartListen(n.gn, nil, ipCstr, C.int(p), primary)
}
C.free(unsafe.Pointer(ipCstr))
}
}
for astr, ipn := range n.interfaceAddresses {
if _, stillPresent := interfaceAddresses[astr]; !stillPresent {
interfaceAddressesChanged = true
ipCstr := C.CString(ipn.String())
for _, p := range ports {
n.infoLog.Printf("UDP closing socket bound to port %d on interface %s", p, astr)
C.ZT_GoNode_phyStopListen(n.gn, nil, ipCstr, C.int(p))
}
C.free(unsafe.Pointer(ipCstr))
}
}
n.interfaceAddresses = interfaceAddresses
n.interfaceAddressesLock.Unlock()
// Update node's understanding of our interface addresses if they've changed
if interfaceAddressesChanged || reflect.DeepEqual(n.localConfig.Settings.ExplicitAddresses, previousExplicitExternalAddresses) {
previousExplicitExternalAddresses = n.localConfig.Settings.ExplicitAddresses
// Consolidate explicit and detected interface addresses, removing duplicates.
externalAddresses := make(map[[3]uint64]*ExternalAddress)
for _, ip := range interfaceAddresses {
for _, p := range ports {
a := &ExternalAddress{
InetAddress: InetAddress{
IP: ip,
Port: p,
},
Permanent: false,
}
externalAddresses[a.key()] = a
}
}
for _, a := range n.localConfig.Settings.ExplicitAddresses {
externalAddresses[a.key()] = &a
}
if len(externalAddresses) > 0 {
cAddrs := make([]C.ZT_InterfaceAddress, len(externalAddresses))
cAddrCount := 0
for _, a := range externalAddresses {
makeSockaddrStorage(a.IP, a.Port, &(cAddrs[cAddrCount].address))
if a.Permanent {
cAddrs[cAddrCount].permanent = 1
} else {
cAddrs[cAddrCount].permanent = 0
}
cAddrCount++
}
C.ZT_Node_setInterfaceAddresses(unsafe.Pointer(n.zn), &cAddrs[0], C.uint(cAddrCount))
} else {
C.ZT_Node_setInterfaceAddresses(unsafe.Pointer(n.zn), nil, 0)
}
}
// Trim infoLog if it's gone over its size limit
if n.localConfig.Settings.LogSizeMax > 0 && n.infoLogW != nil {
_ = n.infoLogW.trim(n.localConfig.Settings.LogSizeMax*1024, 0.5, true)
}
n.localConfigLock.RUnlock()
//////////////////////////////////////////////////////////////////////
}
}
}()
return n, nil
}
// Close closes this Node and frees its underlying C++ Node structures
func (n *Node) Close() {
if atomic.SwapUint32(&n.running, 0) != 0 {
if n.namedSocketApiServer != nil {
_ = n.namedSocketApiServer.Close()
}
if n.tcpApiServer != nil {
_ = n.tcpApiServer.Close()
}
C.ZT_GoNode_delete(n.gn)
n.runWaitGroup.Wait()
nodesByUserPtrLock.Lock()
delete(nodesByUserPtr, uintptr(unsafe.Pointer(n)))
nodesByUserPtrLock.Unlock()
}
}
// Address returns this node's address
func (n *Node) Address() Address { return n.id.address }
// Identity returns this node's identity (including secret portion)
func (n *Node) Identity() *Identity { return n.id }
// Online returns true if this node can reach something
func (n *Node) Online() bool { return atomic.LoadUint32(&n.online) != 0 }
// InterfaceAddresses are external IPs belonging to physical interfaces on this machine
func (n *Node) InterfaceAddresses() []net.IP {
var ea []net.IP
n.interfaceAddressesLock.Lock()
for _, a := range n.interfaceAddresses {
ea = append(ea, a)
}
n.interfaceAddressesLock.Unlock()
sort.Slice(ea, func(a, b int) bool { return bytes.Compare(ea[a], ea[b]) < 0 })
return ea
}
// LocalConfig gets this node's local configuration
func (n *Node) LocalConfig() LocalConfig {
n.localConfigLock.RLock()
defer n.localConfigLock.RUnlock()
return n.localConfig
}
// SetLocalConfig updates this node's local configuration
func (n *Node) SetLocalConfig(lc *LocalConfig) (restartRequired bool, err error) {
n.networksLock.RLock()
n.localConfigLock.Lock()
defer n.localConfigLock.Unlock()
defer n.networksLock.RUnlock()
for nid, nc := range lc.Network {
nw := n.networks[nid]
if nw != nil {
nw.SetLocalSettings(&nc)
}
}
if n.localConfig.Settings.PrimaryPort != lc.Settings.PrimaryPort || n.localConfig.Settings.SecondaryPort != lc.Settings.SecondaryPort {
restartRequired = true
}
if lc.Settings.LogSizeMax < 0 {
n.infoLog = nullLogger
_ = n.infoLogW.Close()
n.infoLogW = nil
} else if n.infoLogW != nil {
n.infoLogW, err = sizeLimitWriterOpen(path.Join(n.basePath, "service.infoLog"))
if err == nil {
n.infoLog = log.New(n.infoLogW, "", log.LstdFlags)
} else {
n.infoLog = nullLogger
n.infoLogW = nil
}
}
n.localConfig = *lc
return
}
// Join a network.
// If tap is nil, the default system tap for this OS/platform is used (if available).
func (n *Node) Join(nwid NetworkID, settings *NetworkLocalSettings, tap Tap) (*Network, error) {
n.networksLock.RLock()
if nw, have := n.networks[nwid]; have {
n.infoLog.Printf("join network %.16x ignored: already a member", nwid)
if settings != nil {
nw.SetLocalSettings(settings)
}
return nw, nil
}
n.networksLock.RUnlock()
if tap != nil {
panic("non-native taps not yet implemented")
}
ntap := C.ZT_GoNode_join(n.gn, C.uint64_t(nwid))
if ntap == nil {
n.infoLog.Printf("join network %.16x failed: tap device failed to initialize (check drivers / kernel modules)", uint64(nwid))
return nil, ErrTapInitFailed
}
nw, err := newNetwork(n, nwid, &nativeTap{tap: unsafe.Pointer(ntap), enabled: 1})
if err != nil {
n.infoLog.Printf("join network %.16x failed: network failed to initialize: %s", nwid, err.Error())
C.ZT_GoNode_leave(n.gn, C.uint64_t(nwid))
return nil, err
}
n.networksLock.Lock()
n.networks[nwid] = nw
n.networksLock.Unlock()
if settings != nil {
nw.SetLocalSettings(settings)
}
return nw, nil
}
// Leave a network.
func (n *Node) Leave(nwid NetworkID) error {
n.networksLock.Lock()
nw := n.networks[nwid]
delete(n.networks, nwid)
n.networksLock.Unlock()
if nw != nil {
n.infoLog.Printf("leaving network %.16x", nwid)
nw.leaving()
}
C.ZT_GoNode_leave(n.gn, C.uint64_t(nwid))
return nil
}
// AddRoot adds a root server with an optional bootstrap address for establishing first contact.
// If you're already using roots backed by proper global LF data stores the bootstrap address may
// be unnecessary as your node can probably find the new root automatically.
func (n *Node) AddRoot(id *Identity, bootstrap *InetAddress) error {
if id == nil {
return ErrInvalidParameter
}
var cBootstrap C.struct_sockaddr_storage
if bootstrap != nil {
makeSockaddrStorage(bootstrap.IP, bootstrap.Port, &cBootstrap)
} else {
zeroSockaddrStorage(&cBootstrap)
}
C.ZT_Node_addRoot(n.zn, nil, id.cIdentity(), &cBootstrap)
return nil
}
// RemoveRoot removes a root server for this node.
// This doesn't instantly close paths to the given peer or forget about it. It just
// demotes it to a normal peer.
func (n *Node) RemoveRoot(id *Identity) {
if id != nil {
C.ZT_Node_removeRoot(n.zn, nil, id.cIdentity())
}
}
// 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
n.networksLock.RLock()
for _, nw := range n.networks {
nws = append(nws, nw)
}
n.networksLock.RUnlock()
return nws
}
// Peers retrieves a list of current peers
func (n *Node) Peers() []*Peer {
var peers []*Peer
pl := C.ZT_Node_peers(unsafe.Pointer(n.zn))
if pl != nil {
for i := uintptr(0); i < uintptr(pl.peerCount); i++ {
p := (*C.ZT_Peer)(unsafe.Pointer(uintptr(unsafe.Pointer(pl.peers)) + (i * C.sizeof_ZT_Peer)))
p2 := new(Peer)
p2.Address = Address(p.address)
p2.Identity, _ = newIdentityFromCIdentity(unsafe.Pointer(p.identity))
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.Root = p.root != 0
p2.Bootstrap = NewInetAddressFromSockaddr(unsafe.Pointer(&p.bootstrap))
p2.Paths = make([]Path, 0, int(p.pathCount))
for j := 0; j < len(p2.Paths); j++ {
pt := &p.paths[j]
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),
})
}
}
}
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
}
//////////////////////////////////////////////////////////////////////////////
func (n *Node) multicastSubscribe(nwid uint64, mg *MulticastGroup) {
C.ZT_Node_multicastSubscribe(unsafe.Pointer(n.zn), nil, C.uint64_t(nwid), C.uint64_t(mg.MAC), C.ulong(mg.ADI))
}
func (n *Node) multicastUnsubscribe(nwid uint64, mg *MulticastGroup) {
C.ZT_Node_multicastUnsubscribe(unsafe.Pointer(n.zn), C.uint64_t(nwid), C.uint64_t(mg.MAC), C.ulong(mg.ADI))
}
func (n *Node) pathCheck(ip net.IP) bool {
n.localConfigLock.RLock()
defer n.localConfigLock.RUnlock()
for cidr, phy := range n.localConfig.Physical {
if phy.Blacklist {
_, ipn, _ := net.ParseCIDR(cidr)
if ipn != nil && ipn.Contains(ip) {
return false
}
}
}
return true
}
func (n *Node) pathLookup(id *Identity) (net.IP, int) {
n.localConfigLock.RLock()
defer n.localConfigLock.RUnlock()
virt := n.localConfig.Virtual[id.address]
if len(virt.Try) > 0 {
idx := rand.Int() % len(virt.Try)
return virt.Try[idx].IP, virt.Try[idx].Port
}
return nil, 0
}
func (n *Node) makeStateObjectPath(objType int, id [2]uint64) (string, bool) {
var fp string
secret := false
switch objType {
case C.ZT_STATE_OBJECT_IDENTITY_PUBLIC:
fp = path.Join(n.basePath, "identity.public")
case C.ZT_STATE_OBJECT_IDENTITY_SECRET:
fp = path.Join(n.basePath, "identity.secret")
secret = true
case C.ZT_STATE_OBJECT_LOCATOR:
fp = path.Join(n.basePath, "locator")
case C.ZT_STATE_OBJECT_PEER:
fp = path.Join(n.basePath, "peers.d")
_ = os.Mkdir(fp, 0700)
fp = path.Join(fp, fmt.Sprintf("%.10x.peer", id[0]))
secret = true
case C.ZT_STATE_OBJECT_NETWORK_CONFIG:
fp = path.Join(n.basePath, "networks.d")
_ = os.Mkdir(fp, 0755)
fp = path.Join(fp, fmt.Sprintf("%.16x.conf", id[0]))
case C.ZT_STATE_OBJECT_ROOTS:
fp = path.Join(n.basePath, "roots")
}
return fp, secret
}
func (n *Node) stateObjectPut(objType int, id [2]uint64, data []byte) {
fp, secret := n.makeStateObjectPath(objType, id)
if len(fp) > 0 {
fileMode := os.FileMode(0644)
if secret {
fileMode = os.FileMode(0600)
}
_ = ioutil.WriteFile(fp, data, fileMode)
if secret {
_ = acl.Chmod(fp, 0600) // this emulates Unix chmod on Windows and uses os.Chmod on Unix-type systems
}
}
}
func (n *Node) stateObjectDelete(objType int, id [2]uint64) {
fp, _ := n.makeStateObjectPath(objType, id)
if len(fp) > 0 {
_ = os.Remove(fp)
}
}
func (n *Node) stateObjectGet(objType int, id [2]uint64) ([]byte, bool) {
fp, _ := n.makeStateObjectPath(objType, id)
if len(fp) > 0 {
fd, err := ioutil.ReadFile(fp)
if err != nil {
return nil, false
}
return fd, true
}
return nil, false
}
func (n *Node) handleTrace(traceMessage string) {
if len(traceMessage) > 0 {
n.infoLog.Print("TRACE: " + traceMessage)
}
}
// func (n *Node) handleUserMessage(origin *Identity, messageTypeID uint64, data []byte) {
// }
//////////////////////////////////////////////////////////////////////////////
// These are callbacks called by the core and GoGlue stuff to talk to the
// service. These launch goroutines to do their work where possible to
// avoid blocking anything in the core.
//export goPathCheckFunc
func goPathCheckFunc(gn, _ unsafe.Pointer, af C.int, ip unsafe.Pointer, _ C.int) C.int {
nodesByUserPtrLock.RLock()
node := nodesByUserPtr[uintptr(gn)]
nodesByUserPtrLock.RUnlock()
var nip net.IP
if af == syscall.AF_INET {
nip = ((*[4]byte)(ip))[:]
} else if af == syscall.AF_INET6 {
nip = ((*[16]byte)(ip))[:]
} else {
return 0
}
if node != nil && len(nip) > 0 && node.pathCheck(nip) {
return 1
}
return 0
}
//export goPathLookupFunc
func goPathLookupFunc(gn unsafe.Pointer, _ C.uint64_t, _ int, identity, familyP, ipP, portP unsafe.Pointer) C.int {
nodesByUserPtrLock.RLock()
node := nodesByUserPtr[uintptr(gn)]
nodesByUserPtrLock.RUnlock()
if node == nil {
return 0
}
id, err := newIdentityFromCIdentity(identity)
if err != nil {
return 0
}
ip, port := node.pathLookup(id)
if len(ip) > 0 && port > 0 && port <= 65535 {
ip4 := ip.To4()
if len(ip4) == 4 {
*((*C.int)(familyP)) = C.int(syscall.AF_INET)
copy((*[4]byte)(ipP)[:], ip4)
*((*C.int)(portP)) = C.int(port)
return 1
} else if len(ip) == 16 {
*((*C.int)(familyP)) = C.int(syscall.AF_INET6)
copy((*[16]byte)(ipP)[:], ip)
*((*C.int)(portP)) = C.int(port)
return 1
}
}
return 0
}
//export goStateObjectPutFunc
func goStateObjectPutFunc(gn unsafe.Pointer, objType C.int, id, data unsafe.Pointer, len C.int) {
id2 := *((*[2]uint64)(id))
var data2 []byte
if len > 0 {
data2 = C.GoBytes(data, len)
}
nodesByUserPtrLock.RLock()
node := nodesByUserPtr[uintptr(gn)]
nodesByUserPtrLock.RUnlock()
if node == nil {
return
}
node.runWaitGroup.Add(1)
go func() {
if len < 0 {
node.stateObjectDelete(int(objType), id2)
} else {
node.stateObjectPut(int(objType), id2, data2)
}
node.runWaitGroup.Done()
}()
}
//export goStateObjectGetFunc
func goStateObjectGetFunc(gn unsafe.Pointer, objType C.int, id, dataP unsafe.Pointer) C.int {
nodesByUserPtrLock.RLock()
node := nodesByUserPtr[uintptr(gn)]
nodesByUserPtrLock.RUnlock()
if node == nil {
return -1
}
*((*uintptr)(dataP)) = 0
tmp, found := node.stateObjectGet(int(objType), *((*[2]uint64)(id)))
if found && len(tmp) > 0 {
cData := C.malloc(C.ulong(len(tmp))) // GoGlue sends free() to the core as the free function
if uintptr(cData) == 0 {
return -1
}
*((*uintptr)(dataP)) = uintptr(cData)
return C.int(len(tmp))
}
return -1
}
//export goVirtualNetworkConfigFunc
func goVirtualNetworkConfigFunc(gn, _ unsafe.Pointer, nwid C.uint64_t, op C.int, conf unsafe.Pointer) {
nodesByUserPtrLock.RLock()
node := nodesByUserPtr[uintptr(gn)]
nodesByUserPtrLock.RUnlock()
if node == nil {
return
}
node.networksLock.RLock()
network := node.networks[NetworkID(nwid)]
node.networksLock.RUnlock()
if network != nil {
switch int(op) {
case networkConfigOpUp, networkConfigOpUpdate:
ncc := (*C.ZT_VirtualNetworkConfig)(conf)
if network.networkConfigRevision() > uint64(ncc.netconfRevision) {
return
}
var nc NetworkConfig
nc.ID = NetworkID(ncc.nwid)
nc.MAC = MAC(ncc.mac)
nc.Name = C.GoString(&ncc.name[0])
nc.Status = int(ncc.status)
nc.Type = int(ncc._type)
nc.MTU = int(ncc.mtu)
nc.Bridge = ncc.bridge != 0
nc.BroadcastEnabled = ncc.broadcastEnabled != 0
nc.NetconfRevision = uint64(ncc.netconfRevision)
for i := 0; i < int(ncc.assignedAddressCount); i++ {
a := sockaddrStorageToIPNet(&ncc.assignedAddresses[i])
if a != nil {
nc.AssignedAddresses = append(nc.AssignedAddresses, *a)
}
}
for i := 0; i < int(ncc.routeCount); i++ {
tgt := sockaddrStorageToIPNet(&ncc.routes[i].target)
viaN := sockaddrStorageToIPNet(&ncc.routes[i].via)
var via *net.IP
if viaN != nil && len(viaN.IP) > 0 {
via = &viaN.IP
}
if tgt != nil {
nc.Routes = append(nc.Routes, Route{
Target: *tgt,
Via: via,
Flags: uint16(ncc.routes[i].flags),
Metric: uint16(ncc.routes[i].metric),
})
}
}
node.runWaitGroup.Add(1)
go func() {
network.updateConfig(&nc, nil)
node.runWaitGroup.Done()
}()
}
}
}
//export goZtEvent
func goZtEvent(gn unsafe.Pointer, eventType C.int, data unsafe.Pointer) {
nodesByUserPtrLock.RLock()
node := nodesByUserPtr[uintptr(gn)]
nodesByUserPtrLock.RUnlock()
if node == nil {
return
}
switch eventType {
case C.ZT_EVENT_OFFLINE:
atomic.StoreUint32(&node.online, 0)
case C.ZT_EVENT_ONLINE:
atomic.StoreUint32(&node.online, 1)
}
}