Browse Source

Gogogogogogogo

Adam Ierymenko 5 years ago
parent
commit
b6175bd408

+ 32 - 0
go/cmd/zerotier/zerotier.go

@@ -0,0 +1,32 @@
+/*
+ * 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 main
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+)
+
+func nodeStart() {
+	osSignalChannel := make(chan os.Signal, 2)
+	signal.Notify(osSignalChannel, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGBUS)
+	signal.Ignore(syscall.SIGUSR1, syscall.SIGUSR2)
+	go func() {
+		<-osSignalChannel
+	}()
+}
+
+func main() {
+}

+ 4 - 1
go/go.mod

@@ -2,4 +2,7 @@ module zerotier
 
 go 1.13
 
-require github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95
+require (
+	github.com/Microsoft/go-winio v0.4.14 // indirect
+	github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95
+)

+ 11 - 0
go/go.sum

@@ -1,4 +1,15 @@
+github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
+github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95 h1:S4qyfL2sEm5Budr4KVMyEniCy+PbS55651I/a+Kn/NQ=
 github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190529164535-6a60838ec259 h1:so6Hr/LodwSZ5UQDu/7PmQiDeS112WwtLvU3lpSPZTU=
 golang.org/x/sys v0.0.0-20190529164535-6a60838ec259/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

+ 3 - 2
go/native/GoGlue.cpp

@@ -57,6 +57,7 @@
 #include <array>
 #include <set>
 #include <memory>
+#include <atomic>
 
 #ifdef __WINDOWS__
 #define SETSOCKOPT_FLAG_TYPE BOOL
@@ -79,7 +80,7 @@ struct ZT_GoNodeThread
 	std::string ip;
 	int port;
 	int af;
-	std::atomic_bool run;
+	std::atomic<bool> run;
 	std::thread thr;
 };
 
@@ -89,7 +90,7 @@ struct ZT_GoNode_Impl
 	volatile int64_t nextBackgroundTaskDeadline;
 
 	std::string path;
-	std::atomic_bool run;
+	std::atomic<bool> run;
 
 	std::map< ZT_SOCKET,ZT_GoNodeThread > threads;
 	std::mutex threads_l;

+ 168 - 24
go/pkg/zerotier/api.go

@@ -14,24 +14,32 @@
 package zerotier
 
 import (
+	secrand "crypto/rand"
 	"encoding/json"
+	"fmt"
+	"io/ioutil"
 	"net"
 	"net/http"
 	"path"
+	"strings"
 	"time"
+
+	acl "github.com/hectane/go-acl"
 )
 
 type apiStatus struct {
-	Address         Address
-	Clock           int64
-	Config          *LocalConfig
-	Online          bool
-	Identity        *Identity
-	Version         string
-	VersionMajor    int
-	VersionMinor    int
-	VersionRevision int
-	VersionBuild    int
+	Address                 Address
+	Clock                   int64
+	Config                  LocalConfig
+	Online                  bool
+	Identity                *Identity
+	InterfaceAddresses      []net.IP
+	MappedExternalAddresses []*InetAddress
+	Version                 string
+	VersionMajor            int
+	VersionMinor            int
+	VersionRevision         int
+	VersionBuild            int
 }
 
 type apiNetwork struct {
@@ -80,36 +88,119 @@ func apiReadObj(out http.ResponseWriter, req *http.Request, dest interface{}) (e
 	return
 }
 
+func apiCheckAuth(out http.ResponseWriter, req *http.Request, token string) bool {
+	ah := req.Header.Get("X-ZT1-Auth")
+	if len(ah) > 0 && strings.TrimSpace(ah) == token {
+		return true
+	}
+	ah = req.Header.Get("Authorization")
+	if len(ah) > 0 && strings.TrimSpace(ah) == ("bearer "+token) {
+		return true
+	}
+	apiSendObj(out, req, http.StatusUnauthorized, nil)
+	return false
+}
+
 // createAPIServer creates and starts an HTTP server for a given node
 func createAPIServer(basePath string, node *Node) (*http.Server, error) {
+	// Read authorization token, automatically generating one if it's missing
+	var authToken string
+	authTokenFile := path.Join(basePath, "authtoken.secret")
+	authTokenB, err := ioutil.ReadFile(authTokenFile)
+	if err != nil {
+		var atb [20]byte
+		_, err = secrand.Read(atb[:])
+		if err != nil {
+			return nil, err
+		}
+		for i := 0; i < 20; i++ {
+			atb[i] = byte("abcdefghijklmnopqrstuvwxyz0123456789"[atb[i]%36])
+		}
+		err = ioutil.WriteFile(authTokenFile, atb[:], 0600)
+		if err != nil {
+			return nil, err
+		}
+		acl.Chmod(authTokenFile, 0600)
+		authToken = string(atb[:])
+	} else {
+		authToken = strings.TrimSpace(string(authTokenB))
+	}
+
 	smux := http.NewServeMux()
 
-	smux.HandleFunc("/config", func(out http.ResponseWriter, req *http.Request) {
+	smux.HandleFunc("/status", func(out http.ResponseWriter, req *http.Request) {
+		if !apiCheckAuth(out, req, authToken) {
+			return
+		}
 		apiSetStandardHeaders(out)
 		if req.Method == http.MethodGet || req.Method == http.MethodHead {
-			apiSendObj(out, req, http.StatusOK, nil)
+			apiSendObj(out, req, http.StatusOK, &apiStatus{
+				Address:                 node.Address(),
+				Clock:                   TimeMs(),
+				Config:                  node.LocalConfig(),
+				Online:                  node.Online(),
+				Identity:                node.Identity(),
+				InterfaceAddresses:      node.InterfaceAddresses(),
+				MappedExternalAddresses: nil,
+				Version:                 fmt.Sprintf("%d.%d.%d", CoreVersionMajor, CoreVersionMinor, CoreVersionRevision),
+				VersionMajor:            CoreVersionMajor,
+				VersionMinor:            CoreVersionMinor,
+				VersionRevision:         CoreVersionRevision,
+				VersionBuild:            CoreVersionBuild,
+			})
 		} else {
 			out.Header().Set("Allow", "GET, HEAD")
 			apiSendObj(out, req, http.StatusMethodNotAllowed, nil)
 		}
 	})
 
-	smux.HandleFunc("/status", func(out http.ResponseWriter, req *http.Request) {
+	smux.HandleFunc("/config", func(out http.ResponseWriter, req *http.Request) {
+		if !apiCheckAuth(out, req, authToken) {
+			return
+		}
 		apiSetStandardHeaders(out)
-		if req.Method == http.MethodGet || req.Method == http.MethodHead {
-			var status apiStatus
-			apiSendObj(out, req, http.StatusOK, &status)
+		if req.Method == http.MethodPost || req.Method == http.MethodPut {
+			var c LocalConfig
+			if apiReadObj(out, req, &c) == nil {
+				apiSendObj(out, req, http.StatusOK, node.LocalConfig())
+			}
+		} else if req.Method == http.MethodGet || req.Method == http.MethodHead {
+			apiSendObj(out, req, http.StatusOK, node.LocalConfig())
 		} else {
-			out.Header().Set("Allow", "GET, HEAD")
+			out.Header().Set("Allow", "GET, HEAD, PUT, POST")
 			apiSendObj(out, req, http.StatusMethodNotAllowed, nil)
 		}
 	})
 
 	smux.HandleFunc("/peer/", func(out http.ResponseWriter, req *http.Request) {
+		if !apiCheckAuth(out, req, authToken) {
+			return
+		}
 		apiSetStandardHeaders(out)
+
+		var queriedID Address
+		if len(req.URL.Path) > 6 {
+			var err error
+			queriedID, err = NewAddressFromString(req.URL.Path[6:])
+			if err != nil {
+				apiSendObj(out, req, http.StatusNotFound, nil)
+				return
+			}
+		}
+
 		if req.Method == http.MethodGet || req.Method == http.MethodHead {
 			peers := node.Peers()
-			apiSendObj(out, req, http.StatusOK, peers)
+			if queriedID != 0 {
+				p2 := make([]*Peer, 0, len(peers))
+				for _, p := range peers {
+					if p.Address == queriedID {
+						p2 = append(p2, p)
+					}
+				}
+				apiSendObj(out, req, http.StatusOK, p2)
+			} else {
+				apiSendObj(out, req, http.StatusOK, peers)
+			}
 		} else {
 			out.Header().Set("Allow", "GET, HEAD")
 			apiSendObj(out, req, http.StatusMethodNotAllowed, nil)
@@ -117,17 +208,67 @@ func createAPIServer(basePath string, node *Node) (*http.Server, error) {
 	})
 
 	smux.HandleFunc("/network/", func(out http.ResponseWriter, req *http.Request) {
+		if !apiCheckAuth(out, req, authToken) {
+			return
+		}
 		apiSetStandardHeaders(out)
-		if req.Method == http.MethodGet || req.Method == http.MethodHead {
-			networks := node.Networks()
-			apiSendObj(out, req, http.StatusOK, networks)
+
+		var queriedID NetworkID
+		if len(req.URL.Path) > 9 {
+			var err error
+			queriedID, err = NewNetworkIDFromString(req.URL.Path[9:])
+			if err != nil {
+				apiSendObj(out, req, http.StatusNotFound, nil)
+				return
+			}
+		}
+
+		if req.Method == http.MethodPost || req.Method == http.MethodPut {
+			if queriedID == 0 {
+				apiSendObj(out, req, http.StatusBadRequest, nil)
+			}
+		} else if req.Method == http.MethodGet || req.Method == http.MethodHead {
+			if queriedID == 0 { // no queried ID lists all networks
+				networks := node.Networks()
+				apiSendObj(out, req, http.StatusOK, networks)
+			} else {
+			}
 		} else {
-			out.Header().Set("Allow", "GET, HEAD")
+			out.Header().Set("Allow", "GET, HEAD, PUT, POST")
+			apiSendObj(out, req, http.StatusMethodNotAllowed, nil)
+		}
+	})
+
+	smux.HandleFunc("/root/", func(out http.ResponseWriter, req *http.Request) {
+		if !apiCheckAuth(out, req, authToken) {
+			return
+		}
+		apiSetStandardHeaders(out)
+
+		var queriedID Address
+		if len(req.URL.Path) > 6 {
+			var err error
+			queriedID, err = NewAddressFromString(req.URL.Path[6:])
+			if err != nil {
+				apiSendObj(out, req, http.StatusNotFound, nil)
+				return
+			}
+		}
+
+		if req.Method == http.MethodPost || req.Method == http.MethodPut {
+			if queriedID == 0 {
+				apiSendObj(out, req, http.StatusBadRequest, nil)
+			}
+		} else if req.Method == http.MethodGet || req.Method == http.MethodHead {
+			roots := node.Roots()
+			apiSendObj(out, req, http.StatusOK, roots)
+		} else {
+			out.Header().Set("Allow", "GET, HEAD, PUT, POST")
 			apiSendObj(out, req, http.StatusMethodNotAllowed, nil)
 		}
 	})
 
-	unixListener, err := net.Listen("unix", path.Join(basePath, "apisocket"))
+	listener, err := createNamedSocketListener(basePath, "apisocket")
 	if err != nil {
 		return nil, err
 	}
@@ -139,7 +280,10 @@ func createAPIServer(basePath string, node *Node) (*http.Server, error) {
 		WriteTimeout:   600 * time.Second,
 	}
 	httpServer.SetKeepAlivesEnabled(true)
-	go httpServer.Serve(unixListener)
+	go func() {
+		httpServer.Serve(listener)
+		listener.Close()
+	}()
 
 	return httpServer, nil
 }

+ 123 - 0
go/pkg/zerotier/inetaddress.go

@@ -0,0 +1,123 @@
+/*
+ * 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 (
+	"bytes"
+	"encoding/json"
+	"net"
+	"strconv"
+	"strings"
+	"unsafe"
+)
+
+// InetAddress implements net.Addr but has a ZeroTier-like string representation
+type InetAddress struct {
+	IP   net.IP
+	Port int
+}
+
+// Less returns true if this IP/port is lexicographically less than another
+func (i *InetAddress) Less(i2 *InetAddress) bool {
+	c := bytes.Compare(i.IP, i2.IP)
+	if c < 0 {
+		return true
+	}
+	if c == 0 {
+		return i.Port < i2.Port
+	}
+	return false
+}
+
+// NewInetAddressFromString parses an IP[/port] format address
+func NewInetAddressFromString(s string) *InetAddress {
+	i := new(InetAddress)
+	ss := strings.Split(s, "/")
+	if len(ss) > 0 {
+		i.IP = net.ParseIP(ss[0])
+		i4 := i.IP.To4()
+		if len(i4) == 4 { // down-convert IPv4-in-6 IPs to native IPv4 as this is what all our code expects
+			i.IP = i4
+		}
+		if len(ss) > 1 {
+			p64, _ := strconv.ParseUint(s, 10, 64)
+			i.Port = int(p64 & 0xffff)
+		}
+	}
+	return i
+}
+
+// NewInetAddressFromSockaddr parses a sockaddr_in or sockaddr_in6 C structure (may crash if given something other than these!)
+// This is a convenience wrapper around the CGO functions in node.go.
+func NewInetAddressFromSockaddr(sa unsafe.Pointer) *InetAddress {
+	i := new(InetAddress)
+	if uintptr(sa) != 0 {
+		ua := sockaddrStorageToUDPAddr2(sa)
+		if ua != nil {
+			i.IP = ua.IP
+			i.Port = ua.Port
+		}
+	}
+	return i
+}
+
+// Network returns "udp" to implement net.Addr
+func (i *InetAddress) Network() string {
+	return "udp"
+}
+
+// String returns this address in ZeroTier-canonical IP/port format
+func (i *InetAddress) String() string {
+	return i.IP.String() + "/" + strconv.FormatInt(int64(i.Port), 10)
+}
+
+// Family returns the address family (AFInet etc.) or 0 if none
+func (i *InetAddress) Family() int {
+	switch len(i.IP) {
+	case 4:
+		return AFInet
+	case 16:
+		return AFInet6
+	}
+	return 0
+}
+
+// Valid returns true if both the IP and port have valid values
+func (i *InetAddress) Valid() bool {
+	return (len(i.IP) == 4 || len(i.IP) == 16) && (i.Port > 0 && i.Port < 65536)
+}
+
+// MarshalJSON marshals this MAC as a string
+func (i *InetAddress) MarshalJSON() ([]byte, error) {
+	s := i.String()
+	return json.Marshal(&s)
+}
+
+// UnmarshalJSON unmarshals this MAC from a string
+func (i *InetAddress) UnmarshalJSON(j []byte) error {
+	var s string
+	err := json.Unmarshal(j, &s)
+	if err != nil {
+		return err
+	}
+	*i = *NewInetAddressFromString(s)
+	return nil
+}
+
+// key returns a short array suitable for use as a map[] key for this IP
+func (i *InetAddress) key() (k [3]uint64) {
+	copy(((*[16]byte)(unsafe.Pointer(&k[0])))[:], i.IP)
+	k[2] = uint64(i.Port)
+	return
+}

+ 50 - 14
go/pkg/zerotier/localconfig.go

@@ -17,42 +17,69 @@ import (
 	"encoding/json"
 	"io/ioutil"
 	rand "math/rand"
-	"net"
 	"os"
 	"runtime"
 )
 
 // LocalConfigPhysicalPathConfiguration contains settings for physical paths
 type LocalConfigPhysicalPathConfiguration struct {
-	Blacklist     bool
+	// Blacklist flags this path as unusable for ZeroTier traffic
+	Blacklist bool
+
+	// TrustedPathID identifies a path for unencrypted/unauthenticated traffic
 	TrustedPathID uint64
 }
 
 // LocalConfigVirtualAddressConfiguration contains settings for virtual addresses
 type LocalConfigVirtualAddressConfiguration struct {
-	Try []net.Addr
+	// Try is a list of IPs/ports to try for this peer in addition to anything learned from roots or direct path push
+	Try []*InetAddress
 }
 
 // LocalConfigSettings contains node settings
 type LocalConfigSettings struct {
-	PrimaryPort              int
-	SecondaryPort            int
-	TertiaryPort             int
-	PortMappingEnabled       bool
-	MuiltipathMode           int
+	// PrimaryPort is the main UDP port and must be set (defaults to 9993)
+	PrimaryPort int
+
+	// SecondaryPort is the secondary UDP port, set to 0 to disbale (picked at random by default)
+	SecondaryPort int
+
+	// TertiaryPort is a third UDP port, set to 0 to disable (picked at random by default)
+	TertiaryPort int
+
+	// PortAutoSearch causes ZeroTier to try other ports automatically if it can't bind to configured ports
+	PortAutoSearch bool
+
+	// PortMappingEnabled enables uPnP and NAT-PMP support
+	PortMappingEnabled bool
+
+	// MultipathMode sets the multipath link aggregation mode
+	MuiltipathMode int
+
+	// InterfacePrefixBlacklist are prefixes of physical network interface names that won't be used by ZeroTier (e.g. "lo" or "utun")
 	InterfacePrefixBlacklist []string
+
+	// ExplicitAddresses are explicit IP/port addresses to advertise to other nodes, such as externally mapped ports on a router
+	ExplicitAddresses []*InetAddress
 }
 
 // LocalConfig is the local.conf file and stores local settings for the node.
 type LocalConfig struct {
+	// Physical path configurations by CIDR IP/bits
 	Physical map[string]*LocalConfigPhysicalPathConfiguration
-	Virtual  map[Address]*LocalConfigVirtualAddressConfiguration
-	Network  map[NetworkID]*NetworkLocalSettings
+
+	// Virtual node specific configurations by 10-digit hex ZeroTier address
+	Virtual map[Address]*LocalConfigVirtualAddressConfiguration
+
+	// Network local configurations by 16-digit hex ZeroTier network ID
+	Network map[NetworkID]*NetworkLocalSettings
+
+	// LocalConfigSettings contains other local settings for this node
 	Settings LocalConfigSettings
 }
 
 // Read this local config from a file, initializing to defaults if the file does not exist
-func (lc *LocalConfig) Read(p string) error {
+func (lc *LocalConfig) Read(p string, saveDefaultsIfNotExist bool) error {
 	if lc.Physical == nil {
 		lc.Physical = make(map[string]*LocalConfigPhysicalPathConfiguration)
 		lc.Virtual = make(map[Address]*LocalConfigVirtualAddressConfiguration)
@@ -60,6 +87,7 @@ func (lc *LocalConfig) Read(p string) error {
 		lc.Settings.PrimaryPort = 9993
 		lc.Settings.SecondaryPort = 16384 + (rand.Int() % 16384)
 		lc.Settings.TertiaryPort = 32768 + (rand.Int() % 16384)
+		lc.Settings.PortAutoSearch = true
 		lc.Settings.PortMappingEnabled = true
 		lc.Settings.MuiltipathMode = 0
 		switch runtime.GOOS {
@@ -75,10 +103,18 @@ func (lc *LocalConfig) Read(p string) error {
 	}
 
 	data, err := ioutil.ReadFile(p)
-	if err != nil && err != os.ErrNotExist {
-		return err
+	if err != nil {
+		if err != os.ErrNotExist {
+			return err
+		}
+		if saveDefaultsIfNotExist {
+			err = lc.Write(p)
+			if err != nil {
+				return err
+			}
+		}
+		return nil
 	}
-
 	return json.Unmarshal(data, lc)
 }
 

+ 131 - 104
go/pkg/zerotier/node.go

@@ -13,6 +13,9 @@
 
 package zerotier
 
+// This wraps the C++ Node implementation, C++ EthernetTap implementations,
+// and generally contains all the other CGO stuff.
+
 //#cgo CFLAGS: -O3
 //#cgo LDFLAGS: ${SRCDIR}/../../../build/node/libzt_core.a ${SRCDIR}/../../../build/osdep/libzt_osdep.a ${SRCDIR}/../../../build/go/native/libzt_go_native.a -lc++ -lpthread
 //#define ZT_CGO 1
@@ -20,14 +23,17 @@ package zerotier
 import "C"
 
 import (
+	"bytes"
 	"encoding/binary"
 	"errors"
 	"fmt"
 	"io/ioutil"
 	rand "math/rand"
 	"net"
+	"net/http"
 	"os"
 	"path"
+	"sort"
 	"sync"
 	"sync/atomic"
 	"time"
@@ -61,8 +67,8 @@ const (
 	// PlatformDefaultHomePath is the default location of ZeroTier's working path on this system
 	PlatformDefaultHomePath string = C.GoString(C.ZT_PLATFORM_DEFAULT_HOMEPATH)
 
-	afInet  = C.AF_INET
-	afInet6 = C.AF_INET6
+	AFInet  = C.AF_INET
+	AFInet6 = C.AF_INET6
 
 	defaultVirtualNetworkMTU = C.ZT_DEFAULT_MTU
 )
@@ -75,14 +81,14 @@ var (
 func sockaddrStorageToIPNet(ss *C.struct_sockaddr_storage) *net.IPNet {
 	var a net.IPNet
 	switch ss.ss_family {
-	case afInet:
+	case AFInet:
 		sa4 := (*C.struct_sockaddr_in)(unsafe.Pointer(ss))
 		var ip4 [4]byte
 		copy(ip4[:], (*[4]byte)(unsafe.Pointer(&sa4.sin_addr))[:])
 		a.IP = net.IP(ip4[:])
 		a.Mask = net.CIDRMask(int(binary.BigEndian.Uint16(((*[2]byte)(unsafe.Pointer(&sa4.sin_port)))[:])), 32)
 		return &a
-	case afInet6:
+	case AFInet6:
 		sa6 := (*C.struct_sockaddr_in6)(unsafe.Pointer(ss))
 		var ip6 [16]byte
 		copy(ip6[:], (*[16]byte)(unsafe.Pointer(&sa6.sin6_addr))[:])
@@ -96,14 +102,14 @@ func sockaddrStorageToIPNet(ss *C.struct_sockaddr_storage) *net.IPNet {
 func sockaddrStorageToUDPAddr(ss *C.struct_sockaddr_storage) *net.UDPAddr {
 	var a net.UDPAddr
 	switch ss.ss_family {
-	case afInet:
+	case AFInet:
 		sa4 := (*C.struct_sockaddr_in)(unsafe.Pointer(ss))
 		var ip4 [4]byte
 		copy(ip4[:], (*[4]byte)(unsafe.Pointer(&sa4.sin_addr))[:])
 		a.IP = net.IP(ip4[:])
 		a.Port = int(binary.BigEndian.Uint16(((*[2]byte)(unsafe.Pointer(&sa4.sin_port)))[:]))
 		return &a
-	case afInet6:
+	case AFInet6:
 		sa6 := (*C.struct_sockaddr_in6)(unsafe.Pointer(ss))
 		var ip6 [16]byte
 		copy(ip6[:], (*[16]byte)(unsafe.Pointer(&sa6.sin6_addr))[:])
@@ -114,18 +120,22 @@ func sockaddrStorageToUDPAddr(ss *C.struct_sockaddr_storage) *net.UDPAddr {
 	return nil
 }
 
+func sockaddrStorageToUDPAddr2(ss unsafe.Pointer) *net.UDPAddr {
+	return sockaddrStorageToUDPAddr((*C.struct_sockaddr_storage)(ss))
+}
+
 func makeSockaddrStorage(ip net.IP, port int, ss *C.struct_sockaddr_storage) bool {
 	C.memset(unsafe.Pointer(ss), 0, C.sizeof_struct_sockaddr_storage)
 	if len(ip) == 4 {
 		sa4 := (*C.struct_sockaddr_in)(unsafe.Pointer(ss))
-		sa4.sin_family = afInet
+		sa4.sin_family = AFInet
 		copy(((*[4]byte)(unsafe.Pointer(&sa4.sin_addr)))[:], ip)
 		binary.BigEndian.PutUint16(((*[2]byte)(unsafe.Pointer(&sa4.sin_port)))[:], uint16(port))
 		return true
 	}
 	if len(ip) == 16 {
 		sa6 := (*C.struct_sockaddr_in6)(unsafe.Pointer(ss))
-		sa6.sin6_family = afInet6
+		sa6.sin6_family = AFInet6
 		copy(((*[16]byte)(unsafe.Pointer(&sa6.sin6_addr)))[:], ip)
 		binary.BigEndian.PutUint16(((*[2]byte)(unsafe.Pointer(&sa6.sin6_port)))[:], uint16(port))
 		return true
@@ -137,26 +147,26 @@ func makeSockaddrStorage(ip net.IP, port int, ss *C.struct_sockaddr_storage) boo
 
 // Node is an instance of the ZeroTier core node and related C++ I/O code
 type Node struct {
-	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
+	basePath               string
+	localConfigPath        string
+	localConfig            LocalConfig
+	networks               map[NetworkID]*Network
+	networksByMAC          map[MAC]*Network  // locked by networksLock
+	interfaceAddresses     map[string]net.IP // physical external IPs on the machine
+	localConfigLock        sync.RWMutex
+	networksLock           sync.RWMutex
+	interfaceAddressesLock sync.Mutex
+	gn                     *C.ZT_GoNode
+	zn                     *C.ZT_Node
+	id                     *Identity
+	apiServer              *http.Server
+	online                 uint32
+	running                uint32
+	runLock                sync.Mutex
 }
 
 // NewNode creates and initializes a new instance of the ZeroTier node service
 func NewNode(basePath string) (*Node, error) {
-	var err error
-
 	os.MkdirAll(basePath, 0755)
 	if _, err := os.Stat(basePath); err != nil {
 		return nil, err
@@ -164,9 +174,14 @@ func NewNode(basePath string) (*Node, error) {
 
 	n := new(Node)
 	n.basePath = basePath
+	n.localConfigPath = path.Join(basePath, "local.conf")
+	err := n.localConfig.Read(n.localConfigPath, true)
+	if err != nil {
+		return nil, err
+	}
 	n.networks = make(map[NetworkID]*Network)
 	n.networksByMAC = make(map[MAC]*Network)
-	n.externalAddresses = make(map[string]*net.IPNet)
+	n.interfaceAddresses = make(map[string]net.IP)
 
 	cpath := C.CString(basePath)
 	n.gn = C.ZT_GoNode_new(cpath)
@@ -184,6 +199,12 @@ func NewNode(basePath string) (*Node, error) {
 		return nil, err
 	}
 
+	n.apiServer, err = createAPIServer(basePath, n)
+	if err != nil {
+		C.ZT_GoNode_delete(n.gn)
+		return nil, err
+	}
+
 	gnRawAddr := uintptr(unsafe.Pointer(n.gn))
 	nodesByUserPtrLock.Lock()
 	nodesByUserPtr[gnRawAddr] = n
@@ -202,7 +223,7 @@ func NewNode(basePath string) (*Node, error) {
 			if (now - lastScannedInterfaces) >= 30000 {
 				lastScannedInterfaces = now
 
-				externalAddresses := make(map[string]*net.IPNet)
+				interfaceAddresses := make(map[string]net.IP)
 				ifs, _ := net.Interfaces()
 				if len(ifs) > 0 {
 					n.networksLock.RLock()
@@ -214,7 +235,7 @@ func NewNode(basePath string) (*Node, error) {
 								for _, a := range addrs {
 									ipn, _ := a.(*net.IPNet)
 									if ipn != nil {
-										externalAddresses[ipn.String()] = ipn
+										interfaceAddresses[ipn.IP.String()] = ipn.IP
 									}
 								}
 							}
@@ -224,10 +245,10 @@ func NewNode(basePath string) (*Node, error) {
 				}
 
 				n.localConfigLock.RLock()
-				n.externalAddressesLock.Lock()
-				for astr, ipn := range externalAddresses {
-					if _, alreadyKnown := n.externalAddresses[astr]; !alreadyKnown {
-						ipCstr := C.CString(ipn.IP.String())
+				n.interfaceAddressesLock.Lock()
+				for astr, ipn := range interfaceAddresses {
+					if _, alreadyKnown := n.interfaceAddresses[astr]; !alreadyKnown {
+						ipCstr := C.CString(ipn.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))
 						}
@@ -240,9 +261,9 @@ func NewNode(basePath string) (*Node, error) {
 						C.free(unsafe.Pointer(ipCstr))
 					}
 				}
-				for astr, ipn := range n.externalAddresses {
-					if _, stillPresent := externalAddresses[astr]; !stillPresent {
-						ipCstr := C.CString(ipn.IP.String())
+				for astr, ipn := range n.interfaceAddresses {
+					if _, stillPresent := interfaceAddresses[astr]; !stillPresent {
+						ipCstr := C.CString(ipn.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))
 						}
@@ -255,8 +276,8 @@ func NewNode(basePath string) (*Node, error) {
 						C.free(unsafe.Pointer(ipCstr))
 					}
 				}
-				n.externalAddresses = externalAddresses
-				n.externalAddressesLock.Unlock()
+				n.interfaceAddresses = interfaceAddresses
+				n.interfaceAddressesLock.Unlock()
 				n.localConfigLock.RUnlock()
 			}
 		}
@@ -269,6 +290,7 @@ func NewNode(basePath string) (*Node, error) {
 // Close closes this Node and frees its underlying C++ Node structures
 func (n *Node) Close() {
 	if atomic.SwapUint32(&n.running, 0) != 0 {
+		n.apiServer.Close()
 		C.ZT_GoNode_delete(n.gn)
 		nodesByUserPtrLock.Lock()
 		delete(nodesByUserPtr, uintptr(unsafe.Pointer(n.gn)))
@@ -284,6 +306,21 @@ 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()
@@ -396,11 +433,11 @@ func (n *Node) Roots() []*Root {
 			root := (*C.ZT_Root)(unsafe.Pointer(uintptr(unsafe.Pointer(rl)) + C.sizeof_ZT_RootList))
 			id, err := NewIdentityFromString(C.GoString(root.identity))
 			if err == nil {
-				var addrs []net.Addr
+				var addrs []InetAddress
 				for j := uintptr(0); j < uintptr(root.addressCount); j++ {
-					a := sockaddrStorageToUDPAddr((*C.struct_sockaddr_storage)(unsafe.Pointer(uintptr(unsafe.Pointer(root.addresses)) + (j * C.sizeof_struct_sockaddr_storage))))
-					if a != nil {
-						addrs = append(addrs, a)
+					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{
@@ -488,10 +525,8 @@ func (n *Node) pathLookup(ztAddress Address) (net.IP, int) {
 	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
-		}
+		idx := rand.Int() % len(virt.Try)
+		return virt.Try[idx].IP, virt.Try[idx].Port
 	}
 	return nil, 0
 }
@@ -521,28 +556,24 @@ func (n *Node) makeStateObjectPath(objType int, id [2]uint64) (string, bool) {
 }
 
 func (n *Node) stateObjectPut(objType int, id [2]uint64, data []byte) {
-	go func() {
-		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
-			}
+	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) {
-	go func() {
-		fp, _ := n.makeStateObjectPath(objType, id)
-		if len(fp) > 0 {
-			os.Remove(fp)
-		}
-	}()
+	fp, _ := n.makeStateObjectPath(objType, id)
+	if len(fp) > 0 {
+		os.Remove(fp)
+	}
 }
 
 func (n *Node) stateObjectGet(objType int, id [2]uint64) ([]byte, bool) {
@@ -592,12 +623,12 @@ func goPathLookupFunc(gn unsafe.Pointer, ztAddress C.uint64_t, desiredAddressFam
 	if len(ip) > 0 && port > 0 && port <= 65535 {
 		ip4 := ip.To4()
 		if len(ip4) == 4 {
-			*((*C.int)(familyP)) = C.int(afInet)
+			*((*C.int)(familyP)) = C.int(AFInet)
 			copy((*[4]byte)(ipP)[:], ip4)
 			*((*C.int)(portP)) = C.int(port)
 			return 1
 		} else if len(ip) == 16 {
-			*((*C.int)(familyP)) = C.int(afInet6)
+			*((*C.int)(familyP)) = C.int(AFInet6)
 			copy((*[16]byte)(ipP)[:], ip)
 			*((*C.int)(portP)) = C.int(port)
 			return 1
@@ -609,17 +640,19 @@ func goPathLookupFunc(gn unsafe.Pointer, ztAddress C.uint64_t, desiredAddressFam
 
 //export goStateObjectPutFunc
 func goStateObjectPutFunc(gn unsafe.Pointer, objType C.int, id, data unsafe.Pointer, len C.int) {
-	nodesByUserPtrLock.RLock()
-	node := nodesByUserPtr[uintptr(gn)]
-	nodesByUserPtrLock.RUnlock()
-	if node == nil {
-		return
-	}
-	if len < 0 {
-		node.stateObjectDelete(int(objType), *((*[2]uint64)(id)))
-	} else {
-		node.stateObjectPut(int(objType), *((*[2]uint64)(id)), C.GoBytes(data, len))
-	}
+	go func() {
+		nodesByUserPtrLock.RLock()
+		node := nodesByUserPtr[uintptr(gn)]
+		nodesByUserPtrLock.RUnlock()
+		if node == nil {
+			return
+		}
+		if len < 0 {
+			node.stateObjectDelete(int(objType), *((*[2]uint64)(id)))
+		} else {
+			node.stateObjectPut(int(objType), *((*[2]uint64)(id)), C.GoBytes(data, len))
+		}
+	}()
 }
 
 //export goStateObjectGetFunc
@@ -642,17 +675,17 @@ func goStateObjectGetFunc(gn unsafe.Pointer, objType C.int, id, data unsafe.Poin
 
 //export goDNSResolverFunc
 func goDNSResolverFunc(gn unsafe.Pointer, dnsRecordTypes unsafe.Pointer, numDNSRecordTypes C.int, name unsafe.Pointer, requestID C.uintptr_t) {
-	nodesByUserPtrLock.RLock()
-	node := nodesByUserPtr[uintptr(gn)]
-	nodesByUserPtrLock.RUnlock()
-	if node == nil {
-		return
-	}
+	go func() {
+		nodesByUserPtrLock.RLock()
+		node := nodesByUserPtr[uintptr(gn)]
+		nodesByUserPtrLock.RUnlock()
+		if node == nil {
+			return
+		}
 
-	recordTypes := C.GoBytes(dnsRecordTypes, numDNSRecordTypes)
-	recordName := C.GoString((*C.char)(name))
+		recordTypes := C.GoBytes(dnsRecordTypes, numDNSRecordTypes)
+		recordName := C.GoString((*C.char)(name))
 
-	go func() {
 		recordNameCStrCopy := C.CString(recordName)
 		for _, rt := range recordTypes {
 			switch rt {
@@ -794,12 +827,12 @@ func (t *nativeTap) AddIP(ip *net.IPNet) error {
 		if bits > 128 || bits < 0 {
 			return ErrInvalidParameter
 		}
-		C.ZT_GoTap_addIp(t.tap, C.int(afInet6), unsafe.Pointer(&ip.IP[0]), C.int(bits))
+		C.ZT_GoTap_addIp(t.tap, C.int(AFInet6), unsafe.Pointer(&ip.IP[0]), C.int(bits))
 	} else if len(ip.IP) == 4 {
 		if bits > 32 || bits < 0 {
 			return ErrInvalidParameter
 		}
-		C.ZT_GoTap_addIp(t.tap, C.int(afInet), unsafe.Pointer(&ip.IP[0]), C.int(bits))
+		C.ZT_GoTap_addIp(t.tap, C.int(AFInet), unsafe.Pointer(&ip.IP[0]), C.int(bits))
 	}
 	return ErrInvalidParameter
 }
@@ -811,14 +844,14 @@ func (t *nativeTap) RemoveIP(ip *net.IPNet) error {
 		if bits > 128 || bits < 0 {
 			return ErrInvalidParameter
 		}
-		C.ZT_GoTap_removeIp(t.tap, C.int(afInet6), unsafe.Pointer(&ip.IP[0]), C.int(bits))
+		C.ZT_GoTap_removeIp(t.tap, C.int(AFInet6), unsafe.Pointer(&ip.IP[0]), C.int(bits))
 		return nil
 	}
 	if len(ip.IP) == 4 {
 		if bits > 32 || bits < 0 {
 			return ErrInvalidParameter
 		}
-		C.ZT_GoTap_removeIp(t.tap, C.int(afInet), unsafe.Pointer(&ip.IP[0]), C.int(bits))
+		C.ZT_GoTap_removeIp(t.tap, C.int(AFInet), unsafe.Pointer(&ip.IP[0]), C.int(bits))
 		return nil
 	}
 	return ErrInvalidParameter
@@ -839,7 +872,7 @@ func (t *nativeTap) IPs() (ips []net.IPNet, err error) {
 		af := int(ipbuf[ipptr])
 		ipptr++
 		switch af {
-		case afInet:
+		case AFInet:
 			var ip [4]byte
 			for j := 0; j < 4; j++ {
 				ip[j] = ipbuf[ipptr]
@@ -848,7 +881,7 @@ func (t *nativeTap) IPs() (ips []net.IPNet, err error) {
 			bits := ipbuf[ipptr]
 			ipptr++
 			ips = append(ips, net.IPNet{IP: net.IP(ip[:]), Mask: net.CIDRMask(int(bits), 32)})
-		case afInet6:
+		case AFInet6:
 			var ip [16]byte
 			for j := 0; j < 16; j++ {
 				ip[j] = ipbuf[ipptr]
@@ -888,16 +921,16 @@ func (t *nativeTap) AddRoute(r *Route) error {
 		if len(r.Target.IP) == 4 {
 			mask, _ := r.Target.Mask.Size()
 			if len(r.Via) == 4 {
-				rc = int(C.ZT_GoTap_addRoute(t.tap, afInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), afInet, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
+				rc = int(C.ZT_GoTap_addRoute(t.tap, AFInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), AFInet, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
 			} else {
-				rc = int(C.ZT_GoTap_addRoute(t.tap, afInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
+				rc = int(C.ZT_GoTap_addRoute(t.tap, AFInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
 			}
 		} else if len(r.Target.IP) == 16 {
 			mask, _ := r.Target.Mask.Size()
 			if len(r.Via) == 4 {
-				rc = int(C.ZT_GoTap_addRoute(t.tap, afInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), afInet6, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
+				rc = int(C.ZT_GoTap_addRoute(t.tap, AFInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), AFInet6, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
 			} else {
-				rc = int(C.ZT_GoTap_addRoute(t.tap, afInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
+				rc = int(C.ZT_GoTap_addRoute(t.tap, AFInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
 			}
 		}
 	}
@@ -914,16 +947,16 @@ func (t *nativeTap) RemoveRoute(r *Route) error {
 		if len(r.Target.IP) == 4 {
 			mask, _ := r.Target.Mask.Size()
 			if len(r.Via) == 4 {
-				rc = int(C.ZT_GoTap_removeRoute(t.tap, afInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), afInet, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
+				rc = int(C.ZT_GoTap_removeRoute(t.tap, AFInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), AFInet, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
 			} else {
-				rc = int(C.ZT_GoTap_removeRoute(t.tap, afInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
+				rc = int(C.ZT_GoTap_removeRoute(t.tap, AFInet, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
 			}
 		} else if len(r.Target.IP) == 16 {
 			mask, _ := r.Target.Mask.Size()
 			if len(r.Via) == 4 {
-				rc = int(C.ZT_GoTap_removeRoute(t.tap, afInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), afInet6, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
+				rc = int(C.ZT_GoTap_removeRoute(t.tap, AFInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), AFInet6, unsafe.Pointer(&r.Via[0]), C.uint(r.Metric)))
 			} else {
-				rc = int(C.ZT_GoTap_removeRoute(t.tap, afInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
+				rc = int(C.ZT_GoTap_removeRoute(t.tap, AFInet6, unsafe.Pointer(&r.Target.IP[0]), C.int(mask), 0, nil, C.uint(r.Metric)))
 			}
 		}
 	}
@@ -933,12 +966,6 @@ func (t *nativeTap) RemoveRoute(r *Route) error {
 	return nil
 }
 
-// SyncRoutes synchronizes managed routes
-func (t *nativeTap) SyncRoutes() error {
-	C.ZT_GoTap_syncRoutes(t.tap)
-	return nil
-}
-
 //////////////////////////////////////////////////////////////////////////////
 
 func handleTapMulticastGroupChange(gn unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t, added bool) {

+ 28 - 0
go/pkg/zerotier/osdep-posix.go

@@ -0,0 +1,28 @@
+// +build !windows
+
+/*
+ * 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 (
+	"net"
+	"os"
+	"path"
+)
+
+func createNamedSocketListener(basePath, name string) (net.Listener, error) {
+	apiSockPath := path.Join(basePath, name)
+	os.Remove(apiSockPath)
+	return net.Listen("unix", apiSockPath)
+}

+ 28 - 0
go/pkg/zerotier/osdep-windows.go

@@ -0,0 +1,28 @@
+// +build windows
+
+/*
+ * 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 (
+	"net"
+
+	winio "github.com/Microsoft/go-winio"
+)
+
+const windowsAPISocketPathPrefix = "\\\\.\\pipe\\zerotier_"
+
+func createNamedSocketListener(basePath, name string) (net.Listener, error) {
+	winio.ListenPipe(windowsAPISocketPathPrefix+name, nil)
+}

+ 4 - 3
go/pkg/zerotier/root.go

@@ -13,13 +13,14 @@
 
 package zerotier
 
-import "net"
-
 // Root describes a root server used to find and establish communication with other nodes.
 type Root struct {
 	DNSName   string
 	Identity  *Identity
-	Addresses []net.Addr
+	Addresses []InetAddress
 	Preferred bool
 	Online    bool
 }
+
+// Static returns true if this is a static root
+func (r *Root) Static() bool { return len(r.DNSName) == 0 }

+ 0 - 1
go/pkg/zerotier/tap.go

@@ -28,5 +28,4 @@ type Tap interface {
 	AddMulticastGroupChangeHandler(func(bool, *MulticastGroup))
 	AddRoute(r *Route) error
 	RemoveRoute(r *Route) error
-	SyncRoutes() error
 }