From 536bc59abb82fe5c571db9595080345bc2c69347 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Sun, 22 Sep 2019 22:25:55 -0700 Subject: [PATCH] . --- go/pkg/zerotier/api.go | 145 +++++++++++++++++++++++++ go/pkg/zerotier/localconfig.go | 67 +++++++----- go/pkg/zerotier/mac.go | 30 ++++++ go/pkg/zerotier/network.go | 11 +- go/pkg/zerotier/node.go | 192 ++++++++++++++++++++++++++++----- 5 files changed, 390 insertions(+), 55 deletions(-) create mode 100644 go/pkg/zerotier/api.go diff --git a/go/pkg/zerotier/api.go b/go/pkg/zerotier/api.go new file mode 100644 index 000000000..e27a7c181 --- /dev/null +++ b/go/pkg/zerotier/api.go @@ -0,0 +1,145 @@ +/* + * 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 zerotier + +import ( + "encoding/json" + "net" + "net/http" + "path" + "time" +) + +type apiStatus struct { + Address Address + Clock int64 + Config *LocalConfig + Online bool + Identity *Identity + Version string + VersionMajor int + VersionMinor int + VersionRevision int + VersionBuild int +} + +type apiNetwork struct { + Config *NetworkConfig + Settings *NetworkLocalSettings + MulticastSubscriptions []*MulticastGroup + TapDeviceType string + TapDeviceName string + TapDeviceEnabled bool +} + +func apiSetStandardHeaders(out http.ResponseWriter) { + now := time.Now().UTC() + h := out.Header() + h.Set("Cache-Control", "no-cache, no-store, must-revalidate") + h.Set("Expires", "0") + h.Set("Pragma", "no-cache") + h.Set("Date", now.Format(time.RFC1123)) +} + +func apiSendObj(out http.ResponseWriter, req *http.Request, httpStatusCode int, obj interface{}) error { + h := out.Header() + h.Set("Content-Type", "application/json") + if req.Method == http.MethodHead { + out.WriteHeader(httpStatusCode) + return nil + } + var j []byte + var err error + if obj != nil { + j, err = json.Marshal(obj) + if err != nil { + return err + } + } + out.WriteHeader(httpStatusCode) + _, err = out.Write(j) + return err +} + +func apiReadObj(out http.ResponseWriter, req *http.Request, dest interface{}) (err error) { + err = json.NewDecoder(req.Body).Decode(&dest) + if err != nil { + apiSendObj(out, req, http.StatusBadRequest, nil) + } + return +} + +// createAPIServer creates and starts an HTTP server for a given node +func createAPIServer(basePath string, node *Node) (*http.Server, error) { + smux := http.NewServeMux() + + smux.HandleFunc("/config", func(out http.ResponseWriter, req *http.Request) { + apiSetStandardHeaders(out) + if req.Method == http.MethodGet || req.Method == http.MethodHead { + apiSendObj(out, req, http.StatusOK, nil) + } else { + out.Header().Set("Allow", "GET, HEAD") + apiSendObj(out, req, http.StatusMethodNotAllowed, nil) + } + }) + + smux.HandleFunc("/status", func(out http.ResponseWriter, req *http.Request) { + apiSetStandardHeaders(out) + if req.Method == http.MethodGet || req.Method == http.MethodHead { + var status apiStatus + apiSendObj(out, req, http.StatusOK, &status) + } else { + out.Header().Set("Allow", "GET, HEAD") + apiSendObj(out, req, http.StatusMethodNotAllowed, nil) + } + }) + + smux.HandleFunc("/peer/", func(out http.ResponseWriter, req *http.Request) { + apiSetStandardHeaders(out) + if req.Method == http.MethodGet || req.Method == http.MethodHead { + peers := node.Peers() + apiSendObj(out, req, http.StatusOK, peers) + } else { + out.Header().Set("Allow", "GET, HEAD") + apiSendObj(out, req, http.StatusMethodNotAllowed, nil) + } + }) + + smux.HandleFunc("/network/", func(out http.ResponseWriter, req *http.Request) { + apiSetStandardHeaders(out) + if req.Method == http.MethodGet || req.Method == http.MethodHead { + networks := node.Networks() + apiSendObj(out, req, http.StatusOK, networks) + } else { + out.Header().Set("Allow", "GET, HEAD") + apiSendObj(out, req, http.StatusMethodNotAllowed, nil) + } + }) + + unixListener, err := net.Listen("unix", path.Join(basePath, "apisocket")) + if err != nil { + return nil, err + } + httpServer := &http.Server{ + MaxHeaderBytes: 4096, + Handler: smux, + IdleTimeout: 10 * time.Second, + ReadTimeout: 10 * time.Second, + WriteTimeout: 600 * time.Second, + } + httpServer.SetKeepAlivesEnabled(true) + go httpServer.Serve(unixListener) + + return httpServer, nil +} diff --git a/go/pkg/zerotier/localconfig.go b/go/pkg/zerotier/localconfig.go index 29d187e81..91e60b399 100644 --- a/go/pkg/zerotier/localconfig.go +++ b/go/pkg/zerotier/localconfig.go @@ -14,7 +14,11 @@ package zerotier import ( + "encoding/json" + "io/ioutil" + rand "math/rand" "net" + "os" "runtime" ) @@ -41,33 +45,48 @@ type LocalConfigSettings struct { // LocalConfig is the local.conf file and stores local settings for the node. type LocalConfig struct { - Physical map[string]LocalConfigPhysicalPathConfiguration - Virtual map[Address]LocalConfigVirtualAddressConfiguration + Physical map[string]*LocalConfigPhysicalPathConfiguration + Virtual map[Address]*LocalConfigVirtualAddressConfiguration + Network map[NetworkID]*NetworkLocalSettings Settings LocalConfigSettings } -// NewLocalConfig creates a new local.conf file with defaults -func NewLocalConfig() *LocalConfig { - lc := &LocalConfig{ - Physical: make(map[string]LocalConfigPhysicalPathConfiguration), - Virtual: make(map[Address]LocalConfigVirtualAddressConfiguration), - Settings: LocalConfigSettings{ - PrimaryPort: 9993, - SecondaryPort: 0, - TertiaryPort: 0, - PortMappingEnabled: true, - MuiltipathMode: 0, - }, +// Read this local config from a file, initializing to defaults if the file does not exist +func (lc *LocalConfig) Read(p string) error { + if lc.Physical == nil { + lc.Physical = make(map[string]*LocalConfigPhysicalPathConfiguration) + lc.Virtual = make(map[Address]*LocalConfigVirtualAddressConfiguration) + lc.Network = make(map[NetworkID]*NetworkLocalSettings) + lc.Settings.PrimaryPort = 9993 + lc.Settings.SecondaryPort = 16384 + (rand.Int() % 16384) + lc.Settings.TertiaryPort = 32768 + (rand.Int() % 16384) + lc.Settings.PortMappingEnabled = true + lc.Settings.MuiltipathMode = 0 + switch runtime.GOOS { + case "darwin": + lc.Settings.InterfacePrefixBlacklist = []string{"utun", "tun", "tap", "feth", "lo", "zt"} + case "linux": + lc.Settings.InterfacePrefixBlacklist = []string{"tun", "tap", "lo", "zt"} + case "freebsd", "openbsd", "netbsd", "illumos", "solaris", "dragonfly": + lc.Settings.InterfacePrefixBlacklist = []string{"tun", "tap", "zt"} + case "android": + lc.Settings.InterfacePrefixBlacklist = []string{"tun", "tap"} + } } - switch runtime.GOOS { - case "darwin": - lc.Settings.InterfacePrefixBlacklist = []string{"utun", "tun", "tap", "feth", "lo", "zt"} - case "linux": - lc.Settings.InterfacePrefixBlacklist = []string{"tun", "tap", "lo", "zt"} - case "freebsd", "openbsd", "netbsd", "illumos", "solaris", "dragonfly": - lc.Settings.InterfacePrefixBlacklist = []string{"tun", "tap", "zt"} - case "android": - lc.Settings.InterfacePrefixBlacklist = []string{"tun", "tap"} + + data, err := ioutil.ReadFile(p) + if err != nil && err != os.ErrNotExist { + return err } - return lc + + return json.Unmarshal(data, lc) +} + +// Write this local config to a file +func (lc *LocalConfig) Write(p string) error { + data, err := json.MarshalIndent(lc, "", "\t") + if err != nil { + return err + } + return ioutil.WriteFile(p, data, 0644) } diff --git a/go/pkg/zerotier/mac.go b/go/pkg/zerotier/mac.go index 6359cf8b2..1b8aeb05c 100644 --- a/go/pkg/zerotier/mac.go +++ b/go/pkg/zerotier/mac.go @@ -41,6 +41,36 @@ func NewMACFromString(s string) (MAC, error) { return MAC(m), nil } +// NewMACFromBytes decodes a MAC from a 6-byte array +func NewMACFromBytes(b []byte) (MAC, error) { + if len(b) < 6 { + return MAC(0), ErrInvalidMACAddress + } + var m uint64 + for i := 0; i < 6; i++ { + m <<= 8 + m |= uint64(b[i]) + } + return MAC(m), nil +} + +// NewMACForNetworkMember computes the static MAC for a given address and network ID +func NewMACForNetworkMember(addr Address, nwid NetworkID) MAC { + // This is the same algorithm as found in MAC::fromAddress() in MAC.hpp + firstOctetForNetwork := byte((byte(nwid) & 0xfe) | 0x02) + if firstOctetForNetwork == 0x52 { + firstOctetForNetwork = 0x32 + } + m := uint64(firstOctetForNetwork) << 40 + m |= uint64(addr) + m ^= ((uint64(nwid) >> 8) & 0xff) << 32 + m ^= ((uint64(nwid) >> 16) & 0xff) << 24 + m ^= ((uint64(nwid) >> 24) & 0xff) << 16 + m ^= ((uint64(nwid) >> 32) & 0xff) << 8 + m ^= (uint64(nwid) >> 40) & 0xff + return MAC(m) +} + // String returns this MAC address in canonical human-readable form func (m MAC) String() string { return fmt.Sprintf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", (uint64(m)>>40)&0xff, (uint64(m)>>32)&0xff, (uint64(m)>>24)&0xff, (uint64(m)>>16)&0xff, (uint64(m)>>8)&0xff, uint64(m)&0xff) diff --git a/go/pkg/zerotier/network.go b/go/pkg/zerotier/network.go index a6712a222..1533e1b2f 100644 --- a/go/pkg/zerotier/network.go +++ b/go/pkg/zerotier/network.go @@ -130,6 +130,7 @@ type NetworkLocalSettings struct { type Network struct { node *Node id NetworkID + mac MAC tap Tap config NetworkConfig settings NetworkLocalSettings // locked by configLock @@ -143,6 +144,7 @@ func newNetwork(node *Node, id NetworkID, t Tap) (*Network, error) { n := &Network{ node: node, id: id, + mac: NewMACForNetworkMember(node.Identity().address, id), tap: t, config: NetworkConfig{ ID: id, @@ -172,6 +174,12 @@ func newNetwork(node *Node, id NetworkID, t Tap) (*Network, error) { // ID gets this network's unique ID func (n *Network) ID() NetworkID { return n.id } +// MAC returns the assigned MAC address of this network +func (n *Network) MAC() MAC { return n.mac } + +// Tap gets this network's tap device +func (n *Network) Tap() Tap { return n.tap } + // Config returns a copy of this network's current configuration func (n *Network) Config() NetworkConfig { n.configLock.RLock() @@ -179,9 +187,6 @@ func (n *Network) Config() NetworkConfig { return n.config } -// Tap gets this network's tap device -func (n *Network) Tap() Tap { return n.tap } - // SetLocalSettings modifies this network's local settings func (n *Network) SetLocalSettings(ls *NetworkLocalSettings) { n.updateConfig(nil, ls) } diff --git a/go/pkg/zerotier/node.go b/go/pkg/zerotier/node.go index bd654d8a4..9a4f806dd 100644 --- a/go/pkg/zerotier/node.go +++ b/go/pkg/zerotier/node.go @@ -24,11 +24,13 @@ import ( "errors" "fmt" "io/ioutil" + rand "math/rand" "net" "os" "path" "sync" "sync/atomic" + "time" "unsafe" acl "github.com/hectane/go-acl" @@ -131,31 +133,40 @@ func makeSockaddrStorage(ip net.IP, port int, ss *C.struct_sockaddr_storage) boo ////////////////////////////////////////////////////////////////////////////// -// Node represents an instance of the ZeroTier core node and related C++ I/O code +// Node is an instance of the ZeroTier core node and related C++ I/O code type Node struct { - path string - networks map[uint64]*Network - networksLock sync.RWMutex - - gn *C.ZT_GoNode - zn *C.ZT_Node - - online uint32 - running uint32 + basePath string + localConfig LocalConfig + networks map[NetworkID]*Network + networksByMAC map[MAC]*Network // locked by networksLock + externalAddresses map[string]*net.IPNet + localConfigLock sync.RWMutex + networksLock sync.RWMutex + externalAddressesLock sync.Mutex + gn *C.ZT_GoNode + zn *C.ZT_Node + id *Identity + online uint32 + running uint32 + runLock sync.Mutex } // NewNode creates and initializes a new instance of the ZeroTier node service -func NewNode(path string) (*Node, error) { - os.MkdirAll(path, 0755) - if _, err := os.Stat(path); err != nil { +func NewNode(basePath string) (*Node, error) { + var err error + + os.MkdirAll(basePath, 0755) + if _, err := os.Stat(basePath); err != nil { return nil, err } n := new(Node) - n.path = path - n.networks = make(map[uint64]*Network) + n.basePath = basePath + n.networks = make(map[NetworkID]*Network) + n.networksByMAC = make(map[MAC]*Network) + n.externalAddresses = make(map[string]*net.IPNet) - cpath := C.CString(path) + cpath := C.CString(basePath) n.gn = C.ZT_GoNode_new(cpath) C.free(unsafe.Pointer(cpath)) if n.gn == nil { @@ -163,6 +174,14 @@ func NewNode(path string) (*Node, error) { } n.zn = (*C.ZT_Node)(C.ZT_GoNode_getNode(n.gn)) + var ns C.ZT_NodeStatus + C.ZT_Node_status(unsafe.Pointer(n.zn), &ns) + n.id, err = NewIdentityFromString(C.GoString(ns.secretIdentity)) + if err != nil { + C.ZT_GoNode_delete(n.gn) + return nil, err + } + gnRawAddr := uintptr(unsafe.Pointer(n.gn)) nodesByUserPtrLock.Lock() nodesByUserPtr[gnRawAddr] = n @@ -171,6 +190,77 @@ func NewNode(path string) (*Node, error) { n.online = 0 n.running = 1 + n.runLock.Lock() + go func() { + lastScannedInterfaces := int64(0) + for atomic.LoadUint32(&n.running) != 0 { + time.Sleep(1 * time.Second) + + now := TimeMs() + if (now - lastScannedInterfaces) >= 30000 { + lastScannedInterfaces = now + + externalAddresses := make(map[string]*net.IPNet) + ifs, _ := net.Interfaces() + if len(ifs) > 0 { + n.networksLock.RLock() + for _, i := range ifs { + m, _ := NewMACFromBytes(i.HardwareAddr) + if _, isZeroTier := n.networksByMAC[m]; !isZeroTier { + addrs, _ := i.Addrs() + if len(addrs) > 0 { + for _, a := range addrs { + ipn, _ := a.(*net.IPNet) + if ipn != nil { + externalAddresses[ipn.String()] = ipn + } + } + } + } + } + n.networksLock.RUnlock() + } + + n.localConfigLock.RLock() + n.externalAddressesLock.Lock() + for astr, ipn := range externalAddresses { + if _, alreadyKnown := n.externalAddresses[astr]; !alreadyKnown { + ipCstr := C.CString(ipn.IP.String()) + if n.localConfig.Settings.PrimaryPort > 0 && n.localConfig.Settings.PrimaryPort < 65536 { + C.ZT_GoNode_phyStartListen(n.gn, nil, ipCstr, C.int(n.localConfig.Settings.PrimaryPort)) + } + if n.localConfig.Settings.SecondaryPort > 0 && n.localConfig.Settings.SecondaryPort < 65536 { + C.ZT_GoNode_phyStartListen(n.gn, nil, ipCstr, C.int(n.localConfig.Settings.SecondaryPort)) + } + if n.localConfig.Settings.TertiaryPort > 0 && n.localConfig.Settings.TertiaryPort < 65536 { + C.ZT_GoNode_phyStartListen(n.gn, nil, ipCstr, C.int(n.localConfig.Settings.TertiaryPort)) + } + C.free(unsafe.Pointer(ipCstr)) + } + } + for astr, ipn := range n.externalAddresses { + if _, stillPresent := externalAddresses[astr]; !stillPresent { + ipCstr := C.CString(ipn.IP.String()) + if n.localConfig.Settings.PrimaryPort > 0 && n.localConfig.Settings.PrimaryPort < 65536 { + C.ZT_GoNode_phyStopListen(n.gn, nil, ipCstr, C.int(n.localConfig.Settings.PrimaryPort)) + } + if n.localConfig.Settings.SecondaryPort > 0 && n.localConfig.Settings.SecondaryPort < 65536 { + C.ZT_GoNode_phyStopListen(n.gn, nil, ipCstr, C.int(n.localConfig.Settings.SecondaryPort)) + } + if n.localConfig.Settings.TertiaryPort > 0 && n.localConfig.Settings.TertiaryPort < 65536 { + C.ZT_GoNode_phyStopListen(n.gn, nil, ipCstr, C.int(n.localConfig.Settings.TertiaryPort)) + } + C.free(unsafe.Pointer(ipCstr)) + } + } + n.externalAddresses = externalAddresses + n.externalAddressesLock.Unlock() + n.localConfigLock.RUnlock() + } + } + n.runLock.Unlock() + }() + return n, nil } @@ -181,14 +271,29 @@ func (n *Node) Close() { nodesByUserPtrLock.Lock() delete(nodesByUserPtr, uintptr(unsafe.Pointer(n.gn))) nodesByUserPtrLock.Unlock() + n.runLock.Lock() // wait for gorountine to die + n.runLock.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 } + +// LocalConfig gets this node's local configuration +func (n *Node) LocalConfig() LocalConfig { + n.localConfigLock.RLock() + defer n.localConfigLock.RUnlock() + return n.localConfig +} + // 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) { n.networksLock.RLock() - if nw, have := n.networks[nwid]; have { + if nw, have := n.networks[NetworkID(nwid)]; have { return nw, nil } n.networksLock.RUnlock() @@ -207,7 +312,7 @@ func (n *Node) Join(nwid uint64, tap Tap) (*Network, error) { return nil, err } n.networksLock.Lock() - n.networks[nwid] = nw + n.networks[NetworkID(nwid)] = nw n.networksLock.Unlock() return nw, nil @@ -217,11 +322,22 @@ func (n *Node) Join(nwid uint64, tap Tap) (*Network, error) { func (n *Node) Leave(nwid uint64) error { C.ZT_GoNode_leave(n.gn, C.uint64_t(nwid)) n.networksLock.Lock() - delete(n.networks, nwid) + delete(n.networks, NetworkID(nwid)) n.networksLock.Unlock() return nil } +// 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 +} + // AddStaticRoot adds a statically defined root server to this node. // If a static root with the given identity already exists this will update its IP and port information. func (n *Node) AddStaticRoot(id *Identity, addrs []net.Addr) { @@ -351,11 +467,30 @@ 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(ztAddress uint64, af int, ip net.IP, port int) bool { +func (n *Node) pathCheck(ztAddress Address, af int, ip net.IP, port int) 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(ztAddress uint64) (net.IP, int) { +func (n *Node) pathLookup(ztAddress Address) (net.IP, int) { + n.localConfigLock.RLock() + defer n.localConfigLock.RUnlock() + virt := n.localConfig.Virtual[ztAddress] + if virt != nil && len(virt.Try) > 0 { + udpA, _ := virt.Try[rand.Int()%len(virt.Try)].(*net.UDPAddr) + if udpA != nil { + return udpA.IP, udpA.Port + } + } return nil, 0 } @@ -364,21 +499,21 @@ func (n *Node) makeStateObjectPath(objType int, id [2]uint64) (string, bool) { secret := false switch objType { case C.ZT_STATE_OBJECT_IDENTITY_PUBLIC: - fp = path.Join(n.path, "identity.public") + fp = path.Join(n.basePath, "identity.public") case C.ZT_STATE_OBJECT_IDENTITY_SECRET: - fp = path.Join(n.path, "identity.secret") + fp = path.Join(n.basePath, "identity.secret") secret = true case C.ZT_STATE_OBJECT_PEER: - fp = path.Join(n.path, "peers.d") + 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.path, "networks.d") + 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_ROOT_LIST: - fp = path.Join(n.path, "roots") + fp = path.Join(n.basePath, "roots") } return fp, secret } @@ -436,7 +571,7 @@ func goPathCheckFunc(gn unsafe.Pointer, ztAddress C.uint64_t, af C.int, ip unsaf nodesByUserPtrLock.RLock() node := nodesByUserPtr[uintptr(gn)] nodesByUserPtrLock.RUnlock() - if node != nil && node.pathCheck(uint64(ztAddress), int(af), nil, int(port)) { + if node != nil && node.pathCheck(Address(ztAddress), int(af), nil, int(port)) { return 1 } return 0 @@ -451,7 +586,7 @@ func goPathLookupFunc(gn unsafe.Pointer, ztAddress C.uint64_t, desiredAddressFam return 0 } - ip, port := node.pathLookup(uint64(ztAddress)) + ip, port := node.pathLookup(Address(ztAddress)) if len(ip) > 0 && port > 0 && port <= 65535 { ip4 := ip.To4() if len(ip4) == 4 { @@ -466,6 +601,7 @@ func goPathLookupFunc(gn unsafe.Pointer, ztAddress C.uint64_t, desiredAddressFam return 1 } } + return 0 }