From b6175bd408d65b08a29a1eed4aef9f717bc45577 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Mon, 23 Sep 2019 15:18:52 -0700 Subject: [PATCH] Gogogogogogogo --- go/cmd/zerotier/zerotier.go | 32 +++++ go/go.mod | 5 +- go/go.sum | 11 ++ go/native/GoGlue.cpp | 5 +- go/pkg/zerotier/api.go | 192 +++++++++++++++++++++---- go/pkg/zerotier/inetaddress.go | 123 ++++++++++++++++ go/pkg/zerotier/localconfig.go | 64 +++++++-- go/pkg/zerotier/node.go | 237 +++++++++++++++++-------------- go/pkg/zerotier/osdep-posix.go | 28 ++++ go/pkg/zerotier/osdep-windows.go | 28 ++++ go/pkg/zerotier/root.go | 7 +- go/pkg/zerotier/tap.go | 1 - 12 files changed, 583 insertions(+), 150 deletions(-) create mode 100644 go/cmd/zerotier/zerotier.go create mode 100644 go/pkg/zerotier/inetaddress.go create mode 100644 go/pkg/zerotier/osdep-posix.go create mode 100644 go/pkg/zerotier/osdep-windows.go diff --git a/go/cmd/zerotier/zerotier.go b/go/cmd/zerotier/zerotier.go new file mode 100644 index 000000000..fb3d6237f --- /dev/null +++ b/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() { +} diff --git a/go/go.mod b/go/go.mod index 8c49ab486..d2deeb177 100644 --- a/go/go.mod +++ b/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 +) diff --git a/go/go.sum b/go/go.sum index ad7bf1bb3..c925d783b 100644 --- a/go/go.sum +++ b/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= diff --git a/go/native/GoGlue.cpp b/go/native/GoGlue.cpp index 3a374c00b..19db5129e 100644 --- a/go/native/GoGlue.cpp +++ b/go/native/GoGlue.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #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 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 run; std::map< ZT_SOCKET,ZT_GoNodeThread > threads; std::mutex threads_l; diff --git a/go/pkg/zerotier/api.go b/go/pkg/zerotier/api.go index e27a7c181..342b7e238 100644 --- a/go/pkg/zerotier/api.go +++ b/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) } }) - unixListener, err := net.Listen("unix", path.Join(basePath, "apisocket")) + 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) + } + }) + + 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 } diff --git a/go/pkg/zerotier/inetaddress.go b/go/pkg/zerotier/inetaddress.go new file mode 100644 index 000000000..18f6b3546 --- /dev/null +++ b/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 +} diff --git a/go/pkg/zerotier/localconfig.go b/go/pkg/zerotier/localconfig.go index 91e60b399..67f01a726 100644 --- a/go/pkg/zerotier/localconfig.go +++ b/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) } diff --git a/go/pkg/zerotier/node.go b/go/pkg/zerotier/node.go index 849578a1d..56020d2ab 100644 --- a/go/pkg/zerotier/node.go +++ b/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 - } - - recordTypes := C.GoBytes(dnsRecordTypes, numDNSRecordTypes) - recordName := C.GoString((*C.char)(name)) - 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)) + 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) { diff --git a/go/pkg/zerotier/osdep-posix.go b/go/pkg/zerotier/osdep-posix.go new file mode 100644 index 000000000..e10e32563 --- /dev/null +++ b/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) +} diff --git a/go/pkg/zerotier/osdep-windows.go b/go/pkg/zerotier/osdep-windows.go new file mode 100644 index 000000000..8a9c99234 --- /dev/null +++ b/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) +} diff --git a/go/pkg/zerotier/root.go b/go/pkg/zerotier/root.go index a8731c4e0..fdd3a8efb 100644 --- a/go/pkg/zerotier/root.go +++ b/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 } diff --git a/go/pkg/zerotier/tap.go b/go/pkg/zerotier/tap.go index bacf269b5..c64376488 100644 --- a/go/pkg/zerotier/tap.go +++ b/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 }