Attic cleaning, some work on certs, etc.

This commit is contained in:
Adam Ierymenko 2021-03-31 15:33:31 -04:00
parent 4a60ae5736
commit 11d367d5ec
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
63 changed files with 532 additions and 8263 deletions

View file

@ -1,346 +0,0 @@
cmake_minimum_required (VERSION 3.8)
cmake_policy(SET CMP0048 NEW)
if(${CMAKE_VERSION} VERSION_LESS 3.15)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.15)
endif()
set(ZEROTIER_VERSION_MAJOR 1 CACHE INTERNAL "")
set(ZEROTIER_VERSION_MINOR 9 CACHE INTERNAL "")
set(ZEROTIER_VERSION_REVISION 0 CACHE INTERNAL "")
set(ZEROTIER_VERSION_BUILD 0 CACHE INTERNAL "")
project(zerotier
VERSION ${ZEROTIER_VERSION_MAJOR}.${ZEROTIER_VERSION_MINOR}.${ZEROTIER_VERSION_REVISION}.${ZEROTIER_VERSION_BUILD}
DESCRIPTION "ZeroTier Network Hypervisor"
LANGUAGES CXX C)
if(NOT PACKAGE_STATIC)
find_program(
GO go
HINTS "/usr/local/go/bin" "/usr/bin" "/usr/local/bin" "C:/go/bin"
)
if(NOT GO)
message(FATAL_ERROR "Golang not found")
else(NOT GO)
message(STATUS "Found Golang at ${GO}")
endif(NOT GO)
set(default_build_type "Release")
if(WIN32)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_SYSTEM_VERSION "7" CACHE STRING INTERNAL FORCE)
else(WIN32)
if(APPLE)
set(CMAKE_CXX_STANDARD 17)
else(APPLE)
set(CMAKE_CXX_STANDARD 11)
endif(APPLE)
endif(WIN32)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "Setting build type to '${default_build_type}' as none was specified.")
set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()
option(BUILD_CENTRAL_CONTROLLER "Build ZeroTier Central Controller" OFF)
if(BUILD_CENTRAL_CONTROLLER)
find_package(PkgConfig REQUIRED)
if(APPLE)
set(CMAKE_PREFIX_PATH
${CMAKE_PREFIX_PATH}
/usr/local/opt/libpq
/usr/local/lib
)
endif(APPLE)
find_package(PostgreSQL REQUIRED)
pkg_check_modules(hiredis REQUIRED IMPORTED_TARGET hiredis)
add_subdirectory(controller/thirdparty/redis-plus-plus-1.1.1)
set(redispp_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/controller/thirdparty/redis-plus-plus-1.1.1/src/sw)
set(redispp_STATIC_LIB redispp_static)
endif(BUILD_CENTRAL_CONTROLLER)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_definitions(-DZT_DEBUG)
endif(CMAKE_BUILD_TYPE STREQUAL "Debug")
if(WIN32)
message("++ Setting Windows Compiler Flags ${CMAKE_BUILD_TYPE}")
add_definitions(-DNOMINMAX)
add_compile_options(
-Wall
-Wno-deprecated
-Wno-unused-function
-Wno-format
$<$<CONFIG:DEBUG>:-g>
$<$<CONFIG:DEBUG>:-O0>
$<$<CONFIG:RELEASE>:-O3>
$<$<CONFIG:RELEASE>:-ffast-math>
$<$<CONFIG:RELWITHDEBINFO>:-O3>
$<$<CONFIG:RELWITHDEBINFO>:-g>
)
set(GOFLAGS
-a
-trimpath
)
if(BUILD_32BIT)
set(CMAKE_SYSTEM_PROCESSOR "x86" CACHE STRING "system processor")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32" CACHE STRING "c++ flags")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32" CACHE STRING "c flags")
set(GOARCH "GOARCH=386" CACHE STRING "go architecture")
add_compile_options(
-m32
)
add_link_options(
-m32
)
endif(BUILD_32BIT)
else(WIN32)
set(GOFLAGS
-trimpath
-buildmode=pie
)
if(APPLE)
message("++ Setting MacOS Compiler Flags ${CMAKE_BUILD_TYPE}")
set(MACOS_VERSION_MIN "10.12")
add_compile_options(
-Wall
-Wno-deprecated
-Wno-unused-function
-mmacosx-version-min=${MACOS_VERSION_MIN}
$<$<CONFIG:DEBUG>:-g>
$<$<CONFIG:DEBUG>:-O0>
$<$<CONFIG:RELEASE>:-Ofast>
$<$<CONFIG:RELEASE>:-ffast-math>
$<$<CONFIG:RELEASE>:-fPIE>
$<$<CONFIG:RELEASE>:-flto>
$<$<CONFIG:RELWITHDEBINFO>:-O1>
$<$<CONFIG:RELWITHDEBINFO>:-fPIE>
$<$<CONFIG:RELWITHDEBINFO>:-g>
)
add_link_options(
-mmacosx-version-min=${MACOS_VERSION_MIN}
$<$<CONFIG:RELEASE>:-flto>
)
set(GOFLAGS
${GOFLAGS}
-a
-ldflags '-w -extldflags \"-Wl,-undefined -Wl,dynamic_lookup\"'
)
else(APPLE)
message("++ Setting Linux/BSD/Posix Compiler Flags (${CMAKE_BUILD_TYPE})")
add_compile_options(
-Wall
-Wno-deprecated
-Wno-unused-function
-Wno-format
$<$<CONFIG:DEBUG>:-g>
$<$<CONFIG:DEBUG>:-O0>
$<$<CONFIG:RELEASE>:-O3>
$<$<CONFIG:RELEASE>:-ffast-math>
$<$<CONFIG:RELEASE>:-fPIE>
$<$<CONFIG:RELWITHDEBINFO>:-O3>
$<$<CONFIG:RELWITHDEBINFO>:-fPIE>
$<$<CONFIG:RELWITHDEBINFO>:-g>
)
option(BUILD_32BIT "Force building as 32-bit binary" OFF)
option(BUILD_STATIC "Build statically linked executable" OFF)
option(BUILD_ARM_V5 "Build ARMv5" OFF)
option(BUILD_ARM_V6 "Build ARMv6" OFF)
if(BUILD_ARM_V5 AND BUILD_ARM_V6)
message(FATAL_ERROR "BUILD_ARM_V5 and BUILD_ARM_V6 are mutually exclusive!")
endif(BUILD_ARM_V5 AND BUILD_ARM_V6)
if(BUILD_32BIT)
set(CMAKE_SYSTEM_PROCESSOR "x86" CACHE STRING "system processor")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32" CACHE STRING "c++ flags")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32" CACHE STRING "c flags")
set(GOARCH "GOARCH=386" CACHE STRING "go architecture")
add_compile_options(
-m32
)
endif(BUILD_32BIT)
if(BUILD_STATIC)
add_link_options(
-static
)
set(CMAKE_EXE_LINKER_FLAGS "-static ${CMAKE_EXE_LINKER_FLAGS}")
set(GOFLAGS
${GOFLAGS}
-a
-tags osusergo,netgo
-ldflags '-w -extldflags \"-static -Wl,-unresolved-symbols=ignore-all\"'
)
else(BUILD_STATIC)
set(GOFLAGS
${GOFLAGS}
-a
-ldflags '-w -extldflags \"-Wl,-unresolved-symbols=ignore-all\"'
)
endif(BUILD_STATIC)
if(BUILD_ARM_V5)
set(GOARM "GOARM=5")
endif(BUILD_ARM_V5)
if(BUILD_ARM_V6)
set(GOARM "GOARM=6")
endif(BUILD_ARM_V6)
endif(APPLE)
endif(WIN32)
if (
CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64" OR
CMAKE_SYSTEM_PROCESSOR MATCHES "amd64" OR
CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64" OR
CMAKE_SYSTEM_PROCESSOR MATCHES "X86_64" OR
CMAKE_SYSTEM_PROCESSOR MATCHES "x64" OR
CMAKE_SYSTEM_PROCESSOR MATCHES "X64"
)
message("++ Adding flags for processor ${CMAKE_SYSTEM_PROCESSOR}")
add_compile_options(-maes -mrdrnd -mpclmul -msse -msse2)
endif()
if (
CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" OR
CMAKE_SYSTEM_PROCESSOR MATCHES "arm64" OR
CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64" OR
CMAKE_SYSTEM_PROCESSOR MATCHES "AARCH64"
)
message("++ Adding flags for processor ${CMAKE_SYSTEM_PROCESSOR}")
add_compile_options(-march=armv8-a+aes+crypto -mtune=generic -mstrict-align)
endif()
set(GO_BUILD_TAGS)
if(BUILD_CENTRAL_CONTROLLER)
add_definitions(-DZT_CONTROLLER_USE_LIBPQ=1)
set(GO_BUILD_TAGS -tags central)
endif(BUILD_CENTRAL_CONTROLLER)
add_subdirectory(core)
add_subdirectory(controller)
add_subdirectory(osdep)
add_subdirectory(serviceiocore)
file(GLOB go_src
${CMAKE_SOURCE_DIR}/cmd/*.go
${CMAKE_SOURCE_DIR}/cmd/cmd/*.go
${CMAKE_SOURCE_DIR}/pkg/zerotier/*.go
)
file(GLOB go_zt_service_tests_cmd_src
${CMAKE_SOURCE_DIR}/cmd/zt_service_tests/*.go
)
if(WIN32)
set(GO_EXE_NAME "zerotier.exe")
set(GO_SERVICE_TESTS_EXE_NAME "zt_service_tests.exe")
set(GO_EXTRA_LIBRARIES "-lstdc++ -lwsock32 -lws2_32 -liphlpapi -lole32 -loleaut32 -lrpcrt4 -luuid")
else(WIN32)
set(GO_EXE_NAME "zerotier")
set(GO_SERVICE_TESTS_EXE_NAME "zt_service_tests")
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
set(GO_EXTRA_LIBRARIES "-lstdc++")
if(BUILD_ARM_V5)
set(GO_EXTRA_LIBRARIES
${GO_EXTRA_LIBRARIES}
"-latomic"
)
endif(BUILD_ARM_V5)
else()
set(GO_EXTRA_LIBRARIES "-lc++" "-lm")
endif()
endif(WIN32)
add_custom_target(
zt_service_tests ALL
BYPRODUCTS ${CMAKE_BINARY_DIR}/zt_service_tests
SOURCES ${go_src} ${go_zt_service_tests_cmd_src}
COMMAND ${CMAKE_COMMAND} -E env ${GOARCH} ${GOARM} CGO_ENABLED=1 CGO_CFLAGS=\"-O3\" CGO_LDFLAGS=\"$<TARGET_FILE:zt_core> $<TARGET_FILE:zt_controller> $<TARGET_FILE:zt_service_io_core> $<TARGET_FILE:zt_osdep> ${GO_EXTRA_LIBRARIES}\" ${GO} build -mod=vendor ${GOFLAGS} -o ${CMAKE_BINARY_DIR}/${GO_SERVICE_TESTS_EXE_NAME} ${go_zt_service_tests_cmd_src}
COMMENT "Compiling zt_service_tests (Go/cgo self-tests)..."
)
add_dependencies(zt_service_tests zt_osdep zt_core zt_controller zt_service_io_core)
add_custom_target(
zerotier ALL
BYPRODUCTS ${CMAKE_BINARY_DIR}/zerotier
SOURCES ${go_src}
COMMAND ${CMAKE_COMMAND} -E env ${GOARCH} ${GOARM} CGO_ENABLED=1 CGO_CFLAGS=\"-O3\" CGO_LDFLAGS=\"$<TARGET_FILE:zt_core> $<TARGET_FILE:zt_controller> $<TARGET_FILE:zt_service_io_core> $<TARGET_FILE:zt_osdep> ${GO_EXTRA_LIBRARIES}\" ${GO} build -mod=vendor ${GOFLAGS} -o ${CMAKE_BINARY_DIR}/${GO_EXE_NAME} ${CMAKE_SOURCE_DIR}/cmd/zerotier/zerotier.go
COMMENT "Compiling Go Code..."
)
add_dependencies(zerotier zt_osdep zt_core zt_controller zt_service_io_core)
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/zerotier DESTINATION bin)
else(NOT PACKAGE_STATIC)
if(BUILD_32BIT)
set(CMAKE_SYSTEM_PROCESSOR "x86" CACHE STRING "system processor")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32" CACHE STRING "c++ flags")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32" CACHE STRING "c flags")
add_compile_options(
-m32
)
endif(BUILD_32BIT)
set(STATIC_BINARY ${CMAKE_BINARY_DIR}/zerotier)
set(IMPORTED_LOCATION ${CMAKE_BINARY_DIR})
add_executable(zerotier IMPORTED GLOBAL)
install(PROGRAMS ${STATIC_BINARY} DESTINATION bin)
endif(NOT PACKAGE_STATIC)
# Linux packaging
if("${CMAKE_SYSTEM_NAME}" EQUAL "Linux")
if(IS_DIRECTORY /lib/systemd/system)
install(
FILES ${CMAKE_CURRENT_SOURCE_DIR}/packaging/debian/zerotier.service
DESTINATION /lib/systemd/system
)
elseif(IS_DIRECTORY /usr/lib/systemd/system)
install(
FILES ${CMAKE_CURRENT_SOURCE_DIR}/packaging/debian/zerotier.service
DESTINATION /usr/lib/systemd/system
)
else()
install(
FILES ${CMAKE_CURRENT_SOURCE_DIR}/packaging/debian/zerotier.init
DESTINATION /etc/init.d
)
endif()
endif()
if("${ZT_PACKAGE_FORMAT}" MATCHES "DEB")
include(packaging/debian.cmake)
elseif("${ZT_PACKAGE_FORMAT}" MATCHES "RPM")
include(packaging/rpm.cmake)
else()
endif()

View file

@ -1,6 +0,0 @@
echo off
mkdir build
cd build
cmake .. -G "MinGW Makefiles"
make -j4
cd ..

View file

@ -1,190 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
import (
"encoding/json"
"fmt"
"io/ioutil"
"zerotier/pkg/zerotier"
)
func interactiveMakeSubject() *zerotier.CertificateSubject {
s := new(zerotier.CertificateSubject)
return s
}
func Cert(basePath string, authTokenGenerator func() string, args []string, jsonOutput bool) int {
if len(args) < 1 {
Help()
return 1
}
switch args[0] {
case "list":
case "show":
if len(args) != 1 {
Help()
return 1
}
case "newsid":
if len(args) > 2 {
Help()
return 1
}
uniqueId, uniqueIdPrivate, err := zerotier.NewCertificateSubjectUniqueId(zerotier.CertificateUniqueIdTypeNistP384)
if err != nil {
pErr("unable to create unique ID and private key: %s", err.Error())
return 1
}
sec, err := json.MarshalIndent(&zerotier.CertificateSubjectUniqueIDSecret{
UniqueID: uniqueId,
UniqueIDSecret: uniqueIdPrivate,
}, "", " ")
if err != nil {
pErr("unable to create unique ID and private key: %s", err.Error())
return 1
}
if len(args) == 1 {
fmt.Println(string(sec))
} else {
_ = ioutil.WriteFile(args[1], sec, 0600)
pResult("%s", args[1])
}
case "newcsr":
if len(args) != 4 {
Help()
return 1
}
var subject zerotier.CertificateSubject
err := readJSONFile(args[1], &subject)
if err != nil {
pErr("unable to read subject from %s: %s", args[1], err.Error())
return 1
}
var uniqueIdSecret zerotier.CertificateSubjectUniqueIDSecret
err = readJSONFile(args[2], &uniqueIdSecret)
if err != nil {
pErr("unable to read unique ID secret from %s: %s", args[2], err.Error())
return 1
}
csr, err := zerotier.NewCertificateCSR(&subject, uniqueIdSecret.UniqueID, uniqueIdSecret.UniqueIDSecret)
if err != nil {
pErr("problem creating CSR: %s", err.Error())
return 1
}
err = ioutil.WriteFile(args[3], csr, 0644)
if err == nil {
pResult("%s", args[3])
} else {
pErr("unable to write CSR to %s: %s", args[3], err.Error())
return 1
}
case "sign":
if len(args) != 4 {
Help()
return 1
}
csrBytes, err := ioutil.ReadFile(args[1])
if err != nil {
pErr("unable to read CSR from %s: %s", args[1], err.Error())
return 1
}
csr, err := zerotier.NewCertificateFromBytes(csrBytes, false)
if err != nil {
pErr("CSR in %s is invalid: %s", args[1], err.Error())
return 1
}
signingIdentity := cliGetIdentityOrFatal(args[2])
if signingIdentity == nil {
pErr("unable to read identity from %s", args[2])
return 1
}
if !signingIdentity.HasPrivate() {
pErr("signing identity in %s lacks private key", args[2])
return 1
}
cert, err := csr.Sign(signingIdentity)
if err != nil {
pErr("error signing CSR or generating certificate: %s", err.Error())
return 1
}
cb, err := cert.Marshal()
if err != nil {
pErr("error marshaling signed certificate: %s", err.Error())
return 1
}
err = ioutil.WriteFile(args[3], cb, 0644)
if err == nil {
pResult("%s", args[3])
} else {
pErr("unable to write signed certificate to %s: %s", args[3], err.Error())
return 1
}
case "verify", "dump":
if len(args) != 2 {
Help()
return 1
}
certBytes, err := ioutil.ReadFile(args[1])
if err != nil {
pErr("unable to read certificate from %s: %s", args[1], err.Error())
return 1
}
cert, err := zerotier.NewCertificateFromBytes(certBytes, true)
if err != nil {
pErr("certificate in %s invalid: %s", args[1], err.Error())
return 1
}
if args[0] == "dump" {
fmt.Println(cert.JSON())
} else {
fmt.Println("OK")
}
case "import":
case "restore":
case "export":
case "delete":
}
return 0
}

View file

@ -1,26 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
func Controller(basePath string, authTokenGenerator func() string, args []string, jsonOutput bool) int {
if len(args) < 1 {
Help()
return 1
}
switch args[0] {
}
return 0
}

View file

@ -1,128 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
import (
"fmt"
"zerotier/pkg/zerotier"
)
func Help() {
fmt.Printf(`ZeroTier Network Hypervisor Service Version %d.%d.%d
(c)2013-2021 ZeroTier, Inc.
Licensed under the ZeroTier BSL (see LICENSE.txt)
Usage: zerotier [-global options] <command> [command args]
Global Options:
-j Output raw JSON where applicable
-p <path> Use alternate base path
-t <path> Load secret auth token from a file
-T <token> Set secret auth token on command line
Common Operations:
help Show this help
version Print version
· status Show node status and configuration
· set [option] [value] List all settings (with no args)
· port <port> Primary P2P port
· secondaryport <port/0> Secondary P2P port (0 to disable)
· blacklist cidr <IP/bits> <boolean> Toggle physical path blacklisting
· blacklist if <prefix> <boolean> Toggle interface prefix blacklisting
· portmap <boolean> Toggle use of uPnP or NAT-PMP
· peer [address] [command] [option] Peer management commands
· list List peers
· listroots List root peers
· show Show peer details
· try <endpoint> [...] Try peer at explicit endpoint
· network list List VL2 networks
· network <network> [command] [option]
· show Show network details (default)
· set [option] [value] Get or set network options
· manageips <boolean> Is IP management allowed?
· manageroutes <boolean> Is route management allowed?
· managedns <boolean> Allow network to push DNS config
· globalips <boolean> Allow assignment of global IPs?
· globalroutes <boolean> Can global IP space routes be set?
· defaultroute <boolean> Can default route be overridden?
· join [-options] <network> Join a virtual network
-a <token> Token to submit to controller
-c <identity | fingerprint> Controller identity or fingerprint
· leave <network> Leave a virtual network
Advanced Operations:
service Start this node (runs until stopped)
now [duration] Print current time [-]#[ms|s|m|h]
controller <command> [option]
· list List networks on controller
· new Create a new network
· set <network> [setting] [value] Show or modify network settings
· members <network> List members of a network
· member <network> [setting] [value] Show or modify member level settings
· auth <address> Authorize a peer
· deauth <address> Deauthorize a peer
identity <command> [args]
new [c25519 | p384] Create identity (default: c25519)
getpublic <identity> Extract only public part of identity
fingerprint <identity> Get an identity's fingerprint
validate <identity> Locally validate an identity
sign <identity> <file> Sign a file with an identity's key
verify <identity> <file> <sig> Verify a signature
locator <command> [args]
new <identity> <endpoint> [...] Create new signed locator
verify <identity> <locator> Verify locator signature
show <locator> Show contents of a locator
cert <command> [args]
· list List certificates at local node
· show <serial> Show certificate details
newsid <secret out> Create a new subject unique ID
newcsr <subject|-> <secret> <csr out> Create a subject CSR
sign <csr> <identity> <cert out> Sign a CSR to create a certificate
verify <cert> Verify certificate (not chain)
dump <cert> Verify and print certificate
· import <cert> [trust,[trust]] Import certificate into this node
trust flag: rootca Certificate is a root CA
trust flag: ztrootset ZeroTier root node set
· restore Re-import default certificates
· export <serial> [path] Export a certificate from this node
· delete <serial|ALL> Delete certificate from this node
· Command requires a running node and access to a local API token.
An <address> may be specified as a 10-digit short ZeroTier address, a
fingerprint containing both an address and a SHA384 hash, or an identity.
The latter two options are equivalent in terms of specificity and may be
used if stronger security guarantees are desired than those provided by
the basic ZeroTier addressing system. Fields of type <identity> must be
full identities and may be specified either verbatim or as a path to a file.
An <endpoint> is a place where a peer may be reached. Currently these are
just 'IP/port' format addresses but other types may be added in the future.
The 'service' command starts a node. It will run until the node receives
an exit signal and is normally not used directly.
`,zerotier.CoreVersionMajor, zerotier.CoreVersionMinor, zerotier.CoreVersionRevision)
}

View file

@ -1,121 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
import (
"encoding/hex"
"fmt"
"io/ioutil"
"strings"
"zerotier/pkg/zerotier"
)
func Identity(args []string) int {
if len(args) > 0 {
switch args[0] {
case "new":
idType := zerotier.IdentityTypeC25519
if len(args) > 1 {
if len(args) > 2 {
Help()
return 1
}
switch args[1] {
case "c25519", "C25519", "0":
idType = zerotier.IdentityTypeC25519
case "p384", "P384", "1":
idType = zerotier.IdentityTypeP384
default:
Help()
return 1
}
}
id, err := zerotier.NewIdentity(idType)
if err != nil {
pErr("internal error generating identity: %s", err.Error())
return 1
}
fmt.Println(id.PrivateKeyString())
return 0
case "getpublic":
if len(args) == 2 {
fmt.Println(cliGetIdentityOrFatal(args[1]).String())
return 0
}
pErr("no identity specified")
return 1
case "fingerprint":
if len(args) == 2 {
fmt.Println(cliGetIdentityOrFatal(args[1]).Fingerprint().String())
return 0
}
pErr("no identity specified")
return 1
case "validate":
if len(args) == 2 {
if cliGetIdentityOrFatal(args[1]).LocallyValidate() {
fmt.Println("VALID")
return 0
}
fmt.Println("INVALID")
return 1
}
case "sign", "verify":
if len(args) > 2 {
id := cliGetIdentityOrFatal(args[1])
msg, err := ioutil.ReadFile(args[2])
if err != nil {
pErr("unable to read input file: %s", err.Error())
return 1
}
if args[0] == "verify" {
if len(args) == 4 {
sig, err := hex.DecodeString(strings.TrimSpace(args[3]))
if err != nil {
fmt.Println("FAILED")
return 1
}
if id.Verify(msg, sig) {
fmt.Println("OK")
return 0
}
}
fmt.Println("FAILED")
return 1
} else {
sig, err := id.Sign(msg)
if err != nil {
pErr("internal error signing message: %s", err.Error())
return 1
}
fmt.Println(hex.EncodeToString(sig))
return 0
}
}
}
}
Help()
return 1
}

View file

@ -1,89 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"zerotier/pkg/zerotier"
)
func Join(basePath string, authTokenGenerator func() string, args []string) int {
authToken := authTokenGenerator()
joinOpts := flag.NewFlagSet("join", flag.ContinueOnError)
controllerAuthToken := joinOpts.String("a", "", "")
controllerFingerprint := joinOpts.String("c", "", "")
err := joinOpts.Parse(os.Args[1:])
if err != nil {
Help()
return 1
}
args = joinOpts.Args()
if len(args) < 1 {
Help()
return 1
}
if !isValidNetworkID(args[0]) {
pErr("invalid network ID: %s", args[0])
return 1
}
nwid, err := strconv.ParseUint(args[0], 16, 64)
if err != nil {
pErr("ERROR: invalid network ID: %s", args[0])
return 1
}
nwids := fmt.Sprintf("%.16x", nwid)
_ = *controllerAuthToken // TODO: not implemented yet
var fp *zerotier.Fingerprint
if len(*controllerFingerprint) > 0 {
if strings.ContainsRune(*controllerFingerprint, '-') {
fp, err = zerotier.NewFingerprintFromString(*controllerFingerprint)
if err != nil {
pErr("invalid network controller fingerprint: %s", *controllerFingerprint)
return 1
}
} else {
id, err := zerotier.NewIdentityFromString(*controllerFingerprint)
if err != nil {
pErr("invalid network controller identity: %s", *controllerFingerprint)
return 1
}
fp = id.Fingerprint()
}
}
var network zerotier.APINetwork
network.ID = zerotier.NetworkID(nwid)
network.ControllerFingerprint = fp
if apiPost(basePath, authToken, "/network/"+nwids, &network, nil) <= 0 {
fmt.Println("FAILED")
} else {
if fp == nil {
fmt.Printf("OK %s\n", nwids)
} else {
fmt.Printf("OK %s %s\n", nwids, fp.String())
}
}
return 0
}

View file

@ -1,45 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
import (
"fmt"
"strconv"
"zerotier/pkg/zerotier"
)
func Leave(basePath string, authTokenGenerator func() string, args []string) int {
authToken := authTokenGenerator()
if len(args) != 1 {
Help()
return 1
}
if len(args[0]) != zerotier.NetworkIDStringLength {
fmt.Printf("ERROR: invalid network ID: %s\n", args[0])
return 1
}
nwid, err := strconv.ParseUint(args[0], 16, 64)
if err != nil {
fmt.Printf("ERROR: invalid network ID: %s\n", args[0])
return 1
}
nwids := fmt.Sprintf("%.16x", nwid)
apiDelete(basePath, authToken, "/network/"+nwids, nil)
fmt.Printf("OK %s", nwids)
return 0
}

View file

@ -1,82 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
import (
"fmt"
"zerotier/pkg/zerotier"
)
func Locator(args []string) int {
if len(args) < 1 {
Help()
return 1
}
switch args[0] {
case "new":
if len(args) < 3 {
Help()
return 1
}
id := cliGetIdentityOrFatal(args[1])
if !id.HasPrivate() {
pErr("identity must include secret key to sign locator")
return 1
}
var eps []*zerotier.Endpoint
for i:=2;i<len(args);i++ {
ep, err := zerotier.NewEndpointFromString(args[i])
if err != nil {
pErr("invalid endpoint: %s (%s)", args[i], err.Error())
return 1
}
eps = append(eps, ep)
}
loc, err := zerotier.NewLocator(zerotier.TimeMs(), eps, id)
if err != nil {
pErr("error creating or signing locator: %s", err.Error())
return 1
}
fmt.Println(loc.String())
case "verify":
if len(args) != 3 {
Help()
return 1
}
id := cliGetIdentityOrFatal(args[1])
loc := cliGetLocatorOrFatal(args[2])
if !loc.Validate(id) {
fmt.Println("FAILED")
return 1
}
fmt.Println("OK")
case "show":
if len(args) != 2 {
Help()
return 1
}
loc := cliGetLocatorOrFatal(args[1])
fmt.Printf("%s fingerprint %s\n",loc.Fingerprint.Address.String(),loc.Fingerprint.String())
for _, e := range loc.Endpoints {
fmt.Printf("\tendpoint %s type %s\n",e.String(),e.TypeString())
}
}
return 0
}

View file

@ -1,286 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"zerotier/pkg/zerotier"
)
func pErr(format string, args ...interface{}) {
_, _ = fmt.Fprintf(os.Stdout, "ERROR: "+format, args...)
fmt.Println()
}
func pResult(format string, args ...interface{}) {
_, _ = fmt.Printf(format, args...)
fmt.Println()
}
func apiGet(basePath, authToken, urlPath string, result interface{}) int64 {
statusCode, clock, err := zerotier.APIGet(basePath, zerotier.APISocketName, authToken, urlPath, result)
if err != nil {
fmt.Printf("FATAL: API response code %d: %s\n", statusCode, err.Error())
os.Exit(1)
return 0
}
if statusCode != http.StatusOK {
if statusCode == http.StatusUnauthorized {
fmt.Printf("FATAL: API response code %d: unauthorized (authorization token incorrect)\n", statusCode)
}
fmt.Printf("FATAL: API response code %d\n", statusCode)
os.Exit(1)
return 0
}
return clock
}
func apiPost(basePath, authToken, urlPath string, post, result interface{}) int64 {
statusCode, clock, err := zerotier.APIPost(basePath, zerotier.APISocketName, authToken, urlPath, post, result)
if err != nil {
fmt.Printf("FATAL: API response code %d: %s\n", statusCode, err.Error())
os.Exit(1)
return 0
}
if statusCode != http.StatusOK {
if statusCode == http.StatusUnauthorized {
fmt.Printf("FATAL: API response code %d: unauthorized (authorization token incorrect)\n", statusCode)
}
fmt.Printf("FATAL: API response code %d\n", statusCode)
os.Exit(1)
return 0
}
return clock
}
func apiDelete(basePath, authToken, urlPath string, result interface{}) int64 {
statusCode, clock, err := zerotier.APIDelete(basePath, zerotier.APISocketName, authToken, urlPath, result)
if err != nil {
fmt.Printf("FATAL: API response code %d: %s\n", statusCode, err.Error())
os.Exit(1)
return 0
}
if statusCode != http.StatusOK {
if statusCode == http.StatusUnauthorized {
fmt.Printf("FATAL: API response code %d: unauthorized (authorization token incorrect)\n", statusCode)
}
fmt.Printf("FATAL: API response code %d\n", statusCode)
os.Exit(1)
return 0
}
return clock
}
func enabledDisabled(f bool) string {
if f {
return "enabled"
}
return "disabled"
}
func allowedBlocked(f bool) string {
if f {
return "allowed"
}
return "blocked"
}
// isTrueStringPrefixChars matches things like [Tt]rue, [Yy]es, 1, [Ee]nabled, and [Aa]llowed
var isTrueStringPrefixChars = [9]uint8{'t', 'T', 'y', 'Y', '1', 'e', 'E', 'a', 'A'}
func isTrue(s string) bool {
if len(s) > 0 {
f := s[0]
for _, c := range isTrueStringPrefixChars {
if c == f {
return true
}
}
}
return false
}
func jsonDump(obj interface{}) string {
j, _ := json.MarshalIndent(obj, "", "\t")
return string(j)
}
// parseAddressFingerprintOrIdentity parses an argument as an address, fingerprint, or identity.
// If it's an address, only that return variable is filled out. Fingerprints fill out both address and
// fingerprint. Identity fills out all three.
func parseAddressFingerprintOrIdentity(s string) (a zerotier.Address, fp *zerotier.Fingerprint, id *zerotier.Identity) {
var err error
s = strings.TrimSpace(s)
hasColon := strings.ContainsRune(s, ':')
hasDash := strings.ContainsRune(s, '-')
if len(s) == zerotier.AddressStringLength && !hasColon && !hasDash {
a, err = zerotier.NewAddressFromString(s)
if err == nil {
return
}
}
if hasDash {
fp, err = zerotier.NewFingerprintFromString(s)
if err == nil {
a = fp.Address
return
}
}
if hasColon {
id, err = zerotier.NewIdentityFromString(s)
if err == nil {
a = id.Address()
fp = id.Fingerprint()
return
}
}
a = zerotier.Address(0)
return
}
func cliGetIdentityOrFatal(s string) *zerotier.Identity {
if strings.ContainsRune(s, ':') {
id, _ := zerotier.NewIdentityFromString(s)
if id != nil {
return id
}
}
idData, err := ioutil.ReadFile(s)
if err != nil {
pErr("identity '%s' cannot be parsed as file or literal: %s", s, err.Error())
os.Exit(1)
}
id, err := zerotier.NewIdentityFromString(string(idData))
if err != nil {
pErr("identity '%s' cannot be parsed as file or literal: %s", s, err.Error())
os.Exit(1)
}
return id
}
func cliGetLocatorOrFatal(s string) *zerotier.Locator {
if strings.ContainsRune(s, '@') {
loc, _ := zerotier.NewLocatorFromString(s)
if loc != nil {
return loc
}
}
locData, err := ioutil.ReadFile(s)
if err != nil {
pErr("locator '%s' cannot be parsed as file or literal: %s", s, err.Error())
os.Exit(1)
}
loc, err := zerotier.NewLocatorFromString(string(locData))
if err != nil {
pErr("locator '%s' cannot be parsed as file or literal: %s", s, err.Error())
os.Exit(1)
}
return loc
}
func networkStatusStr(status int) string {
switch status {
case zerotier.NetworkStatusNotFound:
return "not-found"
case zerotier.NetworkStatusAccessDenied:
return "access-denied"
case zerotier.NetworkStatusRequestingConfiguration:
return "updating"
case zerotier.NetworkStatusOK:
return "ok"
}
return "???"
}
func readJSONFile(p string, obj interface{}) error {
b, err := ioutil.ReadFile(p)
if err != nil {
return err
}
return json.Unmarshal(b, obj)
}
func isValidAddress(a string) bool {
if len(a) == zerotier.AddressStringLength {
for _, c := range a {
if !strings.ContainsRune("0123456789abcdefABCDEF", c) {
return false
}
}
return true
}
return false
}
func isValidNetworkID(a string) bool {
if len(a) == zerotier.NetworkIDStringLength {
for _, c := range a {
if !strings.ContainsRune("0123456789abcdefABCDEF", c) {
return false
}
}
return true
}
return false
}
/*
func prompt(str string, dfl string) string {
if len(dfl) > 0 {
fmt.Printf("%s [%s]: ", str, dfl)
text, _ := bufio.NewReader(os.Stdin).ReadString('\n')
text = strings.TrimSpace(text)
if len(text) == 0 {
text = dfl
}
return text
}
fmt.Print(str)
text, _ := bufio.NewReader(os.Stdin).ReadString('\n')
return strings.TrimSpace(text)
}
func promptInt(str string, dfl int64) int64 {
s := prompt(str, "")
if len(s) > 0 {
i, err := strconv.ParseInt(s, 10, 64)
if err == nil {
return i
}
}
return dfl
}
func promptFile(str string) []byte {
s := prompt(str, "")
if len(s) > 0 {
b, err := ioutil.ReadFile(s)
if err == nil {
return b
}
}
return nil
}
*/

View file

@ -1,188 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
import (
"fmt"
"strconv"
"strings"
"zerotier/pkg/zerotier"
)
func listNetworks(basePath, authToken string, jsonOutput bool) int {
var networks []zerotier.APINetwork
apiGet(basePath, authToken, "/network", &networks)
if jsonOutput {
fmt.Println(jsonDump(networks))
} else {
fmt.Printf("%-16s %-24s %-17s %-8s <type> <device> <managed IP(s)>\n", "<id>", "<name>", "<mac>", "<status>")
for _, nw := range networks {
t := "PRIVATE"
if nw.Config.Type == zerotier.NetworkTypePublic {
t = "PUBLIC"
}
fmt.Printf("%.16x %-24s %-17s %-16s %-7s %-16s ", uint64(nw.ID), nw.Config.Name, nw.Config.MAC.String(), networkStatusStr(nw.Config.Status), t, nw.PortName)
for i, ip := range nw.Config.AssignedAddresses {
if i > 0 {
fmt.Print(",")
}
fmt.Print(ip.String())
}
fmt.Print("\n")
}
}
return 0
}
func showNetwork(nwids string, network *zerotier.APINetwork, jsonOutput bool) {
if jsonOutput {
fmt.Println(jsonDump(&network))
} else {
fmt.Printf("%s\t%s\n", nwids, network.Config.Name)
fmt.Printf("\tstatus:\t%s\n", networkStatusStr(network.Config.Status))
enabled := "no"
if network.PortEnabled {
enabled = "yes"
}
bridge := "no"
if network.Config.Bridge {
bridge = "yes"
}
broadcast := "off"
if network.Config.BroadcastEnabled {
broadcast = "on"
}
fmt.Printf("\tport:\t%s dev %s type %s mtu %d enabled %s bridge %s broadcast %s\n", network.Config.MAC.String(), network.PortName, network.PortType, network.Config.MTU, enabled, bridge, broadcast)
fmt.Printf("\tmanaged addresses:\t")
for i, a := range network.Config.AssignedAddresses {
if i > 0 {
fmt.Print(" ")
}
fmt.Print(a.String())
}
fmt.Printf("\n\tmanaged routes:\t")
for i, r := range network.Config.Routes {
if i > 0 {
fmt.Print(" ")
}
fmt.Print(r.Target.String())
if r.Via == nil {
fmt.Print("->LAN")
} else {
fmt.Printf("->%s", r.Via.String())
}
}
managedIPs := "blocked"
if network.Settings.AllowManagedIPs {
managedIPs = "allowed"
}
managedIPsGlobal := "blocked"
if network.Settings.AllowGlobalIPs {
managedIPsGlobal = "allowed"
}
fmt.Printf("\n\tmanaged address local permissions:\t%s global %s\n", managedIPs, managedIPsGlobal)
managedRoutes := "blocked"
if network.Settings.AllowManagedRoutes {
managedRoutes = "allowed"
}
managedGlobalRoutes := "blocked"
if network.Settings.AllowGlobalRoutes {
managedGlobalRoutes = "allowed"
}
managedDefaultRoute := "blocked"
if network.Settings.AllowDefaultRouteOverride {
managedDefaultRoute = "allowed"
}
fmt.Printf("\tmanaged route local permissions:\t%s global %s default %s\n", managedRoutes, managedGlobalRoutes, managedDefaultRoute)
}
}
func Network(basePath string, authTokenGenerator func() string, args []string, jsonOutput bool) int {
if len(args) < 1 {
Help()
return 1
}
authToken := authTokenGenerator()
if len(args) == 1 && args[0] == "list" {
return listNetworks(basePath, authToken, jsonOutput)
}
if len(args[0]) != zerotier.NetworkIDStringLength {
pErr("ERROR: invalid network ID: %s", args[0])
return 1
}
nwid, err := strconv.ParseUint(args[0], 16, 64)
if err != nil {
pErr("ERROR: invalid network ID: %s", args[0])
return 1
}
nwids := fmt.Sprintf("%.16x", nwid)
var network zerotier.APINetwork
apiGet(basePath, authToken, "/network/"+nwids, &network)
if len(args) == 1 {
showNetwork(nwids, &network, jsonOutput)
} else {
switch args[1] {
case "show", "info":
showNetwork(nwids, &network, jsonOutput)
case "set":
if len(args) > 3 {
Help()
return 1
} else if len(args) > 2 {
fieldName := strings.ToLower(strings.TrimSpace(args[2]))
var field *bool
switch fieldName {
case "managedips":
field = &network.Settings.AllowManagedIPs
case "managedroutes":
field = &network.Settings.AllowGlobalRoutes
case "globalips":
field = &network.Settings.AllowGlobalIPs
case "globalroutes":
field = &network.Settings.AllowGlobalRoutes
case "defaultroute":
field = &network.Settings.AllowDefaultRouteOverride
default:
Help()
return 1
}
if len(args) == 3 {
*field = isTrue(args[2])
}
fmt.Printf("%s\t%t\n", fieldName, allowedBlocked(*field))
} else {
fmt.Printf("manageips\t%s\n", allowedBlocked(network.Settings.AllowManagedIPs))
fmt.Printf("manageroutes\t%s\n", allowedBlocked(network.Settings.AllowManagedRoutes))
fmt.Printf("globalips\t%s\n", allowedBlocked(network.Settings.AllowGlobalIPs))
fmt.Printf("globalroutes\t%s\n", allowedBlocked(network.Settings.AllowGlobalRoutes))
fmt.Printf("defaultroute\t%s\n", allowedBlocked(network.Settings.AllowDefaultRouteOverride))
}
}
}
return 0
}

View file

@ -1,100 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
import (
"fmt"
"strings"
"zerotier/pkg/zerotier"
)
func listPeers(basePath, authToken string, jsonOutput bool, rootsOnly bool) int {
var peers []zerotier.Peer
apiGet(basePath, authToken, "/peer", &peers)
if rootsOnly {
roots := make([]zerotier.Peer, 0, len(peers))
for i := range peers {
if peers[i].Root {
roots = append(roots, peers[i])
}
}
peers = roots
}
if jsonOutput {
fmt.Println(jsonDump(&peers))
} else {
fmt.Printf("<address> <ver> <root> <lat(ms)> <path(s)>\n")
for _, peer := range peers {
root := ""
if peer.Root {
root = " *"
}
var paths strings.Builder
if len(peer.Paths) > 0 {
if paths.Len() > 0 {
paths.WriteRune(' ')
}
paths.WriteString(peer.Paths[0].Endpoint.String())
} else {
paths.WriteString("(relayed)")
}
fmt.Printf("%.10x %-7s %-6s %-9d %s\n",
uint64(peer.Address),
fmt.Sprintf("%d.%d.%d", peer.Version[0], peer.Version[1], peer.Version[2]),
root,
peer.Latency,
paths.String())
}
}
return 0
}
func Peer(basePath string, authTokenGenerator func() string, args []string, jsonOutput bool) int {
if len(args) < 1 {
Help()
return 1
}
authToken := authTokenGenerator()
//var addr zerotier.Address
if isValidAddress(args[0]) {
//addr, _ = zerotier.NewAddressFromString(args[0])
args = args[1:]
if len(args) < 1 {
Help()
return 1
}
}
switch args[0] {
case "list":
return listPeers(basePath, authToken, jsonOutput, false)
case "listroots":
return listPeers(basePath, authToken, jsonOutput, true)
case "show":
case "try":
}
return 0
}

View file

@ -1,49 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
import (
"fmt"
"io/ioutil"
"os"
"os/signal"
"path"
"strconv"
"syscall"
"zerotier/pkg/zerotier"
)
func Service(basePath string, args []string) int {
if len(args) > 0 {
Help()
return 1
}
pidPath := path.Join(basePath, "zerotier.pid")
_ = ioutil.WriteFile(pidPath, []byte(strconv.FormatInt(int64(os.Getpid()), 10)), 0644)
node, err := zerotier.NewNode(basePath)
if err != nil {
fmt.Println("FATAL: error initializing node: " + err.Error())
} else {
osSignalChannel := make(chan os.Signal, 2)
signal.Notify(osSignalChannel, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT)
<-osSignalChannel
node.Close()
}
_ = os.Remove(pidPath)
return 0
}

View file

@ -1,18 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
func Set(basePath string, authTokenGenerator func() string, args []string) int {
return 0
}

View file

@ -1,72 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package cli
import (
"fmt"
"zerotier/pkg/zerotier"
)
func Status(basePath string, authTokenGenerator func() string, args []string, jsonOutput bool) int {
var status zerotier.APIStatus
apiGet(basePath, authTokenGenerator(), "/status", &status)
if jsonOutput {
fmt.Println(jsonDump(&status))
} else {
online := "ONLINE"
if !status.Online {
online = "OFFLINE"
}
fmt.Printf("%.10x: %s %s\n", uint64(status.Address), online, status.Version)
fmt.Printf("\tidentity:\t%s\n", status.Identity.String())
if status.Config.Settings.SecondaryPort > 0 && status.Config.Settings.SecondaryPort < 65536 {
fmt.Printf("\tports:\t%d %d\n", status.Config.Settings.PrimaryPort, status.Config.Settings.SecondaryPort)
} else {
fmt.Printf("\tports:\t%d\n", status.Config.Settings.PrimaryPort)
}
fmt.Printf("\tport mapping (uPnP/NAT-PMP):\t%s\n", enabledDisabled(status.Config.Settings.PortMapping))
fmt.Printf("\tblacklisted interface prefixes:\t")
for i, bl := range status.Config.Settings.InterfacePrefixBlacklist {
if i > 0 {
fmt.Print(" ")
}
fmt.Print(bl)
}
fmt.Printf("\n\texplicit external addresses: ")
for i, ea := range status.Config.Settings.ExplicitAddresses {
if i > 0 {
fmt.Print(" ")
}
fmt.Print(ea.String())
}
fmt.Printf("\n\tsystem interface addresses: ")
for i, a := range status.InterfaceAddresses {
if i > 0 {
fmt.Print(" ")
}
fmt.Print(a.String())
}
fmt.Printf("\n\tmapped external addresses: ")
for i, a := range status.MappedExternalAddresses {
if i > 0 {
fmt.Print(" ")
}
fmt.Print(a.String())
}
fmt.Printf("\n")
}
return 0
}

View file

@ -1,183 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-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 (
"flag"
"fmt"
"io/ioutil"
"os"
"path"
"runtime"
"runtime/debug"
"strings"
"time"
"zerotier/cmd/zerotier/cli"
"zerotier/pkg/zerotier"
)
// authToken returns a function that reads the authorization token if needed.
// If the authorization token can't be read, the function terminates the program with a fatal error.
func authToken(basePath, tflag, tTflag string) func () string {
savedAuthToken := new(string)
return func() string {
authToken := *savedAuthToken
if len(authToken) > 0 {
return authToken
}
if len(tflag) > 0 {
at, err := ioutil.ReadFile(tflag)
if err != nil || len(at) == 0 {
fmt.Println("FATAL: unable to read local service API authorization token from " + tflag)
os.Exit(1)
return ""
}
authToken = string(at)
} else if len(tTflag) > 0 {
authToken = tTflag
} else {
var authTokenPaths []string
authTokenPaths = append(authTokenPaths, path.Join(basePath, "authtoken.secret"))
userHome, _ := os.UserHomeDir()
if len(userHome) > 0 {
if runtime.GOOS == "darwin" {
authTokenPaths = append(authTokenPaths, path.Join(userHome, "Library", "Application Support", "ZeroTier", "authtoken.secret"))
authTokenPaths = append(authTokenPaths, path.Join(userHome, "Library", "Application Support", "ZeroTier", "One", "authtoken.secret"))
}
authTokenPaths = append(authTokenPaths, path.Join(userHome, ".zerotierauth"))
authTokenPaths = append(authTokenPaths, path.Join(userHome, ".zeroTierOneAuthToken"))
}
for _, p := range authTokenPaths {
tmp, _ := ioutil.ReadFile(p)
if len(tmp) > 0 {
authToken = string(tmp)
break
}
}
if len(authToken) == 0 {
fmt.Println("FATAL: unable to read local service API authorization token from any of:")
for _, p := range authTokenPaths {
fmt.Println(" " + p)
}
os.Exit(1)
return ""
}
}
authToken = strings.TrimSpace(authToken)
if len(authToken) == 0 {
fmt.Println("FATAL: unable to read API authorization token from command line or any filesystem location.")
os.Exit(1)
return ""
}
*savedAuthToken = authToken
return authToken
}
}
func main() {
// Reduce Go's thread and memory footprint. This would slow things down if the Go code
// were doing a lot, but it's not. It just manages the core and is not directly involved
// in pushing a lot of packets around. If that ever changes this should be adjusted.
runtime.GOMAXPROCS(1)
debug.SetGCPercent(10)
globalOpts := flag.NewFlagSet("global", flag.ContinueOnError)
hflag := globalOpts.Bool("h", false, "") // support -h to be canonical with other Unix utilities
jflag := globalOpts.Bool("j", false, "")
pflag := globalOpts.String("p", "", "")
tflag := globalOpts.String("t", "", "")
tTflag := globalOpts.String("T", "", "")
err := globalOpts.Parse(os.Args[1:])
if err != nil {
cli.Help()
os.Exit(1)
return
}
args := globalOpts.Args()
if len(args) < 1 || *hflag {
cli.Help()
os.Exit(0)
return
}
var cmdArgs []string
if len(args) > 1 {
cmdArgs = args[1:]
}
basePath := zerotier.PlatformDefaultHomePath
if len(*pflag) > 0 {
basePath = *pflag
}
exitCode := 0
switch args[0] {
default:
cli.Help()
exitCode = 1
case "help":
cli.Help()
case "version":
fmt.Printf("%d.%d.%d\n", zerotier.CoreVersionMajor, zerotier.CoreVersionMinor, zerotier.CoreVersionRevision)
case "now":
if len(args) > 2 {
cli.Help()
exitCode = 1
} else if len(args) == 2 {
d, err := time.ParseDuration(args[1])
if err == nil {
fmt.Printf("%d\n", zerotier.TimeMs() + d.Milliseconds())
} else {
fmt.Printf("FATAL: invalid duration \"%s\": %s\n", args[1], err.Error())
exitCode = 1
}
} else {
fmt.Printf("%d\n", zerotier.TimeMs())
}
case "service":
exitCode = cli.Service(basePath, cmdArgs)
case "status", "info":
exitCode = cli.Status(basePath, authToken(basePath, *tflag, *tTflag), cmdArgs, *jflag)
case "join":
exitCode = cli.Join(basePath, authToken(basePath, *tflag, *tTflag), cmdArgs)
case "leave":
exitCode = cli.Leave(basePath, authToken(basePath, *tflag, *tTflag), cmdArgs)
case "networks", "listnetworks":
exitCode = cli.Network(basePath, authToken(basePath, *tflag, *tTflag), []string{"list"}, *jflag)
case "network":
exitCode = cli.Network(basePath, authToken(basePath, *tflag, *tTflag), cmdArgs, *jflag)
case "peers", "listpeers":
exitCode = cli.Peer(basePath, authToken(basePath, *tflag, *tTflag), []string{"list"}, *jflag)
case "peer":
exitCode = cli.Peer(basePath, authToken(basePath, *tflag, *tTflag), cmdArgs, *jflag)
case "controller":
exitCode = cli.Controller(basePath, authToken(basePath, *tflag, *tTflag), cmdArgs, *jflag)
case "set":
exitCode = cli.Set(basePath, authToken(basePath, *tflag, *tTflag), cmdArgs)
case "identity":
exitCode = cli.Identity(cmdArgs)
case "locator":
exitCode = cli.Locator(cmdArgs)
case "certs", "listcerts", "lscerts": // same as "cert show" with no specific serial to show
exitCode = cli.Cert(basePath, authToken(basePath, *tflag, *tTflag), []string{"show"}, *jflag)
case "cert":
exitCode = cli.Cert(basePath, authToken(basePath, *tflag, *tTflag), cmdArgs, *jflag)
}
os.Exit(exitCode)
}

View file

@ -1,142 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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 (
"bytes"
"fmt"
"zerotier/pkg/zerotier"
)
func TestCertificate() bool {
id, err := zerotier.NewIdentityFromString("8e4df28b72:0:ac3d46abe0c21f3cfe7a6c8d6a85cfcffcb82fbd55af6a4d6350657c68200843fa2e16f9418bbd9702cae365f2af5fb4c420908b803a681d4daef6114d78a2d7:bd8dd6e4ce7022d2f812797a80c6ee8ad180dc4ebf301dec8b06d1be08832bddd63a2f1cfa7b2c504474c75bdc8898ba476ef92e8e2d0509f8441985171ff16e")
if err != nil {
fmt.Printf("FATAL: error deserializing test identity: %s\n", err.Error())
return false
}
uniqueId, uniqueIdPrivate, err := zerotier.NewCertificateSubjectUniqueId(zerotier.CertificateUniqueIdTypeNistP384)
if err != nil {
fmt.Printf("FATAL: error generating unique ID: %s", err.Error())
return false
}
var c zerotier.Certificate
c.SerialNo = make([]byte, 48)
for i := 0; i < 48; i++ {
c.SerialNo[i] = byte(i)
}
c.Flags = 1234
c.Timestamp = 5678
c.Validity[0] = 1010
c.Validity[1] = 2020
c.Subject.Timestamp = 31337
c.Subject.Identities = append(c.Subject.Identities, zerotier.CertificateIdentity{
Identity: id,
Locator: nil,
})
c.Subject.Networks = append(c.Subject.Networks, zerotier.CertificateNetwork{
ID: 1111,
Controller: zerotier.Fingerprint{
Address: zerotier.Address(2222),
Hash: c.SerialNo,
},
})
c.Subject.Certificates = append(c.Subject.Certificates, c.SerialNo)
c.Subject.UpdateURLs = append(c.Subject.UpdateURLs, "https://www.zerotier.com/asdfasdf")
c.Subject.Name.SerialNo = "a"
c.Subject.Name.CommonName = "b"
c.Subject.Name.StreetAddress = "c"
c.Subject.Name.Locality = "d"
c.Subject.Name.Province = "e"
c.Subject.Name.PostalCode = "f"
c.Subject.Name.Country = "g"
c.Subject.Name.Organization = "h"
c.Subject.Name.Unit = "i"
c.Subject.Name.Email = "j"
c.Subject.Name.URL = "k"
c.Subject.Name.Host = "l"
c.Subject.UniqueID = uniqueId
c.Issuer = id
c.IssuerName.SerialNo = "m"
c.IssuerName.CommonName = "n"
c.IssuerName.StreetAddress = "o"
c.IssuerName.Locality = "p"
c.IssuerName.Province = "q"
c.IssuerName.PostalCode = "r"
c.IssuerName.Country = "s"
c.IssuerName.Organization = "t"
c.IssuerName.Unit = "u"
c.IssuerName.Email = "v"
c.IssuerName.URL = "w"
c.IssuerName.Host = "x"
c.ExtendedAttributes = c.SerialNo
c.MaxPathLength = 9999
c.Signature = []byte("qwerty")
fmt.Printf("Checking certificate marshal/unmarshal (10000 tests)... ")
for k := 0; k < 10000; k++ {
cb, err := c.Marshal()
if err != nil {
fmt.Printf("marshal FAILED (%s)\n", err.Error())
return false
}
c2, err := zerotier.NewCertificateFromBytes(cb, false)
if err != nil {
fmt.Printf("unmarshal FAILED (%s)\n", err.Error())
return false
}
cb2, err := c2.Marshal()
if err != nil {
fmt.Printf("second marshal FAILED (%s)\n", err.Error())
return false
}
if !bytes.Equal(cb, cb2) {
fmt.Printf("FAILED (results not equal)\n")
return false
}
}
fmt.Println("OK")
fmt.Printf("Checking certificate CSR sign/verify (100 tests)... ")
for k := 0; k < 100; k++ {
csr, err := zerotier.NewCertificateCSR(&c.Subject, uniqueId, uniqueIdPrivate)
if err != nil {
fmt.Printf("CSR generate FAILED (%s)\n", err.Error())
return false
}
//fmt.Printf("CSR size: %d ", len(csr))
csr2, err := zerotier.NewCertificateFromBytes(csr, false)
if err != nil {
fmt.Printf("CSR decode FAILED (%s)\n", err.Error())
return false
}
signedCert, err := csr2.Sign(id)
if err != nil {
fmt.Printf("CSR sign FAILED (%s)\n", err.Error())
return false
}
if len(signedCert.Signature) == 0 {
fmt.Println("CSR sign FAILED (no signature found)", err.Error())
return false
}
}
fmt.Println("OK")
return true
}

View file

@ -1,84 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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 (
"bytes"
"fmt"
"zerotier/pkg/zerotier"
)
func TestLocator() bool {
fmt.Printf("Creating Endpoint instances... ")
ep0, err := zerotier.NewEndpointFromString("1.1.1.1/1")
if err != nil {
fmt.Printf("IPv4 FAILED (%s)\n",err.Error())
return false
}
ep1, err := zerotier.NewEndpointFromString("2600:1901:0:4006::1234/2")
if err != nil {
fmt.Printf("IPv6 FAILED (%s)\n",err.Error())
return false
}
eps := []*zerotier.Endpoint{ep0, ep1}
fmt.Printf("OK\n")
fmt.Printf("Creating signing Identity... ")
signer, err := zerotier.NewIdentity(zerotier.IdentityTypeP384)
if err != nil {
fmt.Printf("FAILED (%s)\n", err.Error())
return false
}
fmt.Printf("OK %s\n",signer.String())
fmt.Printf("Creating Locator instance... ")
loc, err := zerotier.NewLocator(zerotier.TimeMs(), eps, signer)
if err != nil {
fmt.Printf("FAILED (%s)\n",err.Error())
return false
}
locStr := loc.String()
locBytes := loc.Bytes()
fmt.Printf("OK (%d bytes)\n",len(locBytes))
fmt.Printf("Testing Locator Validate()... ")
if !loc.Validate(signer) {
fmt.Printf("FAILED (should have validated)\n")
return false
}
fmt.Printf("OK\n")
fmt.Printf("Testing Locator marshal/unmarshal... ")
loc2, err := zerotier.NewLocatorFromString(locStr)
if err != nil {
fmt.Printf("FAILED (%s)\n",err.Error())
return false
}
if !bytes.Equal(locBytes, loc2.Bytes()) {
fmt.Printf("FAILED (not equal)\n")
return false
}
loc2, err = zerotier.NewLocatorFromBytes(locBytes)
if err != nil {
fmt.Printf("FAILED (%s)\n",err.Error())
return false
}
if !bytes.Equal(locBytes, loc2.Bytes()) {
fmt.Printf("FAILED (not equal)\n")
return false
}
fmt.Printf("OK\n")
return true
}

View file

@ -1,23 +0,0 @@
package main
import (
"os"
"runtime"
"runtime/debug"
)
const numToRun = 10000
func main() {
runtime.GOMAXPROCS(1)
debug.SetGCPercent(10)
for k:=0;k<numToRun;k++ {
if !TestCertificate(){
os.Exit(1)
}
if !TestLocator(){
os.Exit(1)
}
}
}

View file

@ -1,9 +0,0 @@
module zerotier
go 1.13
require (
github.com/Microsoft/go-winio v0.4.14
github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1 // indirect
)

View file

@ -1,17 +0,0 @@
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=
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1 h1:/DtoiOYKoQCcIFXQjz07RnWNPRCbqmSXSpgEzhC9ZHM=
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View file

@ -1,76 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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"
"fmt"
"strconv"
)
// Address represents a 40-bit short ZeroTier address
type Address uint64
// NewAddressFromString parses a 10-digit ZeroTier address
func NewAddressFromString(s string) (Address, error) {
if len(s) != 10 {
return Address(0), ErrInvalidZeroTierAddress
}
a, err := strconv.ParseUint(s, 16, 64)
return Address(a & 0xffffffffff), err
}
// NewAddressFromBytes reads a 5-byte 40-bit address.
func NewAddressFromBytes(b []byte) (Address, error) {
if len(b) < 5 {
return Address(0), ErrInvalidZeroTierAddress
}
return Address((uint64(b[0]) << 32) | (uint64(b[1]) << 24) | (uint64(b[2]) << 16) | (uint64(b[3]) << 8) | uint64(b[4])), nil
}
// CopyTo writes this address to five bytes.
// If b cannot store five bytes this will panic.
func (a Address) CopyTo(b []byte) {
_ = b[4]
b[0] = byte(a >> 32)
b[1] = byte(a >> 24)
b[2] = byte(a >> 16)
b[3] = byte(a >> 8)
b[4] = byte(a)
}
// IsReserved returns true if this address is reserved and therefore is not valid for a real node.
func (a Address) IsReserved() bool { return a == 0 || (a>>32) == 0xff }
// String returns this address's 10-digit hex identifier.
func (a Address) String() string {
return fmt.Sprintf("%.10x", uint64(a))
}
// MarshalJSON marshals this Address as a string
func (a Address) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("\"%.10x\"", uint64(a))), nil
}
// UnmarshalJSON unmarshals this Address from a string
func (a *Address) UnmarshalJSON(j []byte) error {
var s string
err := json.Unmarshal(j, &s)
if err != nil {
return err
}
tmp, err := NewAddressFromString(s)
*a = tmp
return err
}

View file

@ -1,638 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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"
secrand "crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"path"
"runtime"
"strconv"
"strings"
"time"
"github.com/hectane/go-acl"
)
// APISocketName is the default socket name for accessing the API
const APISocketName = "apisocket"
// APIGet makes a query to the API via a Unix domain or windows pipe socket
func APIGet(basePath, socketName, authToken, queryPath string, obj interface{}) (int, int64, error) {
client, err := createNamedSocketHTTPClient(basePath, socketName)
if err != nil {
return http.StatusTeapot, 0, err
}
req, err := http.NewRequest("GET", "http://socket"+queryPath, nil)
if err != nil {
return http.StatusTeapot, 0, err
}
req.Header.Add("Authorization", "bearer "+authToken)
resp, err := client.Do(req)
if err != nil {
return http.StatusTeapot, 0, err
}
err = json.NewDecoder(resp.Body).Decode(obj)
ts := resp.Header.Get("X-ZT-Clock")
t := int64(0)
if len(ts) > 0 {
t, _ = strconv.ParseInt(ts, 10, 64)
}
return resp.StatusCode, t, err
}
// APIPost posts a JSON object to the API via a Unix domain or windows pipe socket and reads a response
func APIPost(basePath, socketName, authToken, queryPath string, post, result interface{}) (int, int64, error) {
client, err := createNamedSocketHTTPClient(basePath, socketName)
if err != nil {
return http.StatusTeapot, 0, err
}
var data []byte
if post != nil {
data, err = json.Marshal(post)
if err != nil {
return http.StatusTeapot, 0, err
}
} else {
data = []byte("null")
}
req, err := http.NewRequest("POST", "http://socket"+queryPath, bytes.NewReader(data))
if err != nil {
return http.StatusTeapot, 0, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", "bearer "+authToken)
resp, err := client.Do(req)
if err != nil {
return http.StatusTeapot, 0, err
}
ts := resp.Header.Get("X-ZT-Clock")
t := int64(0)
if len(ts) > 0 {
t, _ = strconv.ParseInt(ts, 10, 64)
}
if result != nil {
err = json.NewDecoder(resp.Body).Decode(result)
return resp.StatusCode, t, err
}
return resp.StatusCode, t, nil
}
// APIDelete posts DELETE to a path and fills result with the outcome (if any) if result is non-nil
func APIDelete(basePath, socketName, authToken, queryPath string, result interface{}) (int, int64, error) {
client, err := createNamedSocketHTTPClient(basePath, socketName)
if err != nil {
return http.StatusTeapot, 0, err
}
req, err := http.NewRequest("DELETE", "http://socket"+queryPath, nil)
if err != nil {
return http.StatusTeapot, 0, err
}
req.Header.Add("Authorization", "bearer "+authToken)
resp, err := client.Do(req)
if err != nil {
return http.StatusTeapot, 0, err
}
ts := resp.Header.Get("X-ZT-Clock")
t := int64(0)
if len(ts) > 0 {
t, _ = strconv.ParseInt(ts, 10, 64)
}
if result != nil {
err = json.NewDecoder(resp.Body).Decode(result)
return resp.StatusCode, t, err
}
return resp.StatusCode, t, nil
}
// APIStatus is the object returned by API status inquiries
type APIStatus struct {
Address Address `json:"address"`
Clock int64 `json:"clock"`
StartupTime int64 `json:"startupTime"`
Config *LocalConfig `json:"config"`
Online bool `json:"online"`
PeerCount int `json:"peerCount"`
PathCount int `json:"pathCount"`
Identity *Identity `json:"identity"`
InterfaceAddresses []net.IP `json:"localInterfaceAddresses,omitempty"`
MappedExternalAddresses []*InetAddress `json:"mappedExternalAddresses,omitempty"`
Version string `json:"version"`
VersionMajor int `json:"versionMajor"`
VersionMinor int `json:"versionMinor"`
VersionRevision int `json:"versionRevision"`
VersionBuild int `json:"versionBuild"`
OS string `json:"os"`
Architecture string `json:"architecture"`
Concurrency int `json:"concurrency"`
Runtime string `json:"runtimeVersion"`
}
// APINetwork is the object returned by API network inquiries
type APINetwork struct {
ID NetworkID `json:"id"`
Config NetworkConfig `json:"config"`
Settings *NetworkLocalSettings `json:"settings,omitempty"`
ControllerFingerprint *Fingerprint `json:"controllerFingerprint,omitempty"`
MulticastSubscriptions []*MulticastGroup `json:"multicastSubscriptions,omitempty"`
PortType string `json:"portType"`
PortName string `json:"portName"`
PortEnabled bool `json:"portEnabled"`
PortErrorCode int `json:"portErrorCode"`
PortError string `json:"portError"`
}
func apiNetworkFromNetwork(n *Network) *APINetwork {
var nn APINetwork
nn.ID = n.ID()
nn.Config = n.Config()
ls := n.LocalSettings()
nn.Settings = &ls
nn.MulticastSubscriptions = n.MulticastSubscriptions()
nn.PortType = n.Tap().Type()
nn.PortName = n.Tap().DeviceName()
nn.PortEnabled = n.Tap().Enabled()
ec, errStr := n.Tap().Error()
nn.PortErrorCode = ec
nn.PortError = errStr
return &nn
}
func apiSetStandardHeaders(out http.ResponseWriter) {
h := out.Header()
h.Set("Cache-Control", "no-cache, no-store, must-revalidate")
h.Set("Expires", "0")
h.Set("Pragma", "no-cache")
t := time.Now().UTC()
h.Set("Date", t.Format(time.RFC1123))
h.Set("X-ZT-Clock", strconv.FormatInt(t.UnixNano()/int64(1000000), 10))
}
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, &APIErr{"invalid JSON: " + err.Error()})
}
return
}
func apiCheckAuth(out http.ResponseWriter, req *http.Request, token string) bool {
ah := req.Header.Get("Authorization")
if len(ah) > 0 && strings.TrimSpace(ah) == ("bearer "+token) {
return true
}
ah = req.Header.Get("X-ZT1-Auth")
if len(ah) > 0 && strings.TrimSpace(ah) == token {
return true
}
_ = apiSendObj(out, req, http.StatusUnauthorized, &APIErr{"authorization token not found or incorrect (checked X-ZT1-Auth and Authorization headers)"})
return false
}
// createAPIServer creates and starts an HTTP server for a given node
func createAPIServer(basePath string, node *Node) (*http.Server, *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, nil, err
}
for i := 0; i < 20; i++ {
atb[i] = "abcdefghijklmnopqrstuvwxyz0123456789"[atb[i]%36]
}
err = ioutil.WriteFile(authTokenFile, atb[:], 0600)
if err != nil {
return nil, nil, err
}
_ = acl.Chmod(authTokenFile, 0600)
authToken = string(atb[:])
} else {
authToken = strings.TrimSpace(string(authTokenB))
}
smux := http.NewServeMux()
// -----------------------------------------------------------------------------------------------------------------
smux.HandleFunc("/status", func(out http.ResponseWriter, req *http.Request) {
defer func() {
e := recover()
if e != nil {
_ = apiSendObj(out, req, http.StatusInternalServerError, nil)
}
}()
if !apiCheckAuth(out, req, authToken) {
return
}
apiSetStandardHeaders(out)
if req.Method == http.MethodGet || req.Method == http.MethodHead {
pathCount := 0
peers := node.Peers()
for _, p := range peers {
pathCount += len(p.Paths)
}
_ = apiSendObj(out, req, http.StatusOK, &APIStatus{
Address: node.Address(),
Clock: TimeMs(),
StartupTime: node.startupTime,
Config: node.LocalConfig(),
Online: node.Online(),
PeerCount: len(peers),
PathCount: pathCount,
Identity: node.Identity(),
InterfaceAddresses: node.LocalInterfaceAddresses(),
MappedExternalAddresses: nil,
Version: fmt.Sprintf("%d.%d.%d", CoreVersionMajor, CoreVersionMinor, CoreVersionRevision),
VersionMajor: CoreVersionMajor,
VersionMinor: CoreVersionMinor,
VersionRevision: CoreVersionRevision,
VersionBuild: CoreVersionBuild,
OS: runtime.GOOS,
Architecture: runtime.GOARCH,
Concurrency: runtime.NumCPU(),
Runtime: runtime.Version(),
})
} else {
out.Header().Set("Allow", "GET, HEAD")
_ = apiSendObj(out, req, http.StatusMethodNotAllowed, &APIErr{"/status is read-only"})
}
})
// -----------------------------------------------------------------------------------------------------------------
smux.HandleFunc("/config", func(out http.ResponseWriter, req *http.Request) {
defer func() {
e := recover()
if e != nil {
_ = apiSendObj(out, req, http.StatusInternalServerError, &APIErr{"caught unexpected error in request handler"})
}
}()
if !apiCheckAuth(out, req, authToken) {
return
}
apiSetStandardHeaders(out)
if req.Method == http.MethodPost || req.Method == http.MethodPut {
var c LocalConfig
if apiReadObj(out, req, &c) == nil {
_, err := node.SetLocalConfig(&c)
if err != nil {
_ = apiSendObj(out, req, http.StatusBadRequest, &APIErr{"error applying local config: " + err.Error()})
} else {
lc := node.LocalConfig()
_ = apiSendObj(out, req, http.StatusOK, &lc)
}
}
} else if req.Method == http.MethodGet || req.Method == http.MethodHead {
_ = apiSendObj(out, req, http.StatusOK, node.LocalConfig())
} else {
out.Header().Set("Allow", "GET, HEAD, PUT, POST")
_ = apiSendObj(out, req, http.StatusMethodNotAllowed, &APIErr{"unsupported method: " + req.Method})
}
})
// -----------------------------------------------------------------------------------------------------------------
smux.HandleFunc("/peer/", func(out http.ResponseWriter, req *http.Request) {
var err error
defer func() {
e := recover()
if e != nil {
_ = apiSendObj(out, req, http.StatusInternalServerError, &APIErr{"caught unexpected error in request handler"})
}
}()
if !apiCheckAuth(out, req, authToken) {
return
}
apiSetStandardHeaders(out)
var queriedStr string
var queriedID Address
var queriedFP *Fingerprint
if len(req.URL.Path) > 6 {
queriedStr = req.URL.Path[6:]
if len(queriedStr) == AddressStringLength {
queriedID, err = NewAddressFromString(queriedStr)
if err != nil {
_ = apiSendObj(out, req, http.StatusNotFound, &APIErr{"peer not found"})
return
}
} else {
queriedFP, err = NewFingerprintFromString(queriedStr)
if err != nil {
_ = apiSendObj(out, req, http.StatusNotFound, &APIErr{"peer not found"})
return
}
}
}
var peer *Peer
peers := node.Peers()
if queriedFP != nil {
for _, p := range peers {
if p.Fingerprint.Equals(queriedFP) {
peer = p
break
}
}
} else if queriedID != 0 {
for _, p := range peers {
if p.Address == queriedID {
peer = p
break
}
}
}
if req.Method == http.MethodGet || req.Method == http.MethodHead || req.Method == http.MethodPost || req.Method == http.MethodPut {
if peer != nil {
_ = apiSendObj(out, req, http.StatusOK, peer)
} else if len(queriedStr) > 0 {
_ = apiSendObj(out, req, http.StatusNotFound, &APIErr{"peer not found"})
} else {
_ = apiSendObj(out, req, http.StatusOK, peers)
}
} else {
out.Header().Set("Allow", "GET, HEAD")
_ = apiSendObj(out, req, http.StatusMethodNotAllowed, &APIErr{"unsupported method"})
}
})
// -----------------------------------------------------------------------------------------------------------------
smux.HandleFunc("/network/", func(out http.ResponseWriter, req *http.Request) {
defer func() {
e := recover()
if e != nil {
_ = apiSendObj(out, req, http.StatusInternalServerError, &APIErr{"caught unexpected error in request handler"})
}
}()
if !apiCheckAuth(out, req, authToken) {
return
}
apiSetStandardHeaders(out)
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.MethodDelete {
if queriedID == 0 {
_ = apiSendObj(out, req, http.StatusBadRequest, &APIErr{"only specific networks can be deleted"})
return
}
networks := node.Networks()
for _, nw := range networks {
if nw.id == queriedID {
_ = node.Leave(queriedID)
_ = apiSendObj(out, req, http.StatusOK, apiNetworkFromNetwork(nw))
return
}
}
_ = apiSendObj(out, req, http.StatusNotFound, &APIErr{"network not found"})
} else if req.Method == http.MethodPost || req.Method == http.MethodPut {
if queriedID == 0 {
_ = apiSendObj(out, req, http.StatusBadRequest, nil)
return
}
var nw APINetwork
if apiReadObj(out, req, &nw) == nil {
n := node.Network(nw.ID)
if n == nil {
if nw.ControllerFingerprint != nil && nw.ControllerFingerprint.Address != nw.ID.Controller() {
_ = apiSendObj(out, req, http.StatusBadRequest, &APIErr{"fingerprint's address does not match what should be the controller's address"})
} else {
n, err := node.Join(nw.ID, nw.ControllerFingerprint, nw.Settings, nil)
if err != nil {
_ = apiSendObj(out, req, http.StatusBadRequest, &APIErr{"only individual networks can be added or modified with POST/PUT"})
} else {
_ = apiSendObj(out, req, http.StatusOK, apiNetworkFromNetwork(n))
}
}
} else {
if nw.Settings != nil {
n.SetLocalSettings(nw.Settings)
}
_ = apiSendObj(out, req, http.StatusOK, apiNetworkFromNetwork(n))
}
}
} else if req.Method == http.MethodGet || req.Method == http.MethodHead {
networks := node.Networks()
if queriedID == 0 { // no queried ID lists all networks
nws := make([]*APINetwork, 0, len(networks))
for _, nw := range networks {
nws = append(nws, apiNetworkFromNetwork(nw))
}
_ = apiSendObj(out, req, http.StatusOK, nws)
return
}
for _, nw := range networks {
if nw.ID() == queriedID {
_ = apiSendObj(out, req, http.StatusOK, apiNetworkFromNetwork(nw))
return
}
}
_ = apiSendObj(out, req, http.StatusNotFound, &APIErr{"network not found"})
} else {
out.Header().Set("Allow", "GET, HEAD, PUT, POST, DELETE")
_ = apiSendObj(out, req, http.StatusMethodNotAllowed, &APIErr{"unsupported method " + req.Method})
}
})
// -----------------------------------------------------------------------------------------------------------------
smux.HandleFunc("/cert/", func(out http.ResponseWriter, req *http.Request) {
defer func() {
e := recover()
if e != nil {
_ = apiSendObj(out, req, http.StatusInternalServerError, nil)
}
}()
if !apiCheckAuth(out, req, authToken) {
return
}
apiSetStandardHeaders(out)
var queriedSerialNo []byte
if len(req.URL.Path) > 6 {
b, err := base64.URLEncoding.DecodeString(req.URL.Path[6:])
if err != nil || len(b) != CertificateSerialNoSize {
_ = apiSendObj(out, req, http.StatusBadRequest, &APIErr{"invalid base64 serial number in certificate path"})
return
}
queriedSerialNo = b
}
if req.Method == http.MethodGet || req.Method == http.MethodHead {
certs, err := node.ListCertificates()
if err != nil {
_ = apiSendObj(out, req, http.StatusInternalServerError, &APIErr{"unexpected error listing certificates"})
return
}
if len(queriedSerialNo) == CertificateSerialNoSize {
for _, c := range certs {
if bytes.Equal(c.Certificate.SerialNo, queriedSerialNo) {
_ = apiSendObj(out, req, http.StatusOK, c)
break
}
}
} else {
_ = apiSendObj(out, req, http.StatusOK, certs)
}
} else if req.Method == http.MethodPost || req.Method == http.MethodPut {
var lc LocalCertificate
if apiReadObj(out, req, &lc) == nil {
if lc.Certificate == nil {
_ = apiSendObj(out, req, http.StatusBadRequest, &APIErr{"missing certificate"})
return
}
}
if len(queriedSerialNo) == CertificateSerialNoSize && !bytes.Equal(queriedSerialNo, lc.Certificate.SerialNo) {
_ = apiSendObj(out, req, http.StatusBadRequest, &APIErr{"certificate serial does not match serial in path"})
return
}
err := node.AddCertificate(lc.Certificate, lc.LocalTrust)
if err == nil {
_ = apiSendObj(out, req, http.StatusOK, lc)
} else {
_ = apiSendObj(out, req, http.StatusBadRequest, &APIErr{"certificate rejected: " + err.Error()})
}
} else if req.Method == http.MethodDelete {
if len(queriedSerialNo) == CertificateSerialNoSize {
certs, err := node.ListCertificates()
if err != nil {
_ = apiSendObj(out, req, http.StatusInternalServerError, &APIErr{"unexpected error"})
return
}
for _, c := range certs {
if bytes.Equal(c.Certificate.SerialNo, queriedSerialNo) {
_ = node.DeleteCertificate(queriedSerialNo)
_ = apiSendObj(out, req, http.StatusOK, c)
return
}
}
}
_ = apiSendObj(out, req, http.StatusNotFound, &APIErr{"certificate not found"})
} else {
out.Header().Set("Allow", "GET, HEAD, PUT, POST, DELETE")
_ = apiSendObj(out, req, http.StatusMethodNotAllowed, &APIErr{"unsupported method " + req.Method})
}
})
// -----------------------------------------------------------------------------------------------------------------
listener, err := createNamedSocketListener(basePath, APISocketName)
if err != nil {
return nil, 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 func() {
_ = httpServer.Serve(listener)
_ = listener.Close()
}()
var tcpHttpServer *http.Server
tcpBindAddr := node.LocalConfig().Settings.APITCPBindAddress
if tcpBindAddr != nil {
tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{
IP: tcpBindAddr.IP,
Port: tcpBindAddr.Port,
})
if err != nil {
node.infoLog.Printf("ERROR: unable to start API HTTP server at TCP bind address %s: %s (named socket listener startd, continuing anyway)", tcpBindAddr.String(), err.Error())
} else {
tcpHttpServer = &http.Server{
MaxHeaderBytes: 4096,
Handler: smux,
IdleTimeout: 10 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 600 * time.Second,
}
tcpHttpServer.SetKeepAlivesEnabled(true)
go func() {
_ = tcpHttpServer.Serve(tcpListener)
_ = tcpListener.Close()
}()
}
}
return httpServer, tcpHttpServer, nil
}

View file

@ -1,38 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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"
"strings"
)
// Base32Blob is a byte array that JSON serializes to a Base32 string.
type Base32Blob []byte
// MarshalJSON returns this blob marshaled as a byte array or a string.
func (b *Base32Blob) MarshalJSON() ([]byte, error) {
return []byte("\""+Base32.EncodeToString(*b)+"\""), nil
}
// UnmarshalJSON unmarshals this blob from a JSON array or string.
func (b *Base32Blob) UnmarshalJSON(j []byte) error {
var b32 string
err := json.Unmarshal(j, &b32)
if err != nil {
return err
}
*b, err = Base32.DecodeString(strings.TrimSpace(b32))
return err
}

View file

@ -1,550 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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
// #include "../../serviceiocore/GoGlue.h"
// static inline void *_ZT_Certificate_clone2(uintptr_t p) { return (void *)ZT_Certificate_clone((const ZT_Certificate *)p); }
import "C"
import (
"encoding/json"
"fmt"
"unsafe"
)
const (
CertificateSerialNoSize = 48
CertificateMaxStringLength = int(C.ZT_CERTIFICATE_MAX_STRING_LENGTH)
CertificateLocalTrustFlagRootCA = int(C.ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA)
CertificateLocalTrustFlagZeroTierRootSet = int(C.ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ZEROTIER_ROOT_SET)
CertificateUniqueIdTypeNistP384 = int(C.ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384)
CertificateUniqueIdTypeNistP384Size = int(C.ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE)
CertificateUniqueIdTypeNistP384PrivateSize = int(C.ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_PRIVATE_SIZE)
)
// CertificateName identifies a real-world entity that owns a subject or has signed a certificate.
type CertificateName struct {
SerialNo string `json:"serialNo,omitempty"`
CommonName string `json:"commonName,omitempty"`
StreetAddress string `json:"streetAddress,omitempty"`
Locality string `json:"locality,omitempty"`
Province string `json:"province,omitempty"`
PostalCode string `json:"postalCode,omitempty"`
Country string `json:"country,omitempty"`
Organization string `json:"organization,omitempty"`
Unit string `json:"unit,omitempty"`
Email string `json:"email,omitempty"`
URL string `json:"url,omitempty"`
Host string `json:"host,omitempty"`
}
// CertificateIdentity bundles an identity with an optional locator.
type CertificateIdentity struct {
Identity *Identity `json:"identity,omitempty"`
Locator *Locator `json:"locator,omitempty"`
}
// CertificateNetwork bundles a network ID with the fingerprint of its primary controller.
type CertificateNetwork struct {
ID uint64 `json:"id"`
Controller Fingerprint `json:"controller"`
}
// CertificateSubject contains information about the subject of a certificate.
type CertificateSubject struct {
Timestamp int64 `json:"timestamp"`
Identities []CertificateIdentity `json:"identities,omitempty"`
Networks []CertificateNetwork `json:"networks,omitempty"`
Certificates []Base32Blob `json:"certificates,omitempty"`
UpdateURLs []string `json:"updateURLs,omitempty"`
Name CertificateName `json:"name"`
UniqueID Base32Blob `json:"uniqueId,omitempty"`
UniqueIDProofSignature Base32Blob `json:"uniqueIdProofSignature,omitempty"`
}
// Certificate is a Go reflection of the C ZT_Certificate struct.
type Certificate struct {
SerialNo Base32Blob `json:"serialNo,omitempty"`
Flags uint64 `json:"flags"`
Timestamp int64 `json:"timestamp"`
Validity [2]int64 `json:"validity"`
Subject CertificateSubject `json:"subject"`
Issuer *Identity `json:"issuer,omitempty"`
IssuerName CertificateName `json:"issuerName"`
ExtendedAttributes Base32Blob `json:"extendedAttributes,omitempty"`
MaxPathLength uint `json:"maxPathLength,omitempty"`
CRL []Base32Blob `json:"crl,omitempty"`
Signature Base32Blob `json:"signature,omitempty"`
}
// CertificateSubjectUniqueIDSecret bundles a certificate subject unique ID and its secret key.
type CertificateSubjectUniqueIDSecret struct {
UniqueID Base32Blob `json:"uniqueId,omitempty"`
UniqueIDSecret Base32Blob `json:"uniqueIdSecret,omitempty"`
}
// LocalCertificate combines a certificate with its local trust flags.
type LocalCertificate struct {
Certificate *Certificate `json:"certificate,omitempty"`
LocalTrust uint `json:"localTrust"`
}
func certificateErrorToError(cerr int) error {
switch cerr {
case C.ZT_CERTIFICATE_ERROR_NONE:
return nil
case C.ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT:
return ErrCertificateHaveNewerCert
case C.ZT_CERTIFICATE_ERROR_INVALID_FORMAT:
return ErrCertificateInvalidFormat
case C.ZT_CERTIFICATE_ERROR_INVALID_IDENTITY:
return ErrCertificateInvalidIdentity
case C.ZT_CERTIFICATE_ERROR_INVALID_PRIMARY_SIGNATURE:
return ErrCertificateInvalidPrimarySignature
case C.ZT_CERTIFICATE_ERROR_INVALID_CHAIN:
return ErrCertificateInvalidChain
case C.ZT_CERTIFICATE_ERROR_INVALID_COMPONENT_SIGNATURE:
return ErrCertificateInvalidComponentSignature
case C.ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF:
return ErrCertificateInvalidUniqueIDProof
case C.ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS:
return ErrCertificateMissingRequiredFields
case C.ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW:
return ErrCertificateOutOfValidTimeWindow
}
return ErrInternal
}
// NewCertificateFromBytes decodes a certificate from an encoded byte string.
// Note that this is also used to decode a CSR. When used for a CSR only the
// Subject part of the certificate will contain anything and the rest will be
// blank. If 'verify' is true the certificate will also be verified. If using
// to decode a CSR this should be false as a CSR will not contain a full set
// of fields or a certificate signature.
func NewCertificateFromBytes(cert []byte, verify bool) (*Certificate, error) {
if len(cert) == 0 {
return nil, ErrInvalidParameter
}
var dec unsafe.Pointer
ver := C.int(0)
if verify {
ver = 1
}
cerr := C.ZT_Certificate_decode((**C.ZT_Certificate)(unsafe.Pointer(&dec)), unsafe.Pointer(&cert[0]), C.int(len(cert)), ver)
if cerr != 0 {
return nil, certificateErrorToError(int(cerr))
}
if dec == nil {
return nil, ErrInternal
}
defer C.ZT_Certificate_delete((*C.ZT_Certificate)(dec))
goCert := newCertificateFromCCertificate(dec)
if goCert == nil {
return nil, ErrInternal
}
return goCert, nil
}
// newCertificateFromCCertificate translates a C ZT_Certificate into a Go Certificate.
func newCertificateFromCCertificate(ccptr unsafe.Pointer) *Certificate {
cc := (*C.ZT_Certificate)(ccptr)
c := new(Certificate)
if cc == nil {
return c
}
sn := (*[48]byte)(unsafe.Pointer(&cc.serialNo[0]))[:]
if !allZero(sn) {
var tmp [48]byte
copy(tmp[:], sn)
c.SerialNo = tmp[:]
}
c.Flags = uint64(cc.flags)
c.Timestamp = int64(cc.timestamp)
c.Validity[0] = int64(cc.validity[0])
c.Validity[1] = int64(cc.validity[1])
c.Subject.Timestamp = int64(cc.subject.timestamp)
for i := 0; i < int(cc.subject.identityCount); i++ {
cid := (*C.ZT_Certificate_Identity)(unsafe.Pointer(uintptr(unsafe.Pointer(cc.subject.identities)) + (uintptr(C.sizeof_ZT_Certificate_Identity) * uintptr(i))))
if cid.identity == nil {
return nil
}
id, err := newIdentityFromCIdentity(cid.identity)
if err != nil {
return nil
}
var loc *Locator
if cid.locator != nil {
loc, err = newLocatorFromCLocator(cid.locator, false)
if err != nil {
return nil
}
}
c.Subject.Identities = append(c.Subject.Identities, CertificateIdentity{
Identity: id,
Locator: loc,
})
}
for i := 0; i < int(cc.subject.networkCount); i++ {
cn := (*C.ZT_Certificate_Network)(unsafe.Pointer(uintptr(unsafe.Pointer(cc.subject.networks)) + (uintptr(C.sizeof_ZT_Certificate_Network) * uintptr(i))))
fp := newFingerprintFromCFingerprint(&cn.controller)
if fp == nil {
return nil
}
c.Subject.Networks = append(c.Subject.Networks, CertificateNetwork{
ID: uint64(cn.id),
Controller: *fp,
})
}
for i := 0; i < int(cc.subject.certificateCount); i++ {
csn := *((**[CertificateSerialNoSize]byte)(unsafe.Pointer(uintptr(unsafe.Pointer(cc.subject.certificates)) + (uintptr(i) * pointerSize))))
var tmp [CertificateSerialNoSize]byte
copy(tmp[:], csn[:])
c.Subject.Certificates = append(c.Subject.Certificates, tmp[:])
}
for i := 0; i < int(cc.subject.updateURLCount); i++ {
curl := *((**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(cc.subject.updateURLs)) + (uintptr(i) * pointerSize))))
c.Subject.UpdateURLs = append(c.Subject.UpdateURLs, C.GoString(curl))
}
c.Subject.Name.SerialNo = C.GoString(&cc.subject.name.serialNo[0])
c.Subject.Name.CommonName = C.GoString(&cc.subject.name.commonName[0])
c.Subject.Name.Country = C.GoString(&cc.subject.name.country[0])
c.Subject.Name.Organization = C.GoString(&cc.subject.name.organization[0])
c.Subject.Name.Unit = C.GoString(&cc.subject.name.unit[0])
c.Subject.Name.Locality = C.GoString(&cc.subject.name.locality[0])
c.Subject.Name.Province = C.GoString(&cc.subject.name.province[0])
c.Subject.Name.StreetAddress = C.GoString(&cc.subject.name.streetAddress[0])
c.Subject.Name.PostalCode = C.GoString(&cc.subject.name.postalCode[0])
c.Subject.Name.Email = C.GoString(&cc.subject.name.email[0])
c.Subject.Name.URL = C.GoString(&cc.subject.name.url[0])
c.Subject.Name.Host = C.GoString(&cc.subject.name.host[0])
if cc.subject.uniqueIdSize > 0 {
c.Subject.UniqueID = C.GoBytes(unsafe.Pointer(cc.subject.uniqueId), C.int(cc.subject.uniqueIdSize))
if cc.subject.uniqueIdProofSignatureSize > 0 {
c.Subject.UniqueIDProofSignature = C.GoBytes(unsafe.Pointer(cc.subject.uniqueIdProofSignature), C.int(cc.subject.uniqueIdProofSignatureSize))
}
}
if cc.issuer != nil {
id, err := newIdentityFromCIdentity(cc.issuer)
if err != nil {
return nil
}
c.Issuer = id
}
c.IssuerName.SerialNo = C.GoString(&cc.issuerName.serialNo[0])
c.IssuerName.CommonName = C.GoString(&cc.issuerName.commonName[0])
c.IssuerName.Country = C.GoString(&cc.issuerName.country[0])
c.IssuerName.Organization = C.GoString(&cc.issuerName.organization[0])
c.IssuerName.Unit = C.GoString(&cc.issuerName.unit[0])
c.IssuerName.Locality = C.GoString(&cc.issuerName.locality[0])
c.IssuerName.Province = C.GoString(&cc.issuerName.province[0])
c.IssuerName.StreetAddress = C.GoString(&cc.issuerName.streetAddress[0])
c.IssuerName.PostalCode = C.GoString(&cc.issuerName.postalCode[0])
c.IssuerName.Email = C.GoString(&cc.issuerName.email[0])
c.IssuerName.URL = C.GoString(&cc.issuerName.url[0])
c.IssuerName.Host = C.GoString(&cc.issuerName.host[0])
if cc.extendedAttributesSize > 0 {
c.ExtendedAttributes = C.GoBytes(unsafe.Pointer(cc.extendedAttributes), C.int(cc.extendedAttributesSize))
}
c.MaxPathLength = uint(cc.maxPathLength)
for i := 0; i < int(cc.crlCount); i++ {
csn := *((**[CertificateSerialNoSize]byte)(unsafe.Pointer(uintptr(unsafe.Pointer(cc.crl)) + (uintptr(i) * pointerSize))))
var tmp [CertificateSerialNoSize]byte
copy(tmp[:], csn[:])
c.CRL = append(c.CRL, tmp[:])
}
if cc.signatureSize > 0 {
c.Signature = C.GoBytes(unsafe.Pointer(cc.signature), C.int(cc.signatureSize))
}
return c
}
// deleteCCertificate deletes a ZT_Certificate object returned by Certificate.CCertificate()
func deleteCCertificate(cc unsafe.Pointer) {
C.ZT_Certificate_delete((*C.ZT_Certificate)(cc))
}
// cCertificate creates a C ZT_Certificate structure from the content of a Certificate.
// It must be deleted with deleteCCertificate.
func (c *Certificate) cCertificate() unsafe.Pointer {
var cc C.ZT_Certificate
var subjectIdentities []C.ZT_Certificate_Identity
var subjectNetworks []C.ZT_Certificate_Network
var subjectCertificates []uintptr
var subjectUpdateURLs []uintptr
var subjectUpdateURLsData [][]byte
var crl []uintptr
if len(c.SerialNo) == 48 {
copy((*[CertificateSerialNoSize]byte)(unsafe.Pointer(&cc.serialNo[0]))[:], c.SerialNo)
}
cc.flags = C.uint64_t(c.Flags)
cc.timestamp = C.int64_t(c.Timestamp)
cc.validity[0] = C.int64_t(c.Validity[0])
cc.validity[1] = C.int64_t(c.Validity[1])
cc.subject.timestamp = C.int64_t(c.Subject.Timestamp)
if len(c.Subject.Identities) > 0 {
subjectIdentities = make([]C.ZT_Certificate_Identity, len(c.Subject.Identities))
for i, id := range c.Subject.Identities {
if id.Identity == nil {
return nil
}
subjectIdentities[i].identity = id.Identity.cIdentity()
if id.Locator != nil {
subjectIdentities[i].locator = id.Locator.cl
}
}
cc.subject.identities = &subjectIdentities[0]
cc.subject.identityCount = C.uint(len(subjectIdentities))
}
if len(c.Subject.Networks) > 0 {
subjectNetworks = make([]C.ZT_Certificate_Network, len(c.Subject.Networks))
for i, n := range c.Subject.Networks {
subjectNetworks[i].id = C.uint64_t(n.ID)
subjectNetworks[i].controller.address = C.uint64_t(n.Controller.Address)
if len(n.Controller.Hash) == 48 {
copy((*[48]byte)(unsafe.Pointer(&subjectNetworks[i].controller.hash[0]))[:], n.Controller.Hash)
}
}
cc.subject.networks = &subjectNetworks[0]
cc.subject.networkCount = C.uint(len(subjectNetworks))
}
if len(c.Subject.Certificates) > 0 {
subjectCertificates = make([]uintptr, len(c.Subject.Certificates))
for i, cert := range c.Subject.Certificates {
if len(cert) != CertificateSerialNoSize {
return nil
}
subjectCertificates[i] = uintptr(unsafe.Pointer(&cert[0]))
}
cc.subject.certificates = (**C.uint8_t)(unsafe.Pointer(&subjectCertificates[0]))
cc.subject.certificateCount = C.uint(len(subjectCertificates))
}
if len(c.Subject.UpdateURLs) > 0 {
subjectUpdateURLs = make([]uintptr, len(c.Subject.UpdateURLs))
subjectUpdateURLsData = make([][]byte, len(c.Subject.UpdateURLs))
for i, u := range c.Subject.UpdateURLs {
subjectUpdateURLsData[i] = stringAsZeroTerminatedBytes(u)
subjectUpdateURLs[i] = uintptr(unsafe.Pointer(&subjectUpdateURLsData[0][0]))
}
cc.subject.updateURLs = (**C.char)(unsafe.Pointer(&subjectUpdateURLs[0]))
cc.subject.updateURLCount = C.uint(len(subjectUpdateURLs))
}
cStrCopy(unsafe.Pointer(&cc.subject.name.serialNo[0]), CertificateMaxStringLength+1, c.Subject.Name.SerialNo)
cStrCopy(unsafe.Pointer(&cc.subject.name.commonName[0]), CertificateMaxStringLength+1, c.Subject.Name.CommonName)
cStrCopy(unsafe.Pointer(&cc.subject.name.country[0]), CertificateMaxStringLength+1, c.Subject.Name.Country)
cStrCopy(unsafe.Pointer(&cc.subject.name.organization[0]), CertificateMaxStringLength+1, c.Subject.Name.Organization)
cStrCopy(unsafe.Pointer(&cc.subject.name.unit[0]), CertificateMaxStringLength+1, c.Subject.Name.Unit)
cStrCopy(unsafe.Pointer(&cc.subject.name.locality[0]), CertificateMaxStringLength+1, c.Subject.Name.Locality)
cStrCopy(unsafe.Pointer(&cc.subject.name.province[0]), CertificateMaxStringLength+1, c.Subject.Name.Province)
cStrCopy(unsafe.Pointer(&cc.subject.name.streetAddress[0]), CertificateMaxStringLength+1, c.Subject.Name.StreetAddress)
cStrCopy(unsafe.Pointer(&cc.subject.name.postalCode[0]), CertificateMaxStringLength+1, c.Subject.Name.PostalCode)
cStrCopy(unsafe.Pointer(&cc.subject.name.email[0]), CertificateMaxStringLength+1, c.Subject.Name.Email)
cStrCopy(unsafe.Pointer(&cc.subject.name.url[0]), CertificateMaxStringLength+1, c.Subject.Name.URL)
cStrCopy(unsafe.Pointer(&cc.subject.name.host[0]), CertificateMaxStringLength+1, c.Subject.Name.Host)
if len(c.Subject.UniqueID) > 0 {
cc.subject.uniqueId = (*C.uint8_t)(unsafe.Pointer(&c.Subject.UniqueID[0]))
cc.subject.uniqueIdSize = C.uint(len(c.Subject.UniqueID))
if len(c.Subject.UniqueIDProofSignature) > 0 {
cc.subject.uniqueIdProofSignature = (*C.uint8_t)(unsafe.Pointer(&c.Subject.UniqueIDProofSignature[0]))
cc.subject.uniqueIdProofSignatureSize = C.uint(len(c.Subject.UniqueIDProofSignature))
}
}
if c.Issuer != nil {
cc.issuer = c.Issuer.cIdentity()
}
cStrCopy(unsafe.Pointer(&cc.issuerName.serialNo[0]), CertificateMaxStringLength+1, c.IssuerName.SerialNo)
cStrCopy(unsafe.Pointer(&cc.issuerName.commonName[0]), CertificateMaxStringLength+1, c.IssuerName.CommonName)
cStrCopy(unsafe.Pointer(&cc.issuerName.country[0]), CertificateMaxStringLength+1, c.IssuerName.Country)
cStrCopy(unsafe.Pointer(&cc.issuerName.organization[0]), CertificateMaxStringLength+1, c.IssuerName.Organization)
cStrCopy(unsafe.Pointer(&cc.issuerName.unit[0]), CertificateMaxStringLength+1, c.IssuerName.Unit)
cStrCopy(unsafe.Pointer(&cc.issuerName.locality[0]), CertificateMaxStringLength+1, c.IssuerName.Locality)
cStrCopy(unsafe.Pointer(&cc.issuerName.province[0]), CertificateMaxStringLength+1, c.IssuerName.Province)
cStrCopy(unsafe.Pointer(&cc.issuerName.streetAddress[0]), CertificateMaxStringLength+1, c.IssuerName.StreetAddress)
cStrCopy(unsafe.Pointer(&cc.issuerName.postalCode[0]), CertificateMaxStringLength+1, c.IssuerName.PostalCode)
cStrCopy(unsafe.Pointer(&cc.issuerName.email[0]), CertificateMaxStringLength+1, c.IssuerName.Email)
cStrCopy(unsafe.Pointer(&cc.issuerName.url[0]), CertificateMaxStringLength+1, c.IssuerName.URL)
cStrCopy(unsafe.Pointer(&cc.issuerName.host[0]), CertificateMaxStringLength+1, c.IssuerName.Host)
if len(c.ExtendedAttributes) > 0 {
cc.extendedAttributes = (*C.uint8_t)(unsafe.Pointer(&c.ExtendedAttributes[0]))
cc.extendedAttributesSize = C.uint(len(c.ExtendedAttributes))
}
cc.maxPathLength = C.uint(c.MaxPathLength)
if len(c.CRL) > 0 {
crl = make([]uintptr, len(c.CRL))
for i, cert := range c.CRL {
if len(cert) != CertificateSerialNoSize {
return nil
}
crl[i] = uintptr(unsafe.Pointer(&cert[0]))
}
cc.crl = (**C.uint8_t)(unsafe.Pointer(&crl[0]))
cc.crlCount = C.uint(len(crl))
}
if len(c.Signature) > 0 {
cc.signature = (*C.uint8_t)(unsafe.Pointer(&c.Signature[0]))
cc.signatureSize = C.uint(len(c.Signature))
}
// HACK: pass pointer to cc as uintptr to disable Go's protection against "Go pointers to
// Go pointers," as the C function called here will make a deep clone and then we are going
// to throw away 'cc' and its components.
return unsafe.Pointer(C._ZT_Certificate_clone2(C.uintptr_t(uintptr(unsafe.Pointer(&cc)))))
}
// Marshal encodes this certificate as a byte array (binary format).
func (c *Certificate) Marshal() ([]byte, error) {
cc := c.cCertificate()
if cc == nil {
return nil, ErrInternal
}
defer deleteCCertificate(cc)
var encoded [16384]byte
encodedSize := C.int(16384)
rv := int(C.ZT_Certificate_encode((*C.ZT_Certificate)(cc), unsafe.Pointer(&encoded[0]), &encodedSize))
if rv != 0 {
return nil, fmt.Errorf("Certificate encode error %d", rv)
}
return append(make([]byte, 0, int(encodedSize)), encoded[0:int(encodedSize)]...), nil
}
// Sign signs this certificate and returns a new one with signature and issuer filled out.
// This should only be used after decoding a CSR with NewCertificateFromBytes. The non-subject
// parts of this Certificate, if any, are ignored. A new Certificate is returned with a completed
// signature.
func (c *Certificate) Sign(id *Identity) (*Certificate, error) {
if id == nil || !id.HasPrivate() {
return nil, ErrInvalidParameter
}
ctmp := c.cCertificate()
if ctmp == nil {
return nil, ErrInternal
}
defer deleteCCertificate(ctmp)
var signedCert [16384]byte
signedCertSize := C.int(16384)
rv := int(C.ZT_Certificate_sign((*C.ZT_Certificate)(ctmp), id.cIdentity(), unsafe.Pointer(&signedCert[0]), &signedCertSize))
if rv != 0 {
return nil, fmt.Errorf("signing failed: error %d", rv)
}
return NewCertificateFromBytes(signedCert[0:int(signedCertSize)], true)
}
// Verify returns nil on success or a certificate error if there is a problem with this certificate.
func (c *Certificate) Verify() error {
cc := c.cCertificate()
if cc == nil {
return ErrInternal
}
defer deleteCCertificate(cc)
return certificateErrorToError(int(C.ZT_Certificate_verify((*C.ZT_Certificate)(cc))))
}
// String returns a compact JSON representation of this certificate.
func (c *Certificate) String() string {
j, _ := json.Marshal(c)
return string(j)
}
// JSON returns this certificate as a human-readable indented JSON string.
func (c *Certificate) JSON() string {
j, _ := json.MarshalIndent(c, "", " ")
return string(j)
}
// NewCertificateSubjectUniqueId creates a new certificate subject unique ID and corresponding private key.
// Right now only one type is supported: CertificateUniqueIdTypeNistP384
func NewCertificateSubjectUniqueId(uniqueIdType int) (id []byte, priv []byte, err error) {
if uniqueIdType != CertificateUniqueIdTypeNistP384 {
err = ErrInvalidParameter
return
}
id = make([]byte, int(C.ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE))
priv = make([]byte, int(C.ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_PRIVATE_SIZE))
idSize := C.int(len(id))
idPrivateSize := C.int(len(priv))
rv := int(C.ZT_Certificate_newSubjectUniqueId((C.enum_ZT_CertificateUniqueIdType)(uniqueIdType), unsafe.Pointer(&id[0]), &idSize, unsafe.Pointer(&priv[0]), &idPrivateSize))
if rv != 0 {
id = nil
priv = nil
err = fmt.Errorf("error %d", rv)
return
}
if int(idSize) != len(id) || int(idPrivateSize) != len(priv) {
id = nil
priv = nil
err = ErrInvalidParameter
return
}
return
}
// NewCertificateCSR creates a new certificate signing request (CSR) from a certificate subject and optional unique ID.
func NewCertificateCSR(subject *CertificateSubject, uniqueId []byte, uniqueIdPrivate []byte) ([]byte, error) {
var uid unsafe.Pointer
var uidp unsafe.Pointer
if len(uniqueId) > 0 && len(uniqueIdPrivate) > 0 {
uid = unsafe.Pointer(&uniqueId[0])
uidp = unsafe.Pointer(&uniqueIdPrivate[0])
}
var tmp Certificate
tmp.Subject = *subject
ctmp := tmp.cCertificate()
if ctmp == nil {
return nil, ErrInternal
}
defer deleteCCertificate(ctmp)
var csr [16384]byte
csrSize := C.int(16384)
cc := (*C.ZT_Certificate)(ctmp)
rv := int(C.ZT_Certificate_newCSR(&(cc.subject), uid, C.int(len(uniqueId)), uidp, C.int(len(uniqueIdPrivate)), unsafe.Pointer(&csr[0]), &csrSize))
if rv != 0 {
return nil, fmt.Errorf("ZT_Certificate_newCSR() failed: %d", rv)
}
return append(make([]byte, 0, int(csrSize)), csr[0:int(csrSize)]...), nil
}

View file

@ -1,178 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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
// #include "../../serviceiocore/GoGlue.h"
// const ZT_Fingerprint *_getFP(const ZT_Endpoint *ep) { return &(ep->value.fp); }
// uint64_t _getAddress(const ZT_Endpoint *ep) { return ep->value.fp.address; }
// uint64_t _getMAC(const ZT_Endpoint *ep) { return ep->value.mac; }
// const struct sockaddr_storage *_getSS(const ZT_Endpoint *ep) { return &(ep->value.ss); }
// void _setSS(ZT_Endpoint *ep,const void *ss) { memcpy(&(ep->value.ss),ss,sizeof(struct sockaddr_storage)); }
import "C"
import (
"encoding/json"
"unsafe"
)
const (
EndpointTypeNil = C.ZT_ENDPOINT_TYPE_NIL
EndpointTypeZeroTier = C.ZT_ENDPOINT_TYPE_ZEROTIER
EndpointTypeEthernet = C.ZT_ENDPOINT_TYPE_ETHERNET
EndpointTypeWifiDirect = C.ZT_ENDPOINT_TYPE_WIFI_DIRECT
EndpointTypeBluetooth = C.ZT_ENDPOINT_TYPE_BLUETOOTH
EndpointTypeIp = C.ZT_ENDPOINT_TYPE_IP
EndpointTypeIpUdp = C.ZT_ENDPOINT_TYPE_IP_UDP
EndpointTypeIpTcp = C.ZT_ENDPOINT_TYPE_IP_TCP
EndpointTypeIpHttp = C.ZT_ENDPOINT_TYPE_IP_HTTP
)
type Endpoint struct {
cep C.ZT_Endpoint
}
func EndpointTypeToString(t int) string {
switch t {
case EndpointTypeZeroTier:
return "zerotier"
case EndpointTypeEthernet:
return "ethernet"
case EndpointTypeWifiDirect:
return "wifi-direct"
case EndpointTypeBluetooth:
return "bluetooth"
case EndpointTypeIp:
return "ip/raw"
case EndpointTypeIpUdp:
return "ip/udp"
case EndpointTypeIpTcp:
return "ip/tcp"
case EndpointTypeIpHttp:
return "ip/http"
}
return "unsupported"
}
// NewEndpointFromString constructs a new endpoint from an InetAddress or Endpoint string.
// This will auto detect whether this is a plain InetAddress or an Endpoint in string
// format. If the former it's created as a ZT_ENDPOINT_TYPE_IP_UDP endpoint.
func NewEndpointFromString(s string) (*Endpoint, error) {
if len(s) == 0 {
var ep Endpoint
ep.cep._type = C.ZT_ENDPOINT_TYPE_NIL
return &ep, nil
}
var ep Endpoint
cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
if C.ZT_Endpoint_fromString(&ep.cep, cs) != 0 {
return nil, ErrInvalidParameter
}
return &ep, nil
}
func NewEndpointFromInetAddress(addr *InetAddress) (*Endpoint, error) {
var ep Endpoint
var ss C.struct_sockaddr_storage
if !makeSockaddrStorage(addr.IP, addr.Port, &ss) {
return nil, ErrInvalidParameter
}
ep.cep._type = C.ZT_ENDPOINT_TYPE_IP_UDP
C._setSS(&ep.cep, unsafe.Pointer(&ss))
return &ep, nil
}
// Type returns this endpoint's type.
func (ep *Endpoint) Type() int {
return int(ep.cep._type)
}
// TypeString returns a human-readable endpoint type.
func (ep *Endpoint) TypeString() string {
return EndpointTypeToString(int(ep.cep._type))
}
// InetAddress gets this Endpoint as an InetAddress or nil if its type is not addressed by one.
func (ep *Endpoint) InetAddress() *InetAddress {
switch ep.cep._type {
case EndpointTypeIp, EndpointTypeIpUdp, EndpointTypeIpTcp, EndpointTypeIpHttp:
ua := sockaddrStorageToUDPAddr(C._getSS(&ep.cep))
return &InetAddress{IP: ua.IP, Port: ua.Port}
}
return nil
}
// Address returns a ZeroTier address if this is a ZeroTier endpoint or a zero address otherwise.
func (ep *Endpoint) Address() Address {
switch ep.cep._type {
case EndpointTypeZeroTier:
return Address(C._getAddress(&ep.cep))
}
return Address(0)
}
// Fingerprint returns a fingerprint if this is a ZeroTier endpoint or nil otherwise.
func (ep *Endpoint) Fingerprint() *Fingerprint {
switch ep.cep._type {
case EndpointTypeZeroTier:
cfp := C._getFP(&ep.cep)
fp := Fingerprint{Address: Address(cfp.address), Hash: C.GoBytes(unsafe.Pointer(&cfp.hash[0]), 48)}
if allZero(fp.Hash) {
fp.Hash = nil
}
return &fp
}
return nil
}
// MAC returns a MAC address if this is an Ethernet type endpoint or a zero address otherwise.
func (ep *Endpoint) MAC() MAC {
switch ep.cep._type {
case EndpointTypeEthernet, EndpointTypeWifiDirect, EndpointTypeBluetooth:
return MAC(C._getMAC(&ep.cep))
}
return MAC(0)
}
func (ep *Endpoint) String() string {
var buf [4096]byte
cs := C.ZT_Endpoint_toString(&ep.cep, (*C.char)(unsafe.Pointer(&buf[0])), 4096)
if cs == nil {
return "0"
}
return C.GoString(cs)
}
func (ep *Endpoint) MarshalJSON() ([]byte, error) {
s := ep.String()
return json.Marshal(&s)
}
func (ep *Endpoint) UnmarshalJSON(j []byte) error {
var s string
err := json.Unmarshal(j, &s)
if err != nil {
return err
}
ep2, err := NewEndpointFromString(s)
if err != nil {
return err
}
*ep = *ep2
return nil
}
func (ep *Endpoint) setFromCEndpoint(cp *C.ZT_Endpoint) {
ep.cep = *cp
}

View file

@ -1,49 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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
// Err is a basic string error type for ZeroTier
type Err string
func (e Err) Error() string { return (string)(e) }
// Simple ZeroTier Errors
const (
ErrInternal Err = "internal error"
ErrNodeInitFailed Err = "unable to initialize core Node instance"
ErrInvalidMACAddress Err = "invalid MAC address"
ErrInvalidZeroTierAddress Err = "invalid ZeroTier address"
ErrInvalidNetworkID Err = "invalid network ID"
ErrInvalidParameter Err = "invalid parameter"
ErrTapInitFailed Err = "unable to create native Tap instance"
ErrUnrecognizedIdentityType Err = "unrecognized identity type"
ErrInvalidKey Err = "invalid key data"
ErrCertificateHaveNewerCert Err = "a newer certificate for this subject unique ID is already loaded"
ErrCertificateInvalidFormat Err = "invalid certificate format"
ErrCertificateInvalidIdentity Err = "invalid identity in certificate"
ErrCertificateInvalidPrimarySignature Err = "invalid primary signature"
ErrCertificateInvalidChain Err = "certificate chain verification failed"
ErrCertificateInvalidComponentSignature Err = "an internal component of this certificate has an invalid signature"
ErrCertificateInvalidUniqueIDProof Err = "certificate subject unique ID proof signature verification failed"
ErrCertificateMissingRequiredFields Err = "certificate is missing one or more required fields"
ErrCertificateOutOfValidTimeWindow Err = "certificate is out of its valid time window"
)
// APIErr is returned by the JSON API when a call fails
type APIErr struct {
Reason string
}
func (e *APIErr) Error() string { return e.Reason }

View file

@ -1,123 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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
// #include "../../serviceiocore/GoGlue.h"
import "C"
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"unsafe"
)
// FingerprintHashSize is the length of a fingerprint hash in bytes.
const FingerprintHashSize = 48
// Fingerprint bundles an address with an optional SHA384 full hash of the identity's key(s).
type Fingerprint struct {
Address Address `json:"address"`
Hash []byte `json:"hash"`
}
// NewFingerprintFromString decodes a string-format fingerprint.
// A fingerprint has the format address-hash, where address is a 10-digit
// ZeroTier address and a hash is a base32-encoded SHA384 hash. Fingerprints
// can be missing the hash in which case they are represented the same as
// an Address and the hash field will be nil.
func NewFingerprintFromString(fps string) (*Fingerprint, error) {
if len(fps) < AddressStringLength {
return nil, ErrInvalidZeroTierAddress
}
ss := strings.Split(fps, "-")
if len(ss) < 1 || len(ss) > 2 {
return nil, ErrInvalidParameter
}
a, err := NewAddressFromString(ss[0])
if err != nil {
return nil, err
}
if len(ss) == 2 {
h, err := Base32.DecodeString(ss[1])
if err != nil {
return nil, err
}
if len(h) != 48 {
return nil, ErrInvalidParameter
}
return &Fingerprint{Address: a, Hash: h}, nil
}
return &Fingerprint{Address: a, Hash: nil}, nil
}
func newFingerprintFromCFingerprint(cfp *C.ZT_Fingerprint) *Fingerprint {
var fp Fingerprint
if uintptr(unsafe.Pointer(cfp)) != 0 {
fp.Address = Address(cfp.address)
fp.Hash = C.GoBytes(unsafe.Pointer(&cfp.hash[0]), 48)
if allZero(fp.Hash) {
fp.Hash = nil
}
}
return &fp
}
// String returns an address or a full address-hash depenting on whether a hash is present.
func (fp *Fingerprint) String() string {
if len(fp.Hash) == FingerprintHashSize {
return fmt.Sprintf("%.10x-%s", uint64(fp.Address), Base32.EncodeToString(fp.Hash))
}
return fp.Address.String()
}
// Equals test for full equality with another fingerprint (including hash).
func (fp *Fingerprint) Equals(fp2 *Fingerprint) bool {
return fp.Address == fp2.Address && bytes.Equal(fp.Hash[:], fp2.Hash[:])
}
// BestSpecificityEquals compares either just the addresses or also the hashes if both are present.
func (fp *Fingerprint) BestSpecificityEquals(fp2 *Fingerprint) bool {
if fp2 == nil || fp.Address != fp2.Address {
return false
}
if len(fp.Hash) == FingerprintHashSize && len(fp2.Hash) == FingerprintHashSize {
return bytes.Equal(fp.Hash, fp2.Hash)
}
return true
}
func (fp *Fingerprint) MarshalJSON() ([]byte, error) {
return []byte("\"" + fp.String() + "\""), nil
}
func (fp *Fingerprint) UnmarshalJSON(j []byte) error {
var s string
err := json.Unmarshal(j, &s)
if err != nil {
return err
}
fp2, err := NewFingerprintFromString(s)
fp.Address = fp2.Address
fp.Hash = fp2.Hash
return err
}
func (fp *Fingerprint) cFingerprint() *C.ZT_Fingerprint {
var apifp C.ZT_Fingerprint
apifp.address = C.uint64_t(fp.Address)
copy((*[48]byte)(unsafe.Pointer(&apifp.hash[0]))[:], fp.Hash[:])
return &apifp
}

View file

@ -1,274 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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
// #include "../../serviceiocore/GoGlue.h"
import "C"
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"runtime"
"strings"
"unsafe"
)
// TODO: export keys in ssh format?
const (
IdentityTypeC25519 = 0
IdentityTypeP384 = 1
IdentityTypeC25519PublicKeySize = 64
IdentityTypeC25519PrivateKeySize = 64
IdentityTypeP384PublicKeySize = 114
IdentityTypeP384PrivateKeySize = 112
)
// Identity is precisely what it sounds like: the address and associated keys for a ZeroTier node
type Identity struct {
address Address
idtype int
publicKey []byte
privateKey []byte
cid unsafe.Pointer
}
func identityFinalizer(obj interface{}) {
cid := obj.(*Identity).cid
if cid != nil {
C.ZT_Identity_delete(cid)
}
}
// NewIdentity generates a new identity of the selected type.
func NewIdentity(identityType int) (*Identity, error) {
var cid unsafe.Pointer
switch identityType {
case C.ZT_IDENTITY_TYPE_C25519:
cid = C.ZT_Identity_new(C.ZT_IDENTITY_TYPE_C25519)
case C.ZT_IDENTITY_TYPE_P384:
cid = C.ZT_Identity_new(C.ZT_IDENTITY_TYPE_P384)
default:
return nil, ErrInvalidParameter
}
id, err := newIdentityFromCIdentity(cid)
if err != nil {
return nil, err
}
id.cid = cid
return id, nil
}
// NewIdentityFromString generates a new identity from its string representation.
// The private key is imported as well if it is present.
func NewIdentityFromString(s string) (*Identity, error) {
ss := strings.Split(strings.TrimSpace(s), ":")
if len(ss) < 3 {
return nil, ErrInvalidParameter
}
var err error
id := new(Identity)
id.address, err = NewAddressFromString(ss[0])
if err != nil {
return nil, err
}
if ss[1] == "0" {
id.idtype = 0
} else if ss[1] == "1" {
id.idtype = 1
} else {
return nil, ErrUnrecognizedIdentityType
}
switch id.idtype {
case 0:
id.publicKey, err = hex.DecodeString(ss[2])
if err != nil {
return nil, err
}
if len(ss) >= 4 {
id.privateKey, err = hex.DecodeString(ss[3])
if err != nil {
return nil, err
}
}
case 1:
id.publicKey, err = Base32.DecodeString(ss[2])
if err != nil {
return nil, err
}
if len(id.publicKey) != IdentityTypeP384PublicKeySize {
return nil, ErrInvalidKey
}
if len(ss) >= 4 {
id.privateKey, err = Base32.DecodeString(ss[3])
if err != nil {
return nil, err
}
if len(id.privateKey) != IdentityTypeP384PrivateKeySize {
return nil, ErrInvalidKey
}
}
}
return id, nil
}
func newIdentityFromCIdentity(cid unsafe.Pointer) (*Identity, error) {
if cid == nil {
return nil, ErrInvalidParameter
}
var idStrBuf [4096]byte
idStr := C.ZT_Identity_toString(cid, (*C.char)(unsafe.Pointer(&idStrBuf[0])), 4096, 1)
if uintptr(unsafe.Pointer(idStr)) == 0 {
return nil, ErrInternal
}
id, err := NewIdentityFromString(C.GoString(idStr))
if err != nil {
return nil, err
}
runtime.SetFinalizer(id, identityFinalizer)
return id, nil
}
func (id *Identity) cIdentity() unsafe.Pointer {
if id.cid == nil {
str := []byte(id.PrivateKeyString())
if len(str) == 0 {
str = []byte(id.String())
}
if len(str) == 0 {
return nil
}
str = append(str, byte(0))
id.cid = C.ZT_Identity_fromString((*C.char)(unsafe.Pointer(&str[0])))
}
return id.cid
}
// Address returns this identity's address.
func (id *Identity) Address() Address { return id.address }
// HasPrivate returns true if this identity has its own private portion.
func (id *Identity) HasPrivate() bool { return len(id.privateKey) > 0 }
// Fingerprint gets this identity's address plus hash of public key(s).
func (id *Identity) Fingerprint() *Fingerprint {
return newFingerprintFromCFingerprint(C.ZT_Identity_fingerprint(id.cIdentity()))
}
// PrivateKeyString returns the full identity.secret if the private key is set,
// or an empty string if no private key is set.
func (id *Identity) PrivateKeyString() string {
switch id.idtype {
case IdentityTypeC25519:
if len(id.publicKey) == IdentityTypeC25519PublicKeySize && len(id.privateKey) == IdentityTypeC25519PrivateKeySize {
return fmt.Sprintf("%.10x:0:%x:%x", uint64(id.address), id.publicKey, id.privateKey)
}
case IdentityTypeP384:
if len(id.publicKey) == IdentityTypeP384PublicKeySize && len(id.privateKey) == IdentityTypeP384PrivateKeySize {
return fmt.Sprintf("%.10x:1:%s:%s", uint64(id.address), Base32.EncodeToString(id.publicKey), Base32.EncodeToString(id.privateKey))
}
}
return ""
}
// PublicKeyString returns the address and public key (identity.public contents).
// An empty string is returned if this identity is invalid or not initialized.
func (id *Identity) String() string {
switch id.idtype {
case IdentityTypeC25519:
if len(id.publicKey) == IdentityTypeC25519PublicKeySize {
return fmt.Sprintf("%.10x:0:%x", uint64(id.address), id.publicKey)
}
case IdentityTypeP384:
if len(id.publicKey) == IdentityTypeP384PublicKeySize {
return fmt.Sprintf("%.10x:1:%s", uint64(id.address), Base32.EncodeToString(id.publicKey))
}
}
return ""
}
// LocallyValidate performs local self-validation of this identity
func (id *Identity) LocallyValidate() bool {
return C.ZT_Identity_validate(id.cIdentity()) != 0
}
// Sign signs a message with this identity
func (id *Identity) Sign(msg []byte) ([]byte, error) {
var dataP unsafe.Pointer
if len(msg) > 0 {
dataP = unsafe.Pointer(&msg[0])
}
var sig [96]byte
sigLen := C.ZT_Identity_sign(id.cIdentity(), dataP, C.uint(len(msg)), unsafe.Pointer(&sig[0]), 96)
if sigLen <= 0 {
return nil, ErrInvalidKey
}
return sig[0:uint(sigLen)], nil
}
// Verify verifies a signature
func (id *Identity) Verify(msg, sig []byte) bool {
if len(sig) == 0 {
return false
}
var dataP unsafe.Pointer
if len(msg) > 0 {
dataP = unsafe.Pointer(&msg[0])
}
return C.ZT_Identity_verify(id.cIdentity(), dataP, C.uint(len(msg)), unsafe.Pointer(&sig[0]), C.uint(len(sig))) != 0
}
// Equals performs a deep equality test between this and another identity
func (id *Identity) Equals(id2 *Identity) bool {
if id2 == nil {
return id == nil
}
if id == nil {
return false
}
return id.address == id2.address && id.idtype == id2.idtype && bytes.Equal(id.publicKey, id2.publicKey) && bytes.Equal(id.privateKey, id2.privateKey)
}
func (id *Identity) MarshalJSON() ([]byte, error) {
return []byte("\"" + id.String() + "\""), nil
}
func (id *Identity) UnmarshalJSON(j []byte) error {
var s string
err := json.Unmarshal(j, &s)
if err != nil {
return err
}
nid, err := NewIdentityFromString(s)
if err != nil {
return err
}
*id = *nid
return nil
}

View file

@ -1,202 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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
// #include "../../serviceiocore/GoGlue.h"
import "C"
import (
"bytes"
"encoding/binary"
"encoding/json"
"net"
"strconv"
"strings"
"syscall"
"unsafe"
)
func sockaddrStorageToIPNet(ss *C.struct_sockaddr_storage) *net.IPNet {
var a net.IPNet
switch ss.ss_family {
case syscall.AF_INET:
sa4 := (*C.struct_sockaddr_in)(unsafe.Pointer(ss))
var ip4 [4]byte
copy(ip4[:], (*[4]byte)(unsafe.Pointer(&sa4.sin_addr))[:])
a.IP = ip4[:]
a.Mask = net.CIDRMask(int(binary.BigEndian.Uint16(((*[2]byte)(unsafe.Pointer(&sa4.sin_port)))[:])), 32)
return &a
case syscall.AF_INET6:
sa6 := (*C.struct_sockaddr_in6)(unsafe.Pointer(ss))
var ip6 [16]byte
copy(ip6[:], (*[16]byte)(unsafe.Pointer(&sa6.sin6_addr))[:])
a.IP = ip6[:]
a.Mask = net.CIDRMask(int(binary.BigEndian.Uint16(((*[2]byte)(unsafe.Pointer(&sa6.sin6_port)))[:])), 128)
return &a
}
return nil
}
func sockaddrStorageToUDPAddr(ss *C.struct_sockaddr_storage) *net.UDPAddr {
var a net.UDPAddr
switch ss.ss_family {
case syscall.AF_INET:
sa4 := (*C.struct_sockaddr_in)(unsafe.Pointer(ss))
var ip4 [4]byte
copy(ip4[:], (*[4]byte)(unsafe.Pointer(&sa4.sin_addr))[:])
a.IP = ip4[:]
a.Port = int(binary.BigEndian.Uint16(((*[2]byte)(unsafe.Pointer(&sa4.sin_port)))[:]))
return &a
case syscall.AF_INET6:
sa6 := (*C.struct_sockaddr_in6)(unsafe.Pointer(ss))
var ip6 [16]byte
copy(ip6[:], (*[16]byte)(unsafe.Pointer(&sa6.sin6_addr))[:])
a.IP = ip6[:]
a.Port = int(binary.BigEndian.Uint16(((*[2]byte)(unsafe.Pointer(&sa6.sin6_port)))[:]))
return &a
}
return nil
}
func sockaddrStorageToUDPAddr2(ss unsafe.Pointer) *net.UDPAddr {
return sockaddrStorageToUDPAddr((*C.struct_sockaddr_storage)(ss))
}
func zeroSockaddrStorage(ss *C.struct_sockaddr_storage) {
C.memset(unsafe.Pointer(ss), 0, C.sizeof_struct_sockaddr_storage)
}
func makeSockaddrStorage(ip net.IP, port int, ss *C.struct_sockaddr_storage) bool {
zeroSockaddrStorage(ss)
if len(ip) == 4 {
sa4 := (*C.struct_sockaddr_in)(unsafe.Pointer(ss))
sa4.sin_family = syscall.AF_INET
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 = syscall.AF_INET6
copy(((*[16]byte)(unsafe.Pointer(&sa6.sin6_addr)))[:], ip)
binary.BigEndian.PutUint16(((*[2]byte)(unsafe.Pointer(&sa6.sin6_port)))[:], uint16(port))
return true
}
return false
}
// InetAddress implements net.Addr but has a ZeroTier-like string representation
type InetAddress struct {
IP net.IP
Port int
}
// Nil returns true if this InetAddress is empty.
func (ina *InetAddress) Nil() bool {
return len(ina.IP) == 0
}
// Less returns true if this IP/port is lexicographically less than another
func (ina *InetAddress) Less(i2 *InetAddress) bool {
c := bytes.Compare(ina.IP, i2.IP)
if c < 0 {
return true
}
if c == 0 {
return ina.Port < i2.Port
}
return false
}
// NewInetAddressFromString parses an IP[/port] format address
func NewInetAddressFromString(s string) *InetAddress {
i := new(InetAddress)
ss := strings.Split(strings.TrimSpace(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(ss[1], 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 (ina *InetAddress) Network() string {
return "udp"
}
// String returns this address in ZeroTier-canonical IP/port format
func (ina *InetAddress) String() string {
return ina.IP.String() + "/" + strconv.FormatInt(int64(ina.Port), 10)
}
// Family returns the address family (AFInet etc.) or 0 if none
func (ina *InetAddress) Family() int {
switch len(ina.IP) {
case 4:
return syscall.AF_INET
case 16:
return syscall.AF_INET6
}
return 0
}
// Valid returns true if both the IP and port have valid values
func (ina *InetAddress) Valid() bool {
return (len(ina.IP) == 4 || len(ina.IP) == 16) && (ina.Port > 0 && ina.Port < 65536)
}
// MarshalJSON marshals this MAC as a string
func (ina *InetAddress) MarshalJSON() ([]byte, error) {
s := ina.String()
return json.Marshal(&s)
}
// UnmarshalJSON unmarshals this MAC from a string
func (ina *InetAddress) UnmarshalJSON(j []byte) error {
var s string
err := json.Unmarshal(j, &s)
if err != nil {
return err
}
*ina = *NewInetAddressFromString(s)
return nil
}
// key returns a short array suitable for use as a map[] key for this IP
func (ina *InetAddress) key() (k [3]uint64) {
copy(((*[16]byte)(unsafe.Pointer(&k[0])))[:], ina.IP)
k[2] = uint64(ina.Port)
return
}

View file

@ -1,130 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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"
"io/ioutil"
"os"
"runtime"
)
// LocalConfigPhysicalPathConfiguration contains settings for physical paths
type LocalConfigPhysicalPathConfiguration struct {
// Blacklist flags this path as unusable for ZeroTier traffic
Blacklist bool
}
// LocalConfigVirtualAddressConfiguration contains settings for virtual addresses
type LocalConfigVirtualAddressConfiguration struct {
// 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 `json:"try,omitempty"`
}
// LocalConfigSettings contains node settings
type LocalConfigSettings struct {
// PrimaryPort is the main UDP port and must be set.
PrimaryPort int `json:"primaryPort"`
// SecondaryPort is the secondary UDP port, set to 0 to disable (picked at random by default)
SecondaryPort int `json:"secondaryPort"`
// PortMapping enables uPnP and NAT-PMP support
PortMapping bool `json:"portMapping"`
// LogSizeMax is the maximum size of the infoLog in kilobytes or 0 for no limit and -1 to disable logging
LogSizeMax int `json:"logSizeMax"`
// IP/port to bind for TCP access to control API (TCP API port disabled if null)
APITCPBindAddress *InetAddress `json:"apiTCPBindAddress,omitempty"`
// InterfacePrefixBlacklist are prefixes of physical network interface names that won't be used by ZeroTier (e.g. "lo" or "utun")
InterfacePrefixBlacklist []string `json:"interfacePrefixBlacklist,omitempty"`
// ExplicitAddresses are explicit IP/port addresses to advertise to other nodes, such as externally mapped ports on a router
ExplicitAddresses []InetAddress `json:"explicitAddresses,omitempty"`
}
// 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 `json:"physical,omitempty"`
// Virtual node specific configurations by 10-digit hex ZeroTier address
Virtual map[Address]LocalConfigVirtualAddressConfiguration `json:"virtual,omitempty"`
// Network local configurations by 16-digit hex ZeroTier network ID
Network map[NetworkID]NetworkLocalSettings `json:"network,omitempty"`
// LocalConfigSettings contains other local settings for this node
Settings LocalConfigSettings `json:"settings"`
initialized bool
}
// Read this local config from a file, initializing to defaults if the file does not exist.
func (lc *LocalConfig) Read(p string, saveDefaultsIfNotExist, isTotallyNewNode bool) error {
// Initialize defaults, which may be replaced if we read a file from disk.
if !lc.initialized {
lc.initialized = true
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 = unassignedPrivilegedPorts[randomUInt()%uint(len(unassignedPrivilegedPorts))]
lc.Settings.PortMapping = true
lc.Settings.LogSizeMax = 128
if !isTotallyNewNode && runtime.GOOS != "darwin" && runtime.GOOS != "windows" {
// If this doesn't look like a new node and it's not a desktop OS, go ahead
// and bind the local TCP API port so as not to break scripts.
lc.Settings.APITCPBindAddress = NewInetAddressFromString("127.0.0.1/9993")
}
switch runtime.GOOS {
case "windows":
lc.Settings.InterfacePrefixBlacklist = []string{"loopback"}
case "darwin":
lc.Settings.InterfacePrefixBlacklist = []string{"lo", "utun", "feth"}
default:
lc.Settings.InterfacePrefixBlacklist = []string{"lo"}
}
}
data, err := ioutil.ReadFile(p)
if err != nil {
if !os.IsNotExist(err) {
return err
}
if saveDefaultsIfNotExist {
err = lc.Write(p)
if err != nil {
return err
}
}
return nil
}
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)
}

View file

@ -1,159 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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
// #include "../../serviceiocore/GoGlue.h"
import "C"
import (
"encoding/json"
"runtime"
"unsafe"
)
type Locator struct {
Timestamp int64 `json:"timestamp"`
Fingerprint *Fingerprint `json:"fingerprint"`
Endpoints []Endpoint `json:"endpoints"`
cl unsafe.Pointer
}
func newLocatorFromCLocator(cl unsafe.Pointer, needFinalizer bool) (*Locator, error) {
loc := new(Locator)
loc.cl = cl
err := loc.init(needFinalizer)
if err != nil {
return nil, err
}
return loc, nil
}
func NewLocator(ts int64, endpoints []*Endpoint, signer *Identity) (*Locator, error) {
if ts <= 0 || len(endpoints) == 0 || signer == nil {
return nil, ErrInvalidParameter
}
eps := make([]C.ZT_Endpoint, 0, len(endpoints))
for _, e := range endpoints {
eps = append(eps, e.cep)
}
loc := C.ZT_Locator_create(C.int64_t(ts), &eps[0], nil, C.uint(len(eps)), signer.cIdentity())
if uintptr(loc) == 0 {
return nil, ErrInvalidParameter
}
goloc := new(Locator)
goloc.cl = unsafe.Pointer(loc)
return goloc, goloc.init(true)
}
func NewLocatorFromBytes(lb []byte) (*Locator, error) {
if len(lb) == 0 {
return nil, ErrInvalidParameter
}
loc := C.ZT_Locator_unmarshal(unsafe.Pointer(&lb[0]), C.uint(len(lb)))
if uintptr(loc) == 0 {
return nil, ErrInvalidParameter
}
goloc := new(Locator)
goloc.cl = unsafe.Pointer(loc)
return goloc, goloc.init(true)
}
func NewLocatorFromString(s string) (*Locator, error) {
if len(s) == 0 {
return nil, ErrInvalidParameter
}
sb := []byte(s)
sb = append(sb, 0)
loc := C.ZT_Locator_fromString((*C.char)(unsafe.Pointer(&sb[0])))
if loc == nil {
return nil, ErrInvalidParameter
}
goloc := new(Locator)
goloc.cl = unsafe.Pointer(loc)
return goloc, goloc.init(true)
}
func (loc *Locator) Validate(id *Identity) bool {
if id == nil {
return false
}
return C.ZT_Locator_verify(loc.cl, id.cIdentity()) != 0
}
func (loc *Locator) Bytes() []byte {
if loc.cl == nil {
return nil
}
var buf [16384]byte // larger than ZT_LOCATOR_MARSHAL_SIZE_MAX
bl := C.ZT_Locator_marshal(loc.cl, unsafe.Pointer(&buf[0]), 16384)
if bl <= 0 {
return nil
}
return buf[0:int(bl)]
}
func (loc *Locator) String() string {
if loc.cl == nil {
return ""
}
var buf [16384]C.char // 16384 == ZT_LOCATOR_STRING_SIZE_MAX
return C.GoString(C.ZT_Locator_toString(loc.cl, &buf[0], 16384))
}
func (loc *Locator) MarshalJSON() ([]byte, error) {
return json.Marshal(loc)
}
func (loc *Locator) UnmarshalJSON(j []byte) error {
if loc.cl != nil {
C.ZT_Locator_delete(loc.cl)
loc.cl = unsafe.Pointer(nil)
}
err := json.Unmarshal(j, loc)
if err != nil {
return err
}
return loc.init(true)
}
func locatorFinalizer(obj interface{}) {
if obj != nil {
cl := obj.(*Locator).cl
if cl != nil {
C.ZT_Locator_delete(cl)
}
}
}
func (loc *Locator) init(needFinalizer bool) error {
loc.Timestamp = int64(C.ZT_Locator_timestamp(loc.cl))
cfp := C.ZT_Locator_fingerprint(loc.cl)
if uintptr(unsafe.Pointer(cfp)) == 0 {
return ErrInternal
}
loc.Fingerprint = newFingerprintFromCFingerprint(cfp)
epc := int(C.ZT_Locator_endpointCount(loc.cl))
loc.Endpoints = make([]Endpoint, epc)
for i := 0; i < epc; i++ {
loc.Endpoints[i].cep = *C.ZT_Locator_endpoint(loc.cl, C.uint(i))
}
if needFinalizer {
runtime.SetFinalizer(loc, locatorFinalizer)
}
return nil
}

View file

@ -1,93 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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"
"fmt"
"strconv"
"strings"
)
// MAC represents an Ethernet hardware address
type MAC uint64
// NewMACFromString decodes a MAC address in canonical colon-separated hex format
func NewMACFromString(s string) (MAC, error) {
ss := strings.Split(s, ":")
if len(ss) != 6 {
return MAC(0), ErrInvalidMACAddress
}
var m uint64
for i := 0; i < 6; i++ {
m <<= 8
c, _ := strconv.ParseUint(ss[i], 16, 64)
if c > 0xff {
return MAC(0), ErrInvalidMACAddress
}
m |= c & 0xff
}
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)
}
// MarshalJSON marshals this MAC as a string
func (m MAC) MarshalJSON() ([]byte, error) {
return []byte("\"" + m.String() + "\""), nil
}
// UnmarshalJSON unmarshals this MAC from a string
func (m *MAC) UnmarshalJSON(j []byte) error {
var s string
err := json.Unmarshal(j, &s)
if err != nil {
return err
}
*m, err = NewMACFromString(s)
return err
}

View file

@ -1,304 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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
// #include "../../serviceiocore/GoGlue.h"
import "C"
import (
"encoding/base32"
"encoding/binary"
"math/rand"
"net"
"sync"
"time"
"unsafe"
)
// LogoChar is the unicode character that is ZeroTier's logo
const LogoChar = "⏁"
// pointerSize is the size of a pointer on this system
const pointerSize = unsafe.Sizeof(uintptr(0))
// Base32Alphabet is the Base32 alphabet used in ZeroTier.
const Base32Alphabet = "abcdefghijklmnopqrstuvwxyz234567"
// Base32 is an encoder using the ZeroTier base32 encoding and no padding (same as core).
var Base32 = base32.NewEncoding(Base32Alphabet).WithPadding(base32.NoPadding)
// unassignedPrivilegedPorts are ports below 1024 that do not appear to be assigned by IANA.
// The new 2.0+ ZeroTier default is 793, which we will eventually seek to have assigned. These
// are searched as backups if this port is already in use on a system.
var unassignedPrivilegedPorts = []int{
4,
6,
8,
10,
12,
14,
15,
16,
26,
28,
30,
32,
34,
36,
40,
60,
269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279,
285,
288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307,
323, 324, 325, 326, 327, 328, 329, 330, 331, 332,
334, 335, 336, 337, 338, 339, 340, 341, 342, 343,
703,
708,
713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728,
732, 733, 734, 735, 736, 737, 738, 739, 740,
743,
745, 746,
755, 756,
766,
768,
778, 779,
781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799,
802, 803, 804, 805, 806, 807, 808, 809,
811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827,
834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846,
849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859,
862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872,
874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885,
889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899,
904, 905, 906, 907, 908, 909, 910, 911,
914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988,
1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009,
1023,
}
var prng = rand.NewSource(time.Now().UnixNano())
var prngLock sync.Mutex
func randomUInt() uint {
prngLock.Lock()
i := prng.Int63()
prngLock.Unlock()
return uint(i)
}
// TimeMs returns the time in milliseconds since epoch.
func TimeMs() int64 { return int64(time.Now().UnixNano()) / int64(1000000) }
// ipNetToKey creates a key that can be used in a map[] from a net.IPNet
func ipNetToKey(ipn *InetAddress) (k [3]uint64) {
copy(((*[16]byte)(unsafe.Pointer(&k[0])))[:], ipn.IP)
k[2] = uint64(ipn.Port)
return
}
func allZero(b []byte) bool {
for _, bb := range b {
if bb != 0 {
return false
}
}
return true
}
// checkPort does trial binding to a port using both UDP and TCP and returns false if any bindings fail.
func checkPort(port int) bool {
var ua net.UDPAddr
ua.IP = net.IPv6zero
ua.Port = port
uc, err := net.ListenUDP("udp6", &ua)
if uc != nil {
_ = uc.Close()
}
if err != nil {
return false
}
ua.IP = net.IPv4zero
uc, err = net.ListenUDP("udp4", &ua)
if uc != nil {
_ = uc.Close()
}
if err != nil {
return false
}
var ta net.TCPAddr
ta.IP = net.IPv6zero
ta.Port = port
tc, err := net.ListenTCP("tcp6", &ta)
if tc != nil {
_ = tc.Close()
}
if err != nil {
return false
}
ta.IP = net.IPv4zero
tc, err = net.ListenTCP("tcp4", &ta)
if tc != nil {
_ = tc.Close()
}
if err != nil {
return false
}
return true
}
// The ipClassify code below is based on and should produce identical results to
// InetAddress::ipScope() in the C++ code.
const (
ipClassificationNone = -1
ipClassificationLoopback = 0
ipClassificationPseudoprivate = 1
ipClassificationPrivate = 2
ipClassificationLinkLocal = 3
ipClassificationMulticast = 4
ipClassificationGlobal = 5
)
var ipv4PseudoprivatePrefixes = []byte{
0x06, // 6.0.0.0/8 (US Army)
0x0b, // 11.0.0.0/8 (US DoD)
0x15, // 21.0.0.0/8 (US DDN-RVN)
0x16, // 22.0.0.0/8 (US DISA)
0x19, // 25.0.0.0/8 (UK Ministry of Defense)
0x1a, // 26.0.0.0/8 (US DISA)
0x1c, // 28.0.0.0/8 (US DSI-North)
0x1d, // 29.0.0.0/8 (US DISA)
0x1e, // 30.0.0.0/8 (US DISA)
0x33, // 51.0.0.0/8 (UK Department of Social Security)
0x37, // 55.0.0.0/8 (US DoD)
0x38, // 56.0.0.0/8 (US Postal Service)
}
// ipClassify determines the official or in a few cases unofficial role of an IP address
func ipClassify(ip net.IP) int {
if len(ip) == 16 {
ip4 := ip.To4()
if len(ip4) == 4 {
ip = ip4
}
}
if len(ip) == 4 {
ip4FirstByte := ip[0]
for _, b := range ipv4PseudoprivatePrefixes {
if ip4FirstByte == b {
return ipClassificationPseudoprivate
}
}
ip4 := binary.BigEndian.Uint32(ip)
switch ip4FirstByte {
case 0x0a: // 10.0.0.0/8
return ipClassificationPrivate
case 0x64: // 100.64.0.0/10
if (ip4 & 0xffc00000) == 0x64400000 {
return ipClassificationPrivate
}
case 0x7f: // 127.0.0.1/8
return ipClassificationLoopback
case 0xa9: // 169.254.0.0/16
if (ip4 & 0xffff0000) == 0xa9fe0000 {
return ipClassificationLinkLocal
}
case 0xac: // 172.16.0.0/12
if (ip4 & 0xfff00000) == 0xac100000 {
return ipClassificationPrivate
}
case 0xc0: // 192.168.0.0/16
if (ip4 & 0xffff0000) == 0xc0a80000 {
return ipClassificationPrivate
}
}
switch ip4 >> 28 {
case 0xe: // 224.0.0.0/4
return ipClassificationMulticast
case 0xf: // 240.0.0.0/4 ("reserved," usually unusable)
return ipClassificationNone
}
return ipClassificationGlobal
}
if len(ip) == 16 {
if (ip[0] & 0xf0) == 0xf0 {
if ip[0] == 0xff { // ff00::/8
return ipClassificationMulticast
}
if ip[0] == 0xfe && (ip[1]&0xc0) == 0x80 {
if allZero(ip[2:15]) {
if ip[15] == 0x01 { // fe80::1/128
return ipClassificationLoopback
}
return ipClassificationLinkLocal
}
}
if (ip[0] & 0xfe) == 0xfc { // fc00::/7
return ipClassificationPrivate
}
}
if allZero(ip[0:15]) {
if ip[15] == 0x01 { // ::1/128
return ipClassificationLoopback
}
if ip[15] == 0x00 { // ::/128
return ipClassificationNone
}
}
return ipClassificationGlobal
}
return ipClassificationNone
}
// stringAsZeroTerminatedBytes creates a C string but as a Go []byte
func stringAsZeroTerminatedBytes(s string) (b []byte) {
if len(s) == 0 {
b = []byte{0} // single zero
return
}
sb := []byte(s)
b = make([]byte, len(sb) + 1)
copy(b, sb)
// make() will zero memory, so b[len(sb)+1] will be 0
return
}
// cStrCopy copies src into dest as a zero-terminated C string
func cStrCopy(dest unsafe.Pointer, destSize int, src string) {
sb := []byte(src)
if len(sb) > (destSize - 1) {
sb = sb[0:destSize - 1]
}
dp := dest
for _, c := range sb {
*((*byte)(dp)) = c
dp = unsafe.Pointer(uintptr(dp) + 1)
}
*((*byte)(dp)) = 0
}
// cStr returns an always zero-terminated byte array.
// It's like C.CString but doesn't do a malloc or need a free.
func cStr(s string) []byte {
sb := []byte(s)
if len(sb) > 0 {
return append(append(make([]byte, 0, len(sb)+1), sb...), byte(0))
} else {
return []byte{0}
}
}

View file

@ -1,42 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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 "fmt"
// MulticastGroup represents a normal Ethernet multicast or broadcast address plus 32 additional ZeroTier-specific bits
type MulticastGroup struct {
MAC MAC `json:"mac"`
ADI uint32 `json:"adi"`
}
// String returns MAC#ADI
func (mg *MulticastGroup) String() string {
if mg.ADI != 0 {
return fmt.Sprintf("%s#%.8x", mg.MAC.String(), mg.ADI)
}
return mg.MAC.String()
}
// Less returns true if this MulticastGroup is less than another.
func (mg *MulticastGroup) Less(mg2 *MulticastGroup) bool {
return mg.MAC < mg2.MAC || (mg.MAC == mg2.MAC && mg.ADI < mg2.ADI)
}
// key returns an array usable as a key for a map[]
func (mg *MulticastGroup) key() (k [2]uint64) {
k[0] = uint64(mg.MAC)
k[1] = uint64(mg.ADI)
return
}

View file

@ -1,193 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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.
*/
/****/
// This wraps the C++ EthernetTap and its implementations.
package zerotier
//#include "../../serviceiocore/GoGlue.h"
import "C"
import (
"fmt"
"net"
"sync"
"sync/atomic"
"syscall"
"unsafe"
)
// nativeTap is a Tap implementation that wraps a native C++ interface to a system tun/tap device
type nativeTap struct {
tap unsafe.Pointer
networkStatus uint32
enabled uint32
multicastGroupHandlers []func(bool, *MulticastGroup)
multicastGroupHandlersLock sync.Mutex
}
// Close is a no-op for the native tap because GoGlue does this when networks are left
func (t *nativeTap) Close() {}
// Type returns a human-readable description of this tap implementation
func (t *nativeTap) Type() string {
return "native"
}
// Error gets this tap device's error status
func (t *nativeTap) Error() (int, string) {
return 0, ""
}
// SetEnabled sets this tap's enabled state
func (t *nativeTap) SetEnabled(enabled bool) {
if enabled && atomic.SwapUint32(&t.enabled, 1) == 0 {
C.ZT_GoTap_setEnabled(t.tap, 1)
} else if !enabled && atomic.SwapUint32(&t.enabled, 0) == 1 {
C.ZT_GoTap_setEnabled(t.tap, 0)
}
}
// Enabled returns true if this tap is currently processing packets
func (t *nativeTap) Enabled() bool {
return atomic.LoadUint32(&t.enabled) != 0
}
// AddIP adds an IP address (with netmask) to this tap
func (t *nativeTap) AddIP(ip *InetAddress) error {
if len(ip.IP) == 16 {
if ip.Port > 128 || ip.Port < 0 {
return ErrInvalidParameter
}
C.ZT_GoTap_addIp(t.tap, C.int(syscall.AF_INET6), unsafe.Pointer(&ip.IP[0]), C.int(ip.Port))
} else if len(ip.IP) == 4 {
if ip.Port > 32 || ip.Port < 0 {
return ErrInvalidParameter
}
C.ZT_GoTap_addIp(t.tap, C.int(syscall.AF_INET), unsafe.Pointer(&ip.IP[0]), C.int(ip.Port))
}
return ErrInvalidParameter
}
// RemoveIP removes this IP address (with netmask) from this tap
func (t *nativeTap) RemoveIP(ip *InetAddress) error {
if len(ip.IP) == 16 {
if ip.Port > 128 || ip.Port < 0 {
return ErrInvalidParameter
}
C.ZT_GoTap_removeIp(t.tap, C.int(syscall.AF_INET6), unsafe.Pointer(&ip.IP[0]), C.int(ip.Port))
return nil
}
if len(ip.IP) == 4 {
if ip.Port > 32 || ip.Port < 0 {
return ErrInvalidParameter
}
C.ZT_GoTap_removeIp(t.tap, C.int(syscall.AF_INET), unsafe.Pointer(&ip.IP[0]), C.int(ip.Port))
return nil
}
return ErrInvalidParameter
}
// IPs returns IPs currently assigned to this tap (including externally or system-assigned IPs)
func (t *nativeTap) IPs() (ips []net.IPNet, err error) {
defer func() {
e := recover()
if e != nil {
err = fmt.Errorf("%v", e)
}
}()
var ipbuf [16384]byte
count := int(C.ZT_GoTap_ips(t.tap, unsafe.Pointer(&ipbuf[0]), 16384))
ipptr := 0
for i := 0; i < count; i++ {
af := int(ipbuf[ipptr])
ipptr++
switch af {
case syscall.AF_INET:
var ip [4]byte
for j := 0; j < 4; j++ {
ip[j] = ipbuf[ipptr]
ipptr++
}
bits := ipbuf[ipptr]
ipptr++
ips = append(ips, net.IPNet{IP: net.IP(ip[:]), Mask: net.CIDRMask(int(bits), 32)})
case syscall.AF_INET6:
var ip [16]byte
for j := 0; j < 16; j++ {
ip[j] = ipbuf[ipptr]
ipptr++
}
bits := ipbuf[ipptr]
ipptr++
ips = append(ips, net.IPNet{IP: net.IP(ip[:]), Mask: net.CIDRMask(int(bits), 128)})
}
}
return
}
// DeviceName gets this tap's OS-specific device name
func (t *nativeTap) DeviceName() string {
var dn [256]byte
C.ZT_GoTap_deviceName(t.tap, (*C.char)(unsafe.Pointer(&dn[0])))
for i, b := range dn {
if b == 0 {
return string(dn[0:i])
}
}
return ""
}
// AddMulticastGroupChangeHandler adds a function to be called when the tap subscribes or unsubscribes to a multicast group.
func (t *nativeTap) AddMulticastGroupChangeHandler(handler func(bool, *MulticastGroup)) {
t.multicastGroupHandlersLock.Lock()
t.multicastGroupHandlers = append(t.multicastGroupHandlers, handler)
t.multicastGroupHandlersLock.Unlock()
}
func handleTapMulticastGroupChange(gn unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t, added bool) {
node := cNodeRefs[uintptr(gn)]
if node == nil {
return
}
node.networksLock.RLock()
network := node.networks[NetworkID(nwid)]
node.networksLock.RUnlock()
if network == nil {
return
}
node.runWaitGroup.Add(1)
go func() {
defer node.runWaitGroup.Done()
tap, _ := network.tap.(*nativeTap)
if tap != nil {
mg := &MulticastGroup{MAC: MAC(mac), ADI: uint32(adi)}
tap.multicastGroupHandlersLock.Lock()
defer tap.multicastGroupHandlersLock.Unlock()
for _, h := range tap.multicastGroupHandlers {
h(added, mg)
}
}
}()
}
//export goHandleTapAddedMulticastGroup
func goHandleTapAddedMulticastGroup(gn, _ unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t) {
handleTapMulticastGroupChange(gn, nwid, mac, adi, true)
}
//export goHandleTapRemovedMulticastGroup
func goHandleTapRemovedMulticastGroup(gn, _ unsafe.Pointer, nwid, mac C.uint64_t, adi C.uint32_t) {
handleTapMulticastGroupChange(gn, nwid, mac, adi, false)
}

View file

@ -1,366 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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/binary"
"encoding/json"
"fmt"
"net"
"sort"
"strconv"
"sync"
)
// NetworkID is a network's 64-bit unique ID
type NetworkID uint64
// NewNetworkIDFromString parses a network ID in string form
func NewNetworkIDFromString(s string) (NetworkID, error) {
if len(s) != 16 {
return NetworkID(0), ErrInvalidNetworkID
}
n, err := strconv.ParseUint(s, 16, 64)
return NetworkID(n), err
}
// NewNetworkIDFromBytes reads an 8-byte / 64-bit network ID.
func NewNetworkIDFromBytes(b []byte) (NetworkID, error) {
if len(b) < 8 {
return NetworkID(0), ErrInvalidNetworkID
}
return NetworkID(binary.BigEndian.Uint64(b)), nil
}
// Controller gets the Address of this network's controller.
func (n NetworkID) Controller() Address {
return Address(uint64(n) >> 24)
}
// String returns this network ID's 16-digit hex identifier
func (n NetworkID) String() string {
return fmt.Sprintf("%.16x", uint64(n))
}
// Bytes returns this network ID as an 8-byte / 64-bit big-endian value.
func (n NetworkID) Bytes() []byte {
var b [8]byte
binary.BigEndian.PutUint64(b[:], uint64(n))
return b[:]
}
// MarshalJSON marshals this NetworkID as a string
func (n NetworkID) MarshalJSON() ([]byte, error) {
return []byte("\"" + n.String() + "\""), nil
}
// UnmarshalJSON unmarshals this NetworkID from a string
func (n *NetworkID) UnmarshalJSON(j []byte) error {
var s string
err := json.Unmarshal(j, &s)
if err != nil {
return err
}
*n, err = NewNetworkIDFromString(s)
return err
}
// NetworkConfig represents the network's current configuration as distributed by its network controller.
type NetworkConfig struct {
// ID is this network's 64-bit globally unique identifier
ID NetworkID `json:"id"`
// MAC is the Ethernet MAC address of this device on this network
MAC MAC `json:"mac"`
// Name is a short human-readable name set by the controller
Name string `json:"name"`
// Status is a status code indicating this network's authorization status
Status int `json:"status"`
// Type is this network's type
Type int `json:"type"`
// MTU is the Ethernet MTU for this network
MTU int `json:"mtu"`
// Bridge is true if this network is allowed to bridge in other devices with different Ethernet addresses
Bridge bool `json:"bridge"`
// BroadcastEnabled is true if the broadcast (ff:ff:ff:ff:ff:ff) address works (excluding IPv4 ARP which is handled via a special path)
BroadcastEnabled bool `json:"broadcastEnabled"`
// NetconfRevision is the revision number reported by the controller
NetconfRevision uint64 `json:"netconfRevision"`
// AssignedAddresses are static IPs assigned by the network controller to this device
AssignedAddresses []InetAddress `json:"assignedAddresses,omitempty"`
// Routes are static routes assigned by the network controller to this device
Routes []Route `json:"routes,omitempty"`
}
// NetworkLocalSettings is settings for this network that can be changed locally
type NetworkLocalSettings struct {
// AllowManagedIPs determines whether managed IP assignment is allowed
AllowManagedIPs bool `json:"allowManagedIPs"`
// AllowGlobalIPs determines if managed IPs that overlap with public Internet addresses are allowed
AllowGlobalIPs bool `json:"allowGlobalIPs"`
// AllowManagedRoutes determines whether managed routes can be set
AllowManagedRoutes bool `json:"allowManagedRoutes"`
// AllowGlobalRoutes determines if managed routes can overlap with public Internet addresses
AllowGlobalRoutes bool `json:"allowGlobalRoutes"`
// AllowDefaultRouteOverride determines if the default (0.0.0.0 or ::0) route on the system can be overridden ("full tunnel" mode)
AllowDefaultRouteOverride bool `json:"allowDefaultRouteOverride"`
}
// Network is a currently joined network
type Network struct {
node *Node
id NetworkID
mac MAC
tap Tap
config NetworkConfig
settings NetworkLocalSettings // locked by configLock
multicastSubscriptions map[[2]uint64]*MulticastGroup
configLock sync.RWMutex
multicastSubscriptionsLock sync.RWMutex
}
// newNetwork creates a new network with default settings
func newNetwork(node *Node, id NetworkID, t Tap) (*Network, error) {
m := NewMACForNetworkMember(node.Identity().address, id)
n := &Network{
node: node,
id: id,
mac: m,
tap: t,
config: NetworkConfig{
ID: id,
MAC: m,
Status: NetworkStatusRequestingConfiguration,
Type: NetworkTypePrivate,
MTU: int(defaultVirtualNetworkMTU),
},
settings: NetworkLocalSettings{
AllowManagedIPs: true,
AllowGlobalIPs: false,
AllowManagedRoutes: true,
AllowGlobalRoutes: false,
AllowDefaultRouteOverride: false,
},
multicastSubscriptions: make(map[[2]uint64]*MulticastGroup),
}
t.AddMulticastGroupChangeHandler(func(added bool, mg *MulticastGroup) {
if added {
n.MulticastSubscribe(mg)
} else {
n.MulticastUnsubscribe(mg)
}
})
return n, nil
}
// 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()
defer n.configLock.RUnlock()
return n.config
}
// SetLocalSettings modifies this network's local settings
func (n *Network) SetLocalSettings(ls *NetworkLocalSettings) { n.updateConfig(nil, ls) }
// LocalSettings gets this network's current local settings
func (n *Network) LocalSettings() NetworkLocalSettings {
n.configLock.RLock()
defer n.configLock.RUnlock()
return n.settings
}
// MulticastSubscribe subscribes to a multicast group
func (n *Network) MulticastSubscribe(mg *MulticastGroup) {
n.node.infoLog.Printf("%.16x joined multicast group %s", uint64(n.id), mg.String())
k := mg.key()
n.multicastSubscriptionsLock.Lock()
if _, have := n.multicastSubscriptions[k]; have {
n.multicastSubscriptionsLock.Unlock()
return
}
n.multicastSubscriptions[k] = mg
n.multicastSubscriptionsLock.Unlock()
n.node.multicastSubscribe(uint64(n.id), mg)
}
// MulticastUnsubscribe removes a subscription to a multicast group
func (n *Network) MulticastUnsubscribe(mg *MulticastGroup) {
n.node.infoLog.Printf("%.16x left multicast group %s", uint64(n.id), mg.String())
n.multicastSubscriptionsLock.Lock()
delete(n.multicastSubscriptions, mg.key())
n.multicastSubscriptionsLock.Unlock()
n.node.multicastUnsubscribe(uint64(n.id), mg)
}
// MulticastSubscriptions returns an array of all multicast subscriptions for this network
func (n *Network) MulticastSubscriptions() []*MulticastGroup {
n.multicastSubscriptionsLock.RLock()
mgs := make([]*MulticastGroup, 0, len(n.multicastSubscriptions))
for _, mg := range n.multicastSubscriptions {
mgs = append(mgs, mg)
}
n.multicastSubscriptionsLock.RUnlock()
sort.Slice(mgs, func(a, b int) bool { return mgs[a].Less(mgs[b]) })
return mgs
}
// leaving is called by Node when the network is being left
func (n *Network) leaving() {
n.tap.Close()
}
func (n *Network) networkConfigRevision() uint64 {
n.configLock.RLock()
defer n.configLock.RUnlock()
return n.config.NetconfRevision
}
func networkManagedIPAllowed(ip net.IP, ls *NetworkLocalSettings) bool {
if !ls.AllowManagedIPs {
return false
}
switch ipClassify(ip) {
case ipClassificationNone, ipClassificationLoopback, ipClassificationLinkLocal, ipClassificationMulticast:
return false
case ipClassificationGlobal:
return ls.AllowGlobalIPs
}
return true
}
func networkManagedRouteAllowed(r *Route, ls *NetworkLocalSettings) bool {
if !ls.AllowManagedRoutes {
return false
}
bits, _ := r.Target.Mask.Size()
if len(r.Target.IP) > 0 && allZero(r.Target.IP) && bits == 0 {
return ls.AllowDefaultRouteOverride
}
switch ipClassify(r.Target.IP) {
case ipClassificationNone, ipClassificationLoopback, ipClassificationLinkLocal, ipClassificationMulticast:
return false
case ipClassificationGlobal:
return ls.AllowGlobalRoutes
}
return true
}
func (n *Network) updateConfig(nc *NetworkConfig, ls *NetworkLocalSettings) {
n.configLock.Lock()
defer n.configLock.Unlock()
if n.tap == nil { // sanity check, should never happen
return
}
if nc == nil {
nc = &n.config
}
if ls == nil {
ls = &n.settings
}
// Add IPs to tap that are newly assigned in this config update,
// and remove any IPs from the tap that were assigned that are no
// longer wanted. IPs assigned to the tap externally (e.g. by an
// "ifconfig" command) are left alone.
haveAssignedIPs := make(map[[3]uint64]*InetAddress)
wantAssignedIPs := make(map[[3]uint64]bool)
if n.settings.AllowManagedIPs {
for _, ip := range n.config.AssignedAddresses {
if networkManagedIPAllowed(ip.IP, &n.settings) { // was it allowed?
haveAssignedIPs[ipNetToKey(&ip)] = &ip
}
}
}
if ls.AllowManagedIPs {
for _, ip := range nc.AssignedAddresses {
if networkManagedIPAllowed(ip.IP, ls) { // should it be allowed now?
k := ipNetToKey(&ip)
wantAssignedIPs[k] = true
if _, have := haveAssignedIPs[k]; !have {
n.node.infoLog.Printf("%.16x adding managed IP %s", uint64(n.id), ip.String())
_ = n.tap.AddIP(&ip)
}
}
}
}
for k, ip := range haveAssignedIPs {
if _, want := wantAssignedIPs[k]; !want {
n.node.infoLog.Printf("%.16x removing managed IP %s", uint64(n.id), ip.String())
_ = n.tap.RemoveIP(ip)
}
}
// Do the same for managed routes
haveManagedRoutes := make(map[[6]uint64]*Route)
wantManagedRoutes := make(map[[6]uint64]bool)
if n.settings.AllowManagedRoutes {
for _, r := range n.config.Routes {
if networkManagedRouteAllowed(&r, &n.settings) { // was it allowed?
haveManagedRoutes[r.key()] = &r
}
}
}
if ls.AllowManagedRoutes {
for _, r := range nc.Routes {
if networkManagedRouteAllowed(&r, ls) { // should it be allowed now?
k := r.key()
wantManagedRoutes[k] = true
if _, have := haveManagedRoutes[k]; !have {
n.node.infoLog.Printf("%.16x adding managed route %s", uint64(n.id), r.String())
//TODO _ = n.tap.AddRoute(&r)
}
}
}
}
for k, r := range haveManagedRoutes {
if _, want := wantManagedRoutes[k]; !want {
n.node.infoLog.Printf("%.16x removing managed route %s", uint64(n.id), r.String())
//TODO _ = n.tap.RemoveRoute(r)
}
}
if nc != &n.config {
n.config = *nc
}
if ls != &n.settings {
n.settings = *ls
}
}

View file

@ -1,955 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
package zerotier
// #cgo CFLAGS: -I${SRCDIR}/../../build/core
// #include "../../serviceiocore/GoGlue.h"
import "C"
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"net"
"net/http"
"os"
"path"
"reflect"
"sort"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
"unsafe"
"github.com/hectane/go-acl"
)
var nullLogger = log.New(ioutil.Discard, "", 0)
const (
NetworkIDStringLength = 16
NetworkIDLength = 8
AddressStringLength = 10
AddressLength = 5
DefaultPort = int(C.ZT_DEFAULT_PORT)
DefaultRawIPProto = int(C.ZT_DEFAULT_RAW_IP_PROTOCOL)
DefaultEthernetProto = int(C.ZT_DEFAULT_ETHERNET_PROTOCOL)
NetworkMaxShortNameLength = int(C.ZT_MAX_NETWORK_SHORT_NAME_LENGTH)
NetworkStatusRequestingConfiguration = int(C.ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION)
NetworkStatusOK = int(C.ZT_NETWORK_STATUS_OK)
NetworkStatusAccessDenied = int(C.ZT_NETWORK_STATUS_ACCESS_DENIED)
NetworkStatusNotFound = int(C.ZT_NETWORK_STATUS_NOT_FOUND)
NetworkTypePrivate = int(C.ZT_NETWORK_TYPE_PRIVATE)
NetworkTypePublic = int(C.ZT_NETWORK_TYPE_PUBLIC)
networkConfigOpUp = int(C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP)
networkConfigOpUpdate = int(C.ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE)
defaultVirtualNetworkMTU = int(C.ZT_DEFAULT_MTU)
maxCNodeRefs = 8 // perfectly fine to increase this
)
var (
PlatformDefaultHomePath string
CoreVersionMajor int
CoreVersionMinor int
CoreVersionRevision int
CoreVersionBuild int
// cNodeRefs maps an index to a *Node
cNodeRefs [maxCNodeRefs]*Node
// cNodeRefUsed maps an index to whether or not the corresponding cNodeRefs[] entry is used.
// This is accessed atomically to provide a really fast way to gate cNodeRefs.
cNodeRefUsed [maxCNodeRefs]uintptr
)
func init() {
PlatformDefaultHomePath = C.GoString(C.ZT_PLATFORM_DEFAULT_HOMEPATH)
var vMaj, vMin, vRev, vBuild C.int
C.ZT_version(&vMaj, &vMin, &vRev, &vBuild)
CoreVersionMajor = int(vMaj)
CoreVersionMinor = int(vMin)
CoreVersionRevision = int(vRev)
CoreVersionBuild = int(vBuild)
}
// Node is an instance of a virtual port on the global switch.
type Node struct {
// Time this node was created
startupTime int64
// cPtr is an arbitrary pseudo-pointer given to the core to map back to our Go object.
// This is an index into the cNodeRefs array.
cPtr uintptr
networks map[NetworkID]*Network
networksByMAC map[MAC]*Network // locked by networksLock
networksLock sync.RWMutex
localInterfaceAddresses map[string]net.IP
localInterfaceAddressesLock sync.Mutex
running uintptr // atomic flag
online uintptr // atomic flag
basePath string
peersPath string
certsPath string
networksPath string
localConfigPath string
infoLogPath string
errorLogPath string
localConfig *LocalConfig
previousLocalConfig *LocalConfig
localConfigLock sync.RWMutex
infoLogW *sizeLimitWriter
errLogW *sizeLimitWriter
traceLogW io.Writer
infoLog *log.Logger
errLog *log.Logger
traceLog *log.Logger
namedSocketAPIServer *http.Server
tcpAPIServer *http.Server
// gn is the GoNode instance, see serviceiocore/GoNode.hpp
gn *C.ZT_GoNode
// zn is the underlying ZT_Node (ZeroTier::Node) instance
zn unsafe.Pointer
// id is the identity of this node (includes private key)
id *Identity
// runWaitGroup is used to wait for all node goroutines on shutdown.
// Any new goroutine is tracked via this wait group so node shutdown can
// itself wait until all goroutines have exited.
runWaitGroup sync.WaitGroup
}
// NewNode creates and initializes a new instance of the ZeroTier node service
func NewNode(basePath string) (n *Node, err error) {
n = new(Node)
n.startupTime = TimeMs()
// Register this with the cNodeRefs lookup array and set up a deferred function
// to unregister this if we exit before the end of the constructor such as by
// returning an error.
cPtr := -1
for i := 0; i < maxCNodeRefs; i++ {
if atomic.CompareAndSwapUintptr(&cNodeRefUsed[i], 0, 1) {
cNodeRefs[i] = n
cPtr = i
n.cPtr = uintptr(i)
break
}
}
if cPtr < 0 {
return nil, ErrInternal
}
defer func() {
if cPtr >= 0 {
atomic.StoreUintptr(&cNodeRefUsed[cPtr], 0)
cNodeRefs[cPtr] = nil
}
}()
n.networks = make(map[NetworkID]*Network)
n.networksByMAC = make(map[MAC]*Network)
n.localInterfaceAddresses = make(map[string]net.IP)
n.running = 1
_ = os.MkdirAll(basePath, 0755)
if _, err = os.Stat(basePath); err != nil {
return
}
n.basePath = basePath
n.peersPath = path.Join(basePath, "peers.d")
_ = os.MkdirAll(n.peersPath, 0700)
_ = acl.Chmod(n.peersPath, 0700)
if _, err = os.Stat(n.peersPath); err != nil {
return
}
n.certsPath = path.Join(basePath, "certs.d")
_ = os.MkdirAll(n.certsPath, 0755)
n.networksPath = path.Join(basePath, "networks.d")
_ = os.MkdirAll(n.networksPath, 0755)
n.localConfigPath = path.Join(basePath, "local.conf")
_, isTotallyNewNode := os.Stat(path.Join(basePath, "identity.secret"))
n.localConfig = new(LocalConfig)
err = n.localConfig.Read(n.localConfigPath, true, isTotallyNewNode != nil)
if err != nil {
return
}
n.infoLogPath = path.Join(basePath, "info.log")
n.errorLogPath = path.Join(basePath, "error.log")
if n.localConfig.Settings.LogSizeMax >= 0 {
n.infoLogW, err = sizeLimitWriterOpen(n.infoLogPath)
if err != nil {
return
}
n.errLogW, err = sizeLimitWriterOpen(n.errorLogPath)
if err != nil {
return
}
n.infoLog = log.New(n.infoLogW, "", log.LstdFlags)
n.errLog = log.New(n.errLogW, "", log.LstdFlags)
} else {
n.infoLog = nullLogger
n.errLog = nullLogger
}
portsChanged := false
portCheckCount := 0
origPort := n.localConfig.Settings.PrimaryPort
for portCheckCount < 256 {
portCheckCount++
if checkPort(n.localConfig.Settings.PrimaryPort) {
if n.localConfig.Settings.PrimaryPort != origPort {
n.infoLog.Printf("primary port %d unavailable, found port %d and saved in local.conf", origPort, n.localConfig.Settings.PrimaryPort)
}
break
}
n.localConfig.Settings.PrimaryPort = int(4096 + (randomUInt() % 16384))
portsChanged = true
}
if portCheckCount == 256 {
return nil, errors.New("unable to bind to primary port: tried configured port and 256 other random ports")
}
if n.localConfig.Settings.SecondaryPort > 0 {
portCheckCount = 0
origPort = n.localConfig.Settings.SecondaryPort
for portCheckCount < 256 {
portCheckCount++
if checkPort(n.localConfig.Settings.SecondaryPort) {
if n.localConfig.Settings.SecondaryPort != origPort {
n.infoLog.Printf("secondary port %d unavailable, found port %d (port search enabled)", origPort, n.localConfig.Settings.SecondaryPort)
}
break
}
n.infoLog.Printf("secondary port %d unavailable, trying a random port (port search enabled)", n.localConfig.Settings.SecondaryPort)
if portCheckCount <= 64 {
n.localConfig.Settings.SecondaryPort = unassignedPrivilegedPorts[randomUInt()%uint(len(unassignedPrivilegedPorts))]
} else {
n.localConfig.Settings.SecondaryPort = int(16384 + (randomUInt() % 16384))
}
portsChanged = true
}
}
if portsChanged {
_ = n.localConfig.Write(n.localConfigPath)
}
n.namedSocketAPIServer, n.tcpAPIServer, err = createAPIServer(basePath, n)
if err != nil {
n.infoLog.Printf("FATAL: unable to start API server: %s", err.Error())
return nil, err
}
cPath := cStr(basePath)
n.gn = C.ZT_GoNode_new((*C.char)(unsafe.Pointer(&cPath[0])), C.uintptr_t(n.cPtr))
if n.gn == nil {
n.infoLog.Println("FATAL: node initialization failed")
return nil, ErrNodeInitFailed
}
n.zn = unsafe.Pointer(C.ZT_GoNode_getNode(n.gn))
n.id, err = newIdentityFromCIdentity(C.ZT_Node_identity(n.zn))
if err != nil {
n.infoLog.Printf("FATAL: error obtaining node's identity")
C.ZT_GoNode_delete(n.gn)
return nil, err
}
n.runWaitGroup.Add(1)
go func() {
defer n.runWaitGroup.Done()
lastMaintenanceRun := int64(0)
for atomic.LoadUintptr(&n.running) != 0 {
time.Sleep(250 * time.Millisecond)
nowS := time.Now().Unix()
if (nowS - lastMaintenanceRun) >= 30 {
lastMaintenanceRun = nowS
n.runMaintenance()
}
time.Sleep(250 * time.Millisecond)
}
}()
// Stop deferred cPtr table cleanup function from de-registering this instance
cPtr = -1
return n, nil
}
// Close closes this Node and frees its underlying C++ Node structures
func (n *Node) Close() {
if atomic.SwapUintptr(&n.running, 0) != 0 {
if n.namedSocketAPIServer != nil {
_ = n.namedSocketAPIServer.Close()
}
if n.tcpAPIServer != nil {
_ = n.tcpAPIServer.Close()
}
C.ZT_GoNode_delete(n.gn)
n.runWaitGroup.Wait()
cNodeRefs[n.cPtr] = nil
atomic.StoreUintptr(&cNodeRefUsed[n.cPtr], 0)
}
}
// Address returns this node's address
func (n *Node) Address() Address { return n.id.address }
// Identity returns this node's identity (including secret portion)
func (n *Node) Identity() *Identity { return n.id }
// Online returns true if this node can reach something
func (n *Node) Online() bool { return atomic.LoadUintptr(&n.online) != 0 }
// LocalInterfaceAddresses are external IPs belonging to physical interfaces on this machine
func (n *Node) LocalInterfaceAddresses() []net.IP {
n.localInterfaceAddressesLock.Lock()
defer n.localInterfaceAddressesLock.Unlock()
var ea []net.IP
for _, a := range n.localInterfaceAddresses {
ea = append(ea, a)
}
sort.Slice(ea, func(a, b int) bool { return bytes.Compare(ea[a], ea[b]) < 0 })
return ea
}
// LocalConfig gets this node's local configuration
func (n *Node) LocalConfig() *LocalConfig {
n.localConfigLock.RLock()
defer n.localConfigLock.RUnlock()
return n.localConfig
}
// SetLocalConfig updates this node's local configuration
func (n *Node) SetLocalConfig(lc *LocalConfig) (restartRequired bool, err error) {
n.networksLock.RLock()
n.localConfigLock.Lock()
defer n.localConfigLock.Unlock()
defer n.networksLock.RUnlock()
for nid, nc := range lc.Network {
nw := n.networks[nid]
if nw != nil {
nw.SetLocalSettings(&nc)
}
}
if n.localConfig.Settings.PrimaryPort != lc.Settings.PrimaryPort || n.localConfig.Settings.SecondaryPort != lc.Settings.SecondaryPort || n.localConfig.Settings.LogSizeMax != lc.Settings.LogSizeMax {
restartRequired = true
}
n.previousLocalConfig = n.localConfig
n.localConfig = lc
return
}
// Join a network.
// If tap is nil, the default system tap for this OS/platform is used (if available).
func (n *Node) Join(nwid NetworkID, controllerFingerprint *Fingerprint, settings *NetworkLocalSettings, tap Tap) (*Network, error) {
n.networksLock.RLock()
defer n.networksLock.RUnlock()
if nw, have := n.networks[nwid]; have {
n.infoLog.Printf("join network %.16x ignored: already a member", nwid)
if settings != nil {
go nw.SetLocalSettings(settings) // "go" this to avoid possible deadlocks
}
return nw, nil
}
if tap != nil {
panic("non-native taps not yet implemented")
}
var fp *C.ZT_Fingerprint
if controllerFingerprint != nil {
fp = controllerFingerprint.cFingerprint()
}
ntap := C.ZT_GoNode_join(n.gn, C.uint64_t(nwid), fp)
if ntap == nil {
n.infoLog.Printf("join network %.16x failed: tap device failed to initialize (check drivers / kernel modules)", uint64(nwid))
return nil, ErrTapInitFailed
}
nw, err := newNetwork(n, nwid, &nativeTap{tap: unsafe.Pointer(ntap), enabled: 1})
if err != nil {
n.infoLog.Printf("join network %.16x failed: network failed to initialize: %s", nwid, err.Error())
C.ZT_GoNode_leave(n.gn, C.uint64_t(nwid))
return nil, err
}
n.networks[nwid] = nw
if settings != nil {
go nw.SetLocalSettings(settings)
}
return nw, nil
}
// Leave a network.
func (n *Node) Leave(nwid NetworkID) error {
n.networksLock.Lock()
nw := n.networks[nwid]
delete(n.networks, nwid)
n.networksLock.Unlock()
if nw != nil {
n.infoLog.Printf("leaving network %.16x", nwid)
nw.leaving()
C.ZT_GoNode_leave(n.gn, C.uint64_t(nwid))
}
return nil
}
// Network looks up a network by ID or returns nil if not joined
func (n *Node) Network(nwid NetworkID) *Network {
n.networksLock.RLock()
nw := n.networks[nwid]
n.networksLock.RUnlock()
return nw
}
// Networks returns a list of networks that this node has joined
func (n *Node) Networks() []*Network {
n.networksLock.RLock()
defer n.networksLock.RUnlock()
var nws []*Network
for _, nw := range n.networks {
nws = append(nws, nw)
}
return nws
}
// Peers retrieves a list of current peers
func (n *Node) Peers() []*Peer {
var peers []*Peer
pl := C.ZT_Node_peers(n.zn)
if pl != nil {
defer C.ZT_freeQueryResult(unsafe.Pointer(pl))
for i := uintptr(0); i < uintptr(pl.peerCount); i++ {
p, _ := newPeerFromCPeer((*C.ZT_Peer)(unsafe.Pointer(uintptr(unsafe.Pointer(pl.peers)) + (i * C.sizeof_ZT_Peer))))
if p != nil {
peers = append(peers, p)
}
}
}
sort.Slice(peers, func(a, b int) bool {
return peers[a].Address < peers[b].Address
})
return peers
}
// Peer looks up a single peer by address or full fingerprint.
// The fpOrAddress parameter may be either. If it is neither nil is returned.
// A nil pointer is returned if nothing is found.
func (n *Node) Peer(fpOrAddress interface{}) *Peer {
fp, _ := fpOrAddress.(*Fingerprint)
if fp == nil {
a, _ := fpOrAddress.(*Address)
if a == nil {
return nil
}
fp = &Fingerprint{Address: *a}
}
pl := C.ZT_Node_peers(n.zn)
if pl != nil {
defer C.ZT_freeQueryResult(unsafe.Pointer(pl))
for i := uintptr(0); i < uintptr(pl.peerCount); i++ {
p, _ := newPeerFromCPeer((*C.ZT_Peer)(unsafe.Pointer(uintptr(unsafe.Pointer(pl.peers)) + (i * C.sizeof_ZT_Peer))))
if p != nil && p.Identity.Fingerprint().BestSpecificityEquals(fp) {
return p
}
}
}
return nil
}
// AddPeer adds a peer by explicit identity.
func (n *Node) AddPeer(id *Identity) error {
if id == nil {
return ErrInvalidParameter
}
rc := C.ZT_Node_addPeer(n.zn, nil, id.cIdentity())
if rc != 0 {
return ErrInvalidParameter
}
return nil
}
// TryPeer attempts to contact a peer at a given explicit endpoint.
// The peer may be identified by an Address or a full Fingerprint. Any other
// type for fpOrAddress will return false.
func (n *Node) TryPeer(fpOrAddress interface{}, ep *Endpoint, retries int) bool {
if ep == nil {
return false
}
fp, _ := fpOrAddress.(*Fingerprint)
if fp == nil {
a, _ := fpOrAddress.(*Address)
if a == nil {
return false
}
fp = &Fingerprint{Address: *a}
}
return C.ZT_Node_tryPeer(n.zn, nil, fp.cFingerprint(), &ep.cep, C.int(retries)) != 0
}
// ListCertificates lists certificates and their corresponding local trust flags.
func (n *Node) ListCertificates() (certs []LocalCertificate, err error) {
cl := C.ZT_Node_listCertificates(n.zn)
if cl != nil {
defer C.ZT_freeQueryResult(unsafe.Pointer(cl))
for i := uintptr(0); i < uintptr(cl.certCount); i++ {
c := newCertificateFromCCertificate(unsafe.Pointer(uintptr(unsafe.Pointer(cl.certs)) + (i * pointerSize)))
if c != nil {
lt := *((*C.uint)(unsafe.Pointer(uintptr(unsafe.Pointer(cl.localTrust)) + (i * C.sizeof_int))))
certs = append(certs, LocalCertificate{Certificate: c, LocalTrust: uint(lt)})
}
}
}
return
}
// AddCertificate adds a certificate to this node's local certificate store (after verification).
func (n *Node) AddCertificate(cert *Certificate, localTrust uint) error {
ccert := cert.cCertificate()
defer deleteCCertificate(ccert)
return certificateErrorToError(int(C.ZT_Node_addCertificate(n.zn, nil, C.int64_t(TimeMs()), C.uint(localTrust), (*C.ZT_Certificate)(ccert), nil, 0)))
}
// DeleteCertificate deletes a certificate from this node's local certificate store.
func (n *Node) DeleteCertificate(serialNo []byte) error {
if len(serialNo) != CertificateSerialNoSize {
return ErrInvalidParameter
}
C.ZT_Node_deleteCertificate(n.zn, nil, unsafe.Pointer(&serialNo[0]))
return nil
}
/********************************************************************************************************************/
func (n *Node) runMaintenance() {
n.localConfigLock.RLock()
defer n.localConfigLock.RUnlock()
// Get local physical interface addresses, excluding blacklisted and
// ZeroTier-created interfaces.
interfaceAddresses := make(map[string]net.IP)
ifs, _ := net.Interfaces()
if len(ifs) > 0 {
n.networksLock.RLock()
scanInterfaces:
for _, i := range ifs {
for _, bl := range n.localConfig.Settings.InterfacePrefixBlacklist {
if strings.HasPrefix(strings.ToLower(i.Name), strings.ToLower(bl)) {
continue scanInterfaces
}
}
m, _ := NewMACFromBytes(i.HardwareAddr)
if _, isZeroTier := n.networksByMAC[m]; !isZeroTier {
addrs, _ := i.Addrs()
for _, a := range addrs {
ipn, _ := a.(*net.IPNet)
if ipn != nil && len(ipn.IP) > 0 && !ipn.IP.IsLoopback() && !ipn.IP.IsMulticast() && !ipn.IP.IsInterfaceLocalMulticast() && !ipn.IP.IsLinkLocalMulticast() && !ipn.IP.IsLinkLocalUnicast() {
isTemporary := false
if len(ipn.IP) == 16 {
var ss C.struct_sockaddr_storage
if makeSockaddrStorage(ipn.IP, 0, &ss) {
cIfName := C.CString(i.Name)
if C.ZT_isTemporaryV6Address(cIfName, &ss) != 0 {
isTemporary = true
}
C.free(unsafe.Pointer(cIfName))
}
}
if !isTemporary {
interfaceAddresses[ipn.IP.String()] = ipn.IP
}
}
}
}
}
n.networksLock.RUnlock()
}
// Open or close locally bound UDP ports for each local interface address.
// This opens ports if they are not already open and then closes ports if
// they are open but no longer seem to exist.
interfaceAddressesChanged := false
ports := make([]int, 0, 2)
if n.localConfig.Settings.PrimaryPort > 0 && n.localConfig.Settings.PrimaryPort < 65536 {
ports = append(ports, n.localConfig.Settings.PrimaryPort)
}
if n.localConfig.Settings.SecondaryPort > 0 && n.localConfig.Settings.SecondaryPort < 65536 {
ports = append(ports, n.localConfig.Settings.SecondaryPort)
}
n.localInterfaceAddressesLock.Lock()
for astr, ipn := range interfaceAddresses {
if _, alreadyKnown := n.localInterfaceAddresses[astr]; !alreadyKnown {
interfaceAddressesChanged = true
ipCstr := C.CString(ipn.String())
for pn, p := range ports {
n.infoLog.Printf("UDP binding to port %d on interface %s", p, astr)
primary := C.int(0)
if pn == 0 {
primary = 1
}
C.ZT_GoNode_phyStartListen(n.gn, nil, ipCstr, C.int(p), primary)
}
C.free(unsafe.Pointer(ipCstr))
}
}
for astr, ipn := range n.localInterfaceAddresses {
if _, stillPresent := interfaceAddresses[astr]; !stillPresent {
interfaceAddressesChanged = true
ipCstr := C.CString(ipn.String())
for _, p := range ports {
n.infoLog.Printf("UDP closing socket bound to port %d on interface %s", p, astr)
C.ZT_GoNode_phyStopListen(n.gn, nil, ipCstr, C.int(p))
}
C.free(unsafe.Pointer(ipCstr))
}
}
n.localInterfaceAddresses = interfaceAddresses
n.localInterfaceAddressesLock.Unlock()
// Update node's interface address list if detected or configured addresses have changed.
if interfaceAddressesChanged || n.previousLocalConfig == nil || !reflect.DeepEqual(n.localConfig.Settings.ExplicitAddresses, n.previousLocalConfig.Settings.ExplicitAddresses) {
var cAddrs []C.ZT_InterfaceAddress
externalAddresses := make(map[[3]uint64]*InetAddress)
for _, a := range n.localConfig.Settings.ExplicitAddresses {
ak := a.key()
if _, have := externalAddresses[ak]; !have {
externalAddresses[ak] = &a
cAddrs = append(cAddrs, C.ZT_InterfaceAddress{})
makeSockaddrStorage(a.IP, a.Port, &(cAddrs[len(cAddrs)-1].address))
cAddrs[len(cAddrs)-1].permanent = 1 // explicit addresses are permanent, meaning they can be put in a locator
}
}
for _, ip := range interfaceAddresses {
for _, p := range ports {
a := InetAddress{IP: ip, Port: p}
ak := a.key()
if _, have := externalAddresses[ak]; !have {
externalAddresses[ak] = &a
cAddrs = append(cAddrs, C.ZT_InterfaceAddress{})
makeSockaddrStorage(a.IP, a.Port, &(cAddrs[len(cAddrs)-1].address))
cAddrs[len(cAddrs)-1].permanent = 0
}
}
}
if len(cAddrs) > 0 {
C.ZT_Node_setInterfaceAddresses(n.zn, &cAddrs[0], C.uint(len(cAddrs)))
} else {
C.ZT_Node_setInterfaceAddresses(n.zn, nil, 0)
}
}
// Trim infoLog if it's gone over its size limit
if n.localConfig.Settings.LogSizeMax > 0 && n.infoLogW != nil {
_ = n.infoLogW.trim(n.localConfig.Settings.LogSizeMax*1024, 0.5, true)
}
}
func (n *Node) multicastSubscribe(nwid uint64, mg *MulticastGroup) {
C.ZT_Node_multicastSubscribe(n.zn, nil, C.uint64_t(nwid), C.uint64_t(mg.MAC), C.ulong(mg.ADI))
}
func (n *Node) multicastUnsubscribe(nwid uint64, mg *MulticastGroup) {
C.ZT_Node_multicastUnsubscribe(n.zn, C.uint64_t(nwid), C.uint64_t(mg.MAC), C.ulong(mg.ADI))
}
func (n *Node) pathCheck(ip net.IP) bool {
n.localConfigLock.RLock()
defer n.localConfigLock.RUnlock()
for cidr, phy := range n.localConfig.Physical {
if phy.Blacklist {
_, ipn, _ := net.ParseCIDR(cidr)
if ipn != nil && ipn.Contains(ip) {
return false
}
}
}
return true
}
func (n *Node) pathLookup(id *Identity) (net.IP, int) {
n.localConfigLock.RLock()
defer n.localConfigLock.RUnlock()
virt := n.localConfig.Virtual[id.address]
if len(virt.Try) > 0 {
idx := rand.Int() % len(virt.Try)
return virt.Try[idx].IP, virt.Try[idx].Port
}
return nil, 0
}
func (n *Node) makeStateObjectPath(objType int, id []uint64) (string, bool) {
var fp string
secret := false
switch objType {
case C.ZT_STATE_OBJECT_IDENTITY_PUBLIC:
fp = path.Join(n.basePath, "identity.public")
case C.ZT_STATE_OBJECT_IDENTITY_SECRET:
fp = path.Join(n.basePath, "identity.secret")
secret = true
case C.ZT_STATE_OBJECT_LOCATOR:
fp = path.Join(n.basePath, "locator")
case C.ZT_STATE_OBJECT_PEER:
_ = os.Mkdir(n.peersPath, 0700)
fp = path.Join(n.peersPath, fmt.Sprintf("%.10x.peer", id[0]))
secret = true
case C.ZT_STATE_OBJECT_NETWORK_CONFIG:
_ = os.Mkdir(n.networksPath, 0755)
fp = path.Join(n.networksPath, fmt.Sprintf("%.16x.conf", id[0]))
case C.ZT_STATE_OBJECT_TRUST_STORE:
fp = path.Join(n.basePath, "truststore")
case C.ZT_STATE_OBJECT_CERT:
_ = os.Mkdir(n.certsPath, 0755)
fp = path.Join(n.certsPath, Base32.EncodeToString((*[48]byte)(unsafe.Pointer(&id[0]))[:]))
}
return fp, secret
}
func (n *Node) stateObjectPut(objType int, id []uint64, data []byte) {
fp, secret := n.makeStateObjectPath(objType, id)
if len(fp) > 0 {
fileMode := os.FileMode(0644)
if secret {
fileMode = os.FileMode(0600)
}
_ = ioutil.WriteFile(fp, data, fileMode)
if secret {
_ = acl.Chmod(fp, 0600) // this emulates Unix chmod on Windows and uses os.Chmod on Unix-type systems
}
}
}
func (n *Node) stateObjectDelete(objType int, id []uint64) {
fp, _ := n.makeStateObjectPath(objType, id)
if len(fp) > 0 {
_ = os.Remove(fp)
}
}
func (n *Node) stateObjectGet(objType int, id []uint64) ([]byte, bool) {
fp, _ := n.makeStateObjectPath(objType, id)
if len(fp) > 0 {
fd, err := ioutil.ReadFile(fp)
if err != nil {
return nil, false
}
return fd, true
}
return nil, false
}
func (n *Node) handleTrace(traceMessage string) {
if len(traceMessage) > 0 {
n.infoLog.Print("TRACE: " + traceMessage)
}
}
/********************************************************************************************************************/
// These are callbacks called by the core and GoGlue stuff to talk to the
// service. These launch goroutines to do their work where possible to
// avoid blocking anything in the core.
//export goPathCheckFunc
func goPathCheckFunc(gn, uptr unsafe.Pointer, af C.int, ip unsafe.Pointer, port C.int) C.int {
node := cNodeRefs[uintptr(gn)]
if node == nil {
return 0
}
var nip net.IP
if af == syscall.AF_INET {
nip = ((*[4]byte)(ip))[:]
} else if af == syscall.AF_INET6 {
nip = ((*[16]byte)(ip))[:]
} else {
return 0
}
if len(nip) > 0 && node.pathCheck(nip) {
return 1
}
return 0
}
//export goPathLookupFunc
func goPathLookupFunc(gn unsafe.Pointer, ztAddress C.uint64_t, desiredAddressFamily int, identity, familyP, ipP, portP unsafe.Pointer) C.int {
node := cNodeRefs[uintptr(gn)]
if node == nil {
return 0
}
id, err := newIdentityFromCIdentity(identity)
if err != nil {
return 0
}
ip, port := node.pathLookup(id)
if len(ip) > 0 && port > 0 && port <= 65535 {
ip4 := ip.To4()
if len(ip4) == 4 {
*((*C.int)(familyP)) = C.int(syscall.AF_INET)
copy((*[4]byte)(ipP)[:], ip4)
*((*C.int)(portP)) = C.int(port)
return 1
} else if len(ip) == 16 {
*((*C.int)(familyP)) = C.int(syscall.AF_INET6)
copy((*[16]byte)(ipP)[:], ip)
*((*C.int)(portP)) = C.int(port)
return 1
}
}
return 0
}
//export goStateObjectPutFunc
func goStateObjectPutFunc(gn unsafe.Pointer, objType C.int, id, data unsafe.Pointer, len C.int) {
node := cNodeRefs[uintptr(gn)]
if node == nil {
return
}
// NOTE: this is unsafe and depends on node.stateObjectDelete() and node.stateObjectPut()
// not accessing beyond the expected number of elements in the id.
id2 := (*[6]uint64)(id)
var data2 []byte
if len > 0 {
data2 = C.GoBytes(data, len)
}
if len < 0 {
node.stateObjectDelete(int(objType), id2[:])
} else {
node.stateObjectPut(int(objType), id2[:], data2)
}
}
//export goStateObjectGetFunc
func goStateObjectGetFunc(gn unsafe.Pointer, objType C.int, id, dataP unsafe.Pointer) C.int {
node := cNodeRefs[uintptr(gn)]
if node == nil {
return -1
}
*((*uintptr)(dataP)) = 0
tmp, found := node.stateObjectGet(int(objType), ((*[6]uint64)(id))[:])
if found && len(tmp) > 0 {
cData := C.ZT_malloc(C.ulong(len(tmp))) // GoGlue sends free() to the core as the free function
if uintptr(cData) == 0 {
return -1
}
*((*uintptr)(dataP)) = uintptr(cData)
return C.int(len(tmp))
}
return -1
}
//export goVirtualNetworkConfigFunc
func goVirtualNetworkConfigFunc(gn, uptr unsafe.Pointer, nwid C.uint64_t, op C.int, conf unsafe.Pointer) {
node := cNodeRefs[uintptr(gn)]
if node == nil {
return
}
node.networksLock.RLock()
network := node.networks[NetworkID(nwid)]
node.networksLock.RUnlock()
if network != nil {
switch int(op) {
case networkConfigOpUp, networkConfigOpUpdate:
ncc := (*C.ZT_VirtualNetworkConfig)(conf)
if network.networkConfigRevision() > uint64(ncc.netconfRevision) {
return
}
var nc NetworkConfig
nc.ID = NetworkID(ncc.nwid)
nc.MAC = MAC(ncc.mac)
nc.Name = C.GoString(&ncc.name[0])
nc.Status = int(ncc.status)
nc.Type = int(ncc._type)
nc.MTU = int(ncc.mtu)
nc.Bridge = ncc.bridge != 0
nc.BroadcastEnabled = ncc.broadcastEnabled != 0
nc.NetconfRevision = uint64(ncc.netconfRevision)
for i := 0; i < int(ncc.assignedAddressCount); i++ {
a := sockaddrStorageToIPNet(&ncc.assignedAddresses[i])
if a != nil {
_, bits := a.Mask.Size()
nc.AssignedAddresses = append(nc.AssignedAddresses, InetAddress{IP: a.IP, Port: bits})
}
}
for i := 0; i < int(ncc.routeCount); i++ {
tgt := sockaddrStorageToIPNet(&ncc.routes[i].target)
viaN := sockaddrStorageToIPNet(&ncc.routes[i].via)
var via *net.IP
if viaN != nil && len(viaN.IP) > 0 {
via = &viaN.IP
}
if tgt != nil {
nc.Routes = append(nc.Routes, Route{
Target: *tgt,
Via: via,
Flags: uint16(ncc.routes[i].flags),
Metric: uint16(ncc.routes[i].metric),
})
}
}
node.runWaitGroup.Add(1)
go func() {
network.updateConfig(&nc, nil)
node.runWaitGroup.Done()
}()
}
}
}
//export goZtEvent
func goZtEvent(gn unsafe.Pointer, eventType C.int, data unsafe.Pointer) {
node := cNodeRefs[uintptr(gn)]
if node == nil {
return
}
switch eventType {
case C.ZT_EVENT_OFFLINE:
atomic.StoreUintptr(&node.online, 0)
case C.ZT_EVENT_ONLINE:
atomic.StoreUintptr(&node.online, 1)
}
}

View file

@ -1,45 +0,0 @@
// +build !windows
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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 (
"context"
"net"
"net/http"
"os"
"path"
"time"
)
func createNamedSocketListener(basePath, name string) (net.Listener, error) {
apiSockPath := path.Join(basePath, name)
_ = os.Remove(apiSockPath)
return net.Listen("unix", apiSockPath)
}
func createNamedSocketHTTPClient(basePath, name string) (*http.Client, error) {
apiSockPath := path.Join(basePath, name)
return &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", apiSockPath)
},
DisableKeepAlives: true,
DisableCompression: true,
},
}, nil
}

View file

@ -1,34 +0,0 @@
// +build windows
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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"
"net/http"
winio "github.com/Microsoft/go-winio"
)
const windowsAPISocketPathPrefix = "\\\\.\\pipe\\zerotier_"
func createNamedSocketListener(basePath, name string) (net.Listener, error) {
return winio.ListenPipe(windowsAPISocketPathPrefix+name, nil)
}
func createNamedSocketHTTPClient(basePath, name string) (*http.Client, error) {
panic("needs implementation")
return nil, nil
}

View file

@ -1,30 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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
// #include "../../serviceiocore/GoGlue.h"
import "C"
// Path is a path to another peer on the network
type Path struct {
Endpoint Endpoint `json:"endpoint"`
LastSend int64 `json:"lastSend"`
LastReceive int64 `json:"lastReceive"`
}
func (p *Path) setFromCPath(cp *C.ZT_Path) {
p.Endpoint.setFromCEndpoint(&(cp.endpoint))
p.LastSend = int64(cp.lastSend)
p.LastReceive = int64(cp.lastReceive)
}

View file

@ -1,52 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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
// #include "../../serviceiocore/GoGlue.h"
import "C"
import "unsafe"
// Peer is another ZeroTier node
type Peer struct {
Address Address `json:"address"`
Identity *Identity `json:"identity"`
Fingerprint *Fingerprint `json:"fingerprint"`
Version [3]int `json:"version"`
Latency int `json:"latency"`
Root bool `json:"root"`
Paths []Path `json:"paths,omitempty"`
Locator *Locator `json:"locator,omitempty"`
}
func newPeerFromCPeer(cp *C.ZT_Peer) (p *Peer, err error) {
p = new(Peer)
p.Address = Address(cp.address)
p.Identity, err = newIdentityFromCIdentity(cp.identity)
if err != nil {
return
}
p.Fingerprint = newFingerprintFromCFingerprint(cp.fingerprint)
p.Version[0] = int(cp.versionMajor)
p.Version[1] = int(cp.versionMinor)
p.Version[2] = int(cp.versionRev)
p.Latency = int(cp.latency)
p.Root = cp.root != 0
p.Paths = make([]Path, int(cp.pathCount))
for i := range p.Paths {
p.Paths[i].setFromCPath((*C.ZT_Path)(unsafe.Pointer(uintptr(unsafe.Pointer(cp.paths)) + (uintptr(C.sizeof_ZT_Path) * uintptr(i)))))
}
p.Locator, err = NewLocatorFromBytes(C.GoBytes(unsafe.Pointer(cp.locator), C.int(cp.locatorSize)))
return
}

View file

@ -1,54 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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"
"unsafe"
)
// Route represents a route in a host's routing table
type Route struct {
// Target for this route
Target net.IPNet `json:"target"`
// Via is how to reach this target (null/empty if the target IP range is local to this virtual LAN)
Via *net.IP `json:"via,omitempty"`
// Route flags (currently unused, always 0)
Flags uint16 `json:"flags"`
// Metric is an interface metric that can affect route priority (behavior can be OS-specific)
Metric uint16 `json:"metric"`
}
// String returns a string representation of this route
func (r *Route) String() string {
if r.Via != nil {
return r.Target.String() + "->LAN"
}
return r.Target.String() + "->" + r.Via.String()
}
// key generates a key suitable for a map[] from this route
func (r *Route) key() (k [6]uint64) {
copy(((*[16]byte)(unsafe.Pointer(&k[0])))[:], r.Target.IP)
ones, bits := r.Target.Mask.Size()
k[2] = (uint64(ones) << 32) | uint64(bits)
if r.Via != nil {
copy(((*[16]byte)(unsafe.Pointer(&k[3])))[:], *r.Via)
}
k[5] = (uint64(r.Flags) << 32) | uint64(r.Metric)
return
}

View file

@ -1,109 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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 (
"io"
"os"
"sync"
)
type sizeLimitWriter struct {
f *os.File
l sync.Mutex
}
func sizeLimitWriterOpen(p string) (*sizeLimitWriter, error) {
f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
return nil, err
}
_, _ = f.Seek(0, io.SeekEnd)
return &sizeLimitWriter{f: f}, nil
}
// Write implements io.Writer
func (w *sizeLimitWriter) Write(b []byte) (int, error) {
w.l.Lock()
defer w.l.Unlock()
return w.f.Write(b)
}
// Close closes the underlying file
func (w *sizeLimitWriter) Close() error {
w.l.Lock()
defer w.l.Unlock()
return w.f.Close()
}
func (w *sizeLimitWriter) trim(maxSize int, trimFactor float64, trimAtCR bool) error {
w.l.Lock()
defer w.l.Unlock()
flen, err := w.f.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if flen > int64(maxSize) {
var buf [131072]byte
trimAt := int64(float64(maxSize) * trimFactor)
if trimAt >= flen { // sanity check
return nil
}
if trimAtCR {
lookForCR:
for {
nr, err := w.f.ReadAt(buf[0:1024], trimAt)
if err != nil {
return err
}
for i := 0; i < nr; i++ {
trimAt++
if buf[i] == byte('\n') {
break lookForCR
}
}
if trimAt >= flen {
return nil
}
}
}
copyTo := int64(0)
for trimAt < flen {
nr, _ := w.f.ReadAt(buf[:], trimAt)
if nr > 0 {
wr, _ := w.f.WriteAt(buf[0:nr], copyTo)
if wr > 0 {
copyTo += int64(wr)
} else {
break
}
} else {
break
}
}
err = w.f.Truncate(copyTo)
if err != nil {
return err
}
_, err = w.f.Seek(0, io.SeekEnd)
return err
}
return nil
}

View file

@ -1,49 +0,0 @@
/*
* Copyright (C)2013-2020 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-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"
// Tap represents an Ethernet tap connecting a virtual network to a device or something else "real"
type Tap interface {
// Close is called when this tap is being shut down
Close()
// Type is a string describing what kind of tap this is e.g. "native" for OS-native
Type() string
// Error returns the most recent error experienced by this tap
Error() (int, string)
// SetEnabled sets whether this tap will accept and process packets
SetEnabled(enabled bool)
// Enabled returns the current enabled status
Enabled() bool
// AddIP assigns an IP address to this tap device
AddIP(ip *InetAddress) error
// RemoveIP removes an IP address from this tap
RemoveIP(ip *InetAddress) error
// IPs returns an array of all IPs currently assigned to this tap including those not assigned by ZeroTier
IPs() ([]net.IPNet, error)
// DeviceName gets the OS-specific device name for this tap or an empty string if none
DeviceName() string
// AddMulticastGroupChangeHandler registers a function to be called on multicast group subscribe or unsubscribe (first argument)
AddMulticastGroupChangeHandler(func(bool, *MulticastGroup))
}

View file

@ -1,18 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(zt_service_io_core)
set(src
GoGlue.cpp
)
set(headers
GoGlue.h
)
add_library(${PROJECT_NAME} STATIC ${src} ${headers})
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_11)
target_include_directories(
${PROJECT_NAME}
PUBLIC
${CMAKE_BINARY_DIR}/core
)

View file

@ -1,738 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-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.
*/
/****/
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x06010000
#endif
#include "GoGlue.h"
#include "../core/Constants.hpp"
#include "../core/InetAddress.hpp"
#include "../core/Node.hpp"
#include "../core/Utils.hpp"
#include "../core/MAC.hpp"
#include "../core/Address.hpp"
#include "../core/Containers.hpp"
#include "../core/Certificate.hpp"
#include "../osdep/OSUtils.hpp"
#include "../osdep/EthernetTap.hpp"
#ifndef __WINDOWS__
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#ifdef __BSD__
#include <netinet6/in6_var.h>
#endif
#include <arpa/inet.h>
#include <errno.h>
#ifdef __LINUX__
#ifndef IPV6_DONTFRAG
#define IPV6_DONTFRAG 62
#endif
#endif
#endif // !__WINDOWS__
#include <thread>
#include <mutex>
#include <memory>
#include <atomic>
#ifdef __WINDOWS__
#define SETSOCKOPT_FLAG_TYPE BOOL
#define SETSOCKOPT_FLAG_TRUE TRUE
#define SETSOCKOPT_FLAG_FALSE FALSE
#else
#define SETSOCKOPT_FLAG_TYPE int
#define SETSOCKOPT_FLAG_TRUE 1
#define SETSOCKOPT_FLAG_FALSE 0
#endif
#ifndef MSG_DONTWAIT
#define MSG_DONTWAIT 0
#endif
using namespace ZeroTier;
struct ZT_GoNodeThread
{
String ip;
int port;
int af;
bool primary;
std::atomic< bool > run;
std::thread thr;
};
struct ZT_GoNode_Impl
{
void *goUserPtr;
Node *node;
volatile int64_t nextBackgroundTaskDeadline;
String path;
std::atomic< bool > run;
Map< ZT_SOCKET, ZT_GoNodeThread > threads;
Map< uint64_t, std::shared_ptr< EthernetTap > > taps;
std::mutex threads_l;
std::mutex taps_l;
std::thread backgroundTaskThread;
};
static const String defaultHomePath(OSUtils::platformDefaultHomePath());
const char *const ZT_PLATFORM_DEFAULT_HOMEPATH = defaultHomePath.c_str();
// These are implemented in Go code.
extern "C" int goPathCheckFunc(void *, const ZT_Identity *, int, const void *, int);
extern "C" int goPathLookupFunc(void *, uint64_t, int, const ZT_Identity *, int *, uint8_t [16], int *);
extern "C" void goStateObjectPutFunc(void *, int, const uint64_t *, const void *, int);
extern "C" int goStateObjectGetFunc(void *, int, const uint64_t *, void **);
extern "C" void goVirtualNetworkConfigFunc(void *, ZT_GoTap *, uint64_t, int, const ZT_VirtualNetworkConfig *);
extern "C" void goZtEvent(void *, int, const void *);
extern "C" void goHandleTapAddedMulticastGroup(void *, ZT_GoTap *, uint64_t, uint64_t, uint32_t);
extern "C" void goHandleTapRemovedMulticastGroup(void *, ZT_GoTap *, uint64_t, uint64_t, uint32_t);
static void ZT_GoNode_VirtualNetworkConfigFunction(
ZT_Node *node,
void *uptr,
void *tptr,
uint64_t nwid,
void **nptr,
enum ZT_VirtualNetworkConfigOperation op,
const ZT_VirtualNetworkConfig *cfg)
{
goVirtualNetworkConfigFunc(reinterpret_cast<ZT_GoNode *>(uptr)->goUserPtr, reinterpret_cast<ZT_GoTap *>(*nptr), nwid, op, cfg);
}
static void ZT_GoNode_VirtualNetworkFrameFunction(
ZT_Node *node,
void *uptr,
void *tptr,
uint64_t nwid,
void **nptr,
uint64_t srcMac,
uint64_t destMac,
unsigned int etherType,
unsigned int vlanId,
const void *data,
unsigned int len)
{
if (*nptr)
reinterpret_cast<EthernetTap *>(*nptr)->put(MAC(srcMac), MAC(destMac), etherType, data, len);
}
static void ZT_GoNode_EventCallback(
ZT_Node *node,
void *uptr,
void *tptr,
enum ZT_Event et,
const void *data)
{
goZtEvent(reinterpret_cast<ZT_GoNode *>(uptr)->goUserPtr, et, data);
}
static void ZT_GoNode_StatePutFunction(
ZT_Node *node,
void *uptr,
void *tptr,
enum ZT_StateObjectType objType,
const uint64_t id[2],
const void *data,
int len)
{
goStateObjectPutFunc(
reinterpret_cast<ZT_GoNode *>(uptr)->goUserPtr,
objType,
id,
data,
len);
}
static void _freeFunc(void *p)
{ if (p) free(p); }
static int ZT_GoNode_StateGetFunction(
ZT_Node *node,
void *uptr,
void *tptr,
enum ZT_StateObjectType objType,
const uint64_t id[2],
void **data,
void (**freeFunc)(void *))
{
*freeFunc = &_freeFunc;
return goStateObjectGetFunc(
reinterpret_cast<ZT_GoNode *>(uptr)->goUserPtr,
(int)objType,
id,
data);
}
static ZT_INLINE void doUdpSend(ZT_SOCKET sock, const struct sockaddr_storage *addr, const void *data, const unsigned int len, const unsigned int ipTTL)
{
switch (addr->ss_family) {
case AF_INET:
if (unlikely((ipTTL > 0) && (ipTTL < 255))) {
#ifdef __WINDOWS__
DWORD tmp = (DWORD)ipTTL;
#else
int tmp = (int)ipTTL;
#endif
setsockopt(sock, IPPROTO_IP, IP_TTL, reinterpret_cast<const char *>(&tmp), sizeof(tmp));
sendto(sock, reinterpret_cast<const char *>(data), len, MSG_DONTWAIT, (const sockaddr *)addr, sizeof(struct sockaddr_in));
tmp = 255;
setsockopt(sock, IPPROTO_IP, IP_TTL, reinterpret_cast<const char *>(&tmp), sizeof(tmp));
} else {
sendto(sock, reinterpret_cast<const char *>(data), len, MSG_DONTWAIT, (const sockaddr *)addr, sizeof(struct sockaddr_in));
}
break;
case AF_INET6:
// The ipTTL option isn't currently used with IPv6. It's only used
// with IPv4 "firewall opener" / "NAT buster" preamble packets as part
// of IPv4 NAT traversal.
sendto(sock, reinterpret_cast<const char *>(data), len, MSG_DONTWAIT, (const sockaddr *)addr, sizeof(struct sockaddr_in6));
break;
}
}
static int ZT_GoNode_WirePacketSendFunction(
ZT_Node *node,
void *uptr,
void *tptr,
int64_t localSocket,
const struct sockaddr_storage *addr,
const void *data,
unsigned int len,
unsigned int ipTTL)
{
if (likely(localSocket > 0)) {
doUdpSend((ZT_SOCKET)localSocket, addr, data, len, ipTTL);
} else {
ZT_GoNode *const gn = reinterpret_cast<ZT_GoNode *>(uptr);
std::lock_guard< std::mutex > l(gn->threads_l);
for (auto t = gn->threads.begin(); t != gn->threads.end(); ++t) {
if ((t->second.af == addr->ss_family) && (t->second.primary)) {
doUdpSend(t->first, addr, data, len, ipTTL);
break;
}
}
}
return 0;
}
static int ZT_GoNode_PathCheckFunction(
ZT_Node *node,
void *uptr,
void *tptr,
uint64_t ztAddress,
const ZT_Identity *id,
int64_t localSocket,
const struct sockaddr_storage *sa)
{
switch (sa->ss_family) {
case AF_INET:
return goPathCheckFunc(
reinterpret_cast<ZT_GoNode *>(uptr)->goUserPtr,
id,
AF_INET,
&(reinterpret_cast<const struct sockaddr_in *>(sa)->sin_addr.s_addr),
Utils::ntoh((uint16_t)reinterpret_cast<const struct sockaddr_in *>(sa)->sin_port));
case AF_INET6:
return goPathCheckFunc(
reinterpret_cast<ZT_GoNode *>(uptr)->goUserPtr,
id,
AF_INET6,
reinterpret_cast<const struct sockaddr_in6 *>(sa)->sin6_addr.s6_addr,
Utils::ntoh((uint16_t)reinterpret_cast<const struct sockaddr_in6 *>(sa)->sin6_port));
}
return 0;
}
static int ZT_GoNode_PathLookupFunction(
ZT_Node *node,
void *uptr,
void *tptr,
uint64_t ztAddress,
const ZT_Identity *id,
int desiredAddressFamily,
struct sockaddr_storage *sa)
{
int family = 0;
uint8_t ip[16];
int port = 0;
const int result = goPathLookupFunc(
reinterpret_cast<ZT_GoNode *>(uptr)->goUserPtr,
ztAddress,
desiredAddressFamily,
id,
&family,
ip,
&port
);
if (result != 0) {
switch (family) {
case AF_INET:
reinterpret_cast<struct sockaddr_in *>(sa)->sin_family = AF_INET;
memcpy(&(reinterpret_cast<struct sockaddr_in *>(sa)->sin_addr.s_addr), ip, 4);
reinterpret_cast<struct sockaddr_in *>(sa)->sin_port = Utils::hton((uint16_t)port);
return 1;
case AF_INET6:
reinterpret_cast<struct sockaddr_in6 *>(sa)->sin6_family = AF_INET6;
memcpy(reinterpret_cast<struct sockaddr_in6 *>(sa)->sin6_addr.s6_addr, ip, 16);
reinterpret_cast<struct sockaddr_in6 *>(sa)->sin6_port = Utils::hton((uint16_t)port);
return 1;
}
}
return 0;
}
extern "C" ZT_GoNode *ZT_GoNode_new(const char *workingPath, uintptr_t userPtr)
{
try {
struct ZT_Node_Callbacks cb;
cb.statePutFunction = &ZT_GoNode_StatePutFunction;
cb.stateGetFunction = &ZT_GoNode_StateGetFunction;
cb.wirePacketSendFunction = &ZT_GoNode_WirePacketSendFunction;
cb.virtualNetworkFrameFunction = &ZT_GoNode_VirtualNetworkFrameFunction;
cb.virtualNetworkConfigFunction = &ZT_GoNode_VirtualNetworkConfigFunction;
cb.eventCallback = &ZT_GoNode_EventCallback;
cb.pathCheckFunction = &ZT_GoNode_PathCheckFunction;
cb.pathLookupFunction = &ZT_GoNode_PathLookupFunction;
ZT_GoNode_Impl *gn = new ZT_GoNode_Impl;
const int64_t now = OSUtils::now();
gn->goUserPtr = reinterpret_cast<void *>(userPtr);
gn->node = new Node(reinterpret_cast<void *>(gn), nullptr, &cb, now);
gn->nextBackgroundTaskDeadline = now;
gn->path = workingPath;
gn->run = true;
gn->backgroundTaskThread = std::thread([gn] {
int64_t lastCheckedTaps = 0;
while (gn->run) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
const int64_t now = OSUtils::now();
if (now >= gn->nextBackgroundTaskDeadline)
gn->node->processBackgroundTasks(nullptr, now, &(gn->nextBackgroundTaskDeadline));
if ((now - lastCheckedTaps) > 10000) {
lastCheckedTaps = now;
std::vector< MulticastGroup > added, removed;
std::lock_guard< std::mutex > tl(gn->taps_l);
for (auto t = gn->taps.begin(); t != gn->taps.end(); ++t) {
added.clear();
removed.clear();
t->second->scanMulticastGroups(added, removed);
for (auto g = added.begin(); g != added.end(); ++g)
goHandleTapAddedMulticastGroup(gn, (ZT_GoTap *)t->second.get(), t->first, g->mac().toInt(), g->adi());
for (auto g = removed.begin(); g != removed.end(); ++g)
goHandleTapRemovedMulticastGroup(gn, (ZT_GoTap *)t->second.get(), t->first, g->mac().toInt(), g->adi());
}
}
}
});
return gn;
} catch (...) {
fprintf(stderr, "FATAL: unable to create new instance of Node (out of memory?)" ZT_EOL_S);
exit(1);
}
}
extern "C" void ZT_GoNode_delete(ZT_GoNode *gn)
{
gn->run = false;
gn->threads_l.lock();
for (auto t = gn->threads.begin(); t != gn->threads.end(); ++t) {
t->second.run = false;
#ifdef __WINDOWS__
shutdown(t->first, SD_BOTH);
closesocket(t->first);
#else
shutdown(t->first, SHUT_RDWR);
close(t->first);
#endif
t->second.thr.join();
}
gn->threads_l.unlock();
gn->taps_l.lock();
for (auto t = gn->taps.begin(); t != gn->taps.end(); ++t)
gn->node->leave(t->first, nullptr, nullptr);
gn->taps.clear();
gn->taps_l.unlock();
gn->backgroundTaskThread.join();
gn->node->shutdown(nullptr);
delete gn->node;
delete gn;
}
extern "C" ZT_Node *ZT_GoNode_getNode(ZT_GoNode *gn)
{
return gn->node;
}
static void setCommonUdpSocketSettings(ZT_SOCKET udpSock, const char *dev)
{
int bufSize = 1048576;
while (bufSize > 131072) {
if (setsockopt(udpSock, SOL_SOCKET, SO_RCVBUF, (const char *)&bufSize, sizeof(bufSize)) == 0)
break;
bufSize -= 131072;
}
bufSize = 1048576;
while (bufSize > 131072) {
if (setsockopt(udpSock, SOL_SOCKET, SO_SNDBUF, (const char *)&bufSize, sizeof(bufSize)) == 0)
break;
bufSize -= 131072;
}
SETSOCKOPT_FLAG_TYPE fl;
#ifdef SO_REUSEPORT
fl = SETSOCKOPT_FLAG_TRUE;
setsockopt(udpSock, SOL_SOCKET, SO_REUSEPORT, &fl, sizeof(fl));
#endif
#ifndef __LINUX__ // linux wants just SO_REUSEPORT
fl = SETSOCKOPT_FLAG_TRUE;
setsockopt(udpSock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&fl), sizeof(fl));
#endif
fl = SETSOCKOPT_FLAG_TRUE;
setsockopt(udpSock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char *>(&fl), sizeof(fl));
#ifdef IP_DONTFRAG
fl = SETSOCKOPT_FLAG_FALSE;
setsockopt(udpSock,IPPROTO_IP,IP_DONTFRAG,&fl,sizeof(fl));
#endif
#ifdef IP_MTU_DISCOVER
fl = SETSOCKOPT_FLAG_FALSE;
setsockopt(udpSock,IPPROTO_IP,IP_MTU_DISCOVER,&fl,sizeof(fl));
#endif
#ifdef SO_BINDTODEVICE
if ((dev)&&(strlen(dev)))
setsockopt(udpSock,SOL_SOCKET,SO_BINDTODEVICE,dev,strlen(dev));
#endif
#if defined(__BSD__) && defined(IP_BOUND_IF)
if ((dev) && (strlen(dev))) {
int idx = if_nametoindex(dev);
if (idx != 0)
setsockopt(udpSock, IPPROTO_IP, IP_BOUND_IF, (void *)&idx, sizeof(idx));
}
#endif
}
extern "C" int ZT_GoNode_phyStartListen(ZT_GoNode *gn, const char *dev, const char *ip, const int port, const int primary)
{
if (strchr(ip, ':')) {
struct sockaddr_in6 in6;
memset(&in6, 0, sizeof(in6));
in6.sin6_family = AF_INET6;
if (inet_pton(AF_INET6, ip, &(in6.sin6_addr)) <= 0)
return errno;
in6.sin6_port = htons((uint16_t)port);
ZT_SOCKET udpSock = socket(AF_INET6, SOCK_DGRAM, 0);
if (udpSock == ZT_INVALID_SOCKET)
return errno;
setCommonUdpSocketSettings(udpSock, dev);
SETSOCKOPT_FLAG_TYPE fl = SETSOCKOPT_FLAG_TRUE;
setsockopt(udpSock, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&fl, sizeof(fl));
#ifdef IPV6_DONTFRAG
fl = SETSOCKOPT_FLAG_FALSE;
setsockopt(udpSock, IPPROTO_IPV6, IPV6_DONTFRAG, reinterpret_cast<const char *>(&fl), sizeof(fl));
#endif
if (bind(udpSock, reinterpret_cast<const struct sockaddr *>(&in6), sizeof(in6)) != 0)
return errno;
{
std::lock_guard< std::mutex > l(gn->threads_l);
ZT_GoNodeThread &gnt = gn->threads[udpSock];
gnt.ip = ip;
gnt.port = port;
gnt.af = AF_INET6;
gnt.primary = (primary != 0);
gnt.run = true;
gnt.thr = std::thread([udpSock, gn, &gnt] {
struct sockaddr_in6 in6;
socklen_t salen;
while (gnt.run) {
salen = sizeof(in6);
void *buf = ZT_getBuffer();
if (buf) {
int s = (int)recvfrom(udpSock, reinterpret_cast<char *>(buf), 16384, 0, reinterpret_cast<struct sockaddr *>(&in6), &salen);
if (s > 0) {
ZT_Node_processWirePacket(
reinterpret_cast<ZT_Node *>(gn->node),
nullptr,
OSUtils::now(),
(int64_t)udpSock,
reinterpret_cast<const struct sockaddr_storage *>(&in6),
buf,
(unsigned int)s,
1,
&(gn->nextBackgroundTaskDeadline));
} else {
ZT_freeBuffer(buf);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
}
});
}
} else {
struct sockaddr_in in;
memset(&in, 0, sizeof(in));
in.sin_family = AF_INET;
if (inet_pton(AF_INET, ip, &(in.sin_addr)) <= 0)
return errno;
in.sin_port = htons((uint16_t)port);
ZT_SOCKET udpSock = socket(AF_INET, SOCK_DGRAM, 0);
if (udpSock == ZT_INVALID_SOCKET)
return errno;
setCommonUdpSocketSettings(udpSock, dev);
#ifdef SO_NO_CHECK
SETSOCKOPT_FLAG_TYPE fl = SETSOCKOPT_FLAG_TRUE;
setsockopt(udpSock,SOL_SOCKET,SO_NO_CHECK,&fl,sizeof(fl));
#endif
if (bind(udpSock, reinterpret_cast<const struct sockaddr *>(&in), sizeof(in)) != 0)
return errno;
{
std::lock_guard< std::mutex > l(gn->threads_l);
ZT_GoNodeThread &gnt = gn->threads[udpSock];
gnt.ip = ip;
gnt.port = port;
gnt.af = AF_INET6;
gnt.primary = (primary != 0);
gnt.run = true;
gnt.thr = std::thread([udpSock, gn, &gnt] {
struct sockaddr_in in4;
socklen_t salen;
while (gnt.run) {
salen = sizeof(in4);
void *buf = ZT_getBuffer();
if (buf) {
int s = (int)recvfrom(udpSock, reinterpret_cast<char *>(buf), sizeof(buf), 0, reinterpret_cast<struct sockaddr *>(&in4), &salen);
if (s > 0) {
ZT_Node_processWirePacket(
reinterpret_cast<ZT_Node *>(gn->node),
nullptr,
OSUtils::now(),
(int64_t)udpSock,
reinterpret_cast<const struct sockaddr_storage *>(&in4),
buf,
(unsigned int)s,
1,
&(gn->nextBackgroundTaskDeadline));
} else {
ZT_freeBuffer(buf);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
}
});
}
}
return 0;
}
extern "C" int ZT_GoNode_phyStopListen(ZT_GoNode *gn, const char *dev, const char *ip, const int port)
{
{
std::lock_guard< std::mutex > l(gn->threads_l);
for (auto t = gn->threads.begin(); t != gn->threads.end();) {
if ((t->second.ip == ip) && (t->second.port == port)) {
t->second.run = false;
#ifdef __WINDOWS__
shutdown(t->first, SD_BOTH);
closesocket(t->first);
#else
shutdown(t->first, SHUT_RDWR);
close(t->first);
#endif
t->second.thr.join();
gn->threads.erase(t++);
} else ++t;
}
}
return 0;
}
static void tapFrameHandler(void *uptr, void *tptr, uint64_t nwid, const MAC &from, const MAC &to, unsigned int etherType, unsigned int vlanId, const void *data, unsigned int len)
{
ZT_Node_processVirtualNetworkFrame(
reinterpret_cast<ZT_Node *>(reinterpret_cast<ZT_GoNode *>(uptr)->node),
tptr,
OSUtils::now(),
nwid,
from.toInt(),
to.toInt(),
etherType,
vlanId,
data,
len,
0,
&(reinterpret_cast<ZT_GoNode *>(uptr)->nextBackgroundTaskDeadline));
}
extern "C" ZT_GoTap *ZT_GoNode_join(ZT_GoNode *gn, uint64_t nwid, const ZT_Fingerprint *const controllerFingerprint)
{
try {
std::lock_guard< std::mutex > l(gn->taps_l);
auto existingTap = gn->taps.find(nwid);
if (existingTap != gn->taps.end())
return (ZT_GoTap *)existingTap->second.get();
char tmp[256];
OSUtils::ztsnprintf(tmp, sizeof(tmp), "ZeroTier Network %.16llx", (unsigned long long)nwid);
std::shared_ptr< EthernetTap > tap(EthernetTap::newInstance(nullptr, gn->path.c_str(), MAC(Address(gn->node->address()), nwid), ZT_DEFAULT_MTU, 0, nwid, tmp, &tapFrameHandler, gn));
if (!tap)
return nullptr;
gn->taps[nwid] = tap;
gn->node->join(nwid, controllerFingerprint, tap.get(), nullptr);
return (ZT_GoTap *)tap.get();
} catch (...) {
return nullptr;
}
}
extern "C" void ZT_GoNode_leave(ZT_GoNode *gn, uint64_t nwid)
{
std::lock_guard< std::mutex > l(gn->taps_l);
auto existingTap = gn->taps.find(nwid);
if (existingTap != gn->taps.end()) {
gn->node->leave(nwid, nullptr, nullptr);
gn->taps.erase(existingTap);
}
}
extern "C" void ZT_GoTap_setEnabled(ZT_GoTap *tap, int enabled)
{
reinterpret_cast<EthernetTap *>(tap)->setEnabled(enabled != 0);
}
extern "C" int ZT_GoTap_addIp(ZT_GoTap *tap, int af, const void *ip, int netmaskBits)
{
switch (af) {
case AF_INET:
return (reinterpret_cast<EthernetTap *>(tap)->addIp(InetAddress(ip, 4, (unsigned int)netmaskBits)) ? 1 : 0);
case AF_INET6:
return (reinterpret_cast<EthernetTap *>(tap)->addIp(InetAddress(ip, 16, (unsigned int)netmaskBits)) ? 1 : 0);
}
return 0;
}
extern "C" int ZT_GoTap_removeIp(ZT_GoTap *tap, int af, const void *ip, int netmaskBits)
{
switch (af) {
case AF_INET:
return (reinterpret_cast<EthernetTap *>(tap)->removeIp(InetAddress(ip, 4, (unsigned int)netmaskBits)) ? 1 : 0);
case AF_INET6:
return (reinterpret_cast<EthernetTap *>(tap)->removeIp(InetAddress(ip, 16, (unsigned int)netmaskBits)) ? 1 : 0);
}
return 0;
}
extern "C" int ZT_GoTap_ips(ZT_GoTap *tap, void *buf, unsigned int bufSize)
{
auto ips = reinterpret_cast<EthernetTap *>(tap)->ips();
unsigned int p = 0;
uint8_t *const b = reinterpret_cast<uint8_t *>(buf);
for (auto ip = ips.begin(); ip != ips.end(); ++ip) {
if ((p + 6) > bufSize)
break;
const uint8_t *const ipd = reinterpret_cast<const uint8_t *>(ip->rawIpData());
if (ip->isV4()) {
b[p++] = AF_INET;
b[p++] = ipd[0];
b[p++] = ipd[1];
b[p++] = ipd[2];
b[p++] = ipd[3];
b[p++] = (uint8_t)ip->netmaskBits();
} else if (ip->isV6()) {
if ((p + 18) <= bufSize) {
b[p++] = AF_INET6;
for (int j = 0; j < 16; ++j)
b[p++] = ipd[j];
b[p++] = (uint8_t)ip->netmaskBits();
}
}
}
return (int)p;
}
extern "C" void ZT_GoTap_deviceName(ZT_GoTap *tap, char nbuf[256])
{
Utils::scopy(nbuf, 256, reinterpret_cast<EthernetTap *>(tap)->deviceName().c_str());
}
extern "C" void ZT_GoTap_setFriendlyName(ZT_GoTap *tap, const char *friendlyName)
{
reinterpret_cast<EthernetTap *>(tap)->setFriendlyName(friendlyName);
}
extern "C" void ZT_GoTap_setMtu(ZT_GoTap *tap, unsigned int mtu)
{
reinterpret_cast<EthernetTap *>(tap)->setMtu(mtu);
}
#if defined(IFA_F_SECONDARY) && !defined(IFA_F_TEMPORARY)
#define IFA_F_TEMPORARY IFA_F_SECONDARY
#endif
extern "C" int ZT_isTemporaryV6Address(const char *ifname, const struct sockaddr_storage *a)
{
#if defined(IN6_IFF_TEMPORARY) && defined(SIOCGIFAFLAG_IN6)
static ZT_SOCKET s_tmpV6Socket = ZT_INVALID_SOCKET;
static std::mutex s_lock;
std::lock_guard< std::mutex > l(s_lock);
if (s_tmpV6Socket == ZT_INVALID_SOCKET) {
s_tmpV6Socket = socket(AF_INET6, SOCK_DGRAM, 0);
if (s_tmpV6Socket <= 0)
return 0;
}
struct in6_ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
memcpy(&(ifr.ifr_addr), a, sizeof(sockaddr_in6));
if (ioctl(s_tmpV6Socket, SIOCGIFAFLAG_IN6, &ifr) < 0) {
return 0;
}
return ((ifr.ifr_ifru.ifru_flags6 & IN6_IFF_TEMPORARY) != 0) ? 1 : 0;
#else
return 0;
#endif
}
extern "C" void *ZT_malloc(unsigned long s)
{ return (void *)malloc((size_t)s); }

View file

@ -1,59 +0,0 @@
/*
* Copyright (c)2013-2021 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: 2026-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.
*/
/****/
#ifndef ZT_GONODE_H
#define ZT_GONODE_H
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "../core/zerotier.h"
#include "../core/Constants.hpp"
#ifdef __cplusplus
extern "C" {
#endif
typedef void ZT_GoTap;
struct ZT_GoNode_Impl;
typedef struct ZT_GoNode_Impl ZT_GoNode;
extern const char *const ZT_PLATFORM_DEFAULT_HOMEPATH;
ZT_GoNode *ZT_GoNode_new(const char *workingPath,uintptr_t userPtr);
void ZT_GoNode_delete(ZT_GoNode *gn);
ZT_Node *ZT_GoNode_getNode(ZT_GoNode *gn);
int ZT_GoNode_phyStartListen(ZT_GoNode *gn,const char *dev,const char *ip,int port,int primary);
int ZT_GoNode_phyStopListen(ZT_GoNode *gn,const char *dev,const char *ip,int port);
ZT_GoTap *ZT_GoNode_join(ZT_GoNode *gn,uint64_t nwid,const ZT_Fingerprint *controllerFingerprint);
void ZT_GoNode_leave(ZT_GoNode *gn,uint64_t nwid);
void ZT_GoTap_setEnabled(ZT_GoTap *tap,int enabled);
int ZT_GoTap_addIp(ZT_GoTap *tap,int af,const void *ip,int netmaskBits);
int ZT_GoTap_removeIp(ZT_GoTap *tap,int af,const void *ip,int netmaskBits);
int ZT_GoTap_ips(ZT_GoTap *tap,void *buf,unsigned int bufSize);
void ZT_GoTap_deviceName(ZT_GoTap *tap,char nbuf[256]);
void ZT_GoTap_setFriendlyName(ZT_GoTap *tap,const char *friendlyName);
void ZT_GoTap_setMtu(ZT_GoTap *tap,unsigned int mtu);
int ZT_isTemporaryV6Address(const char *ifname,const struct sockaddr_storage *a);
void *ZT_malloc(unsigned long s);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -1,4 +0,0 @@
ZeroTier Service I/O Core
------
This contains the I/O core and other helper methods for the ZeroTier Service. Most of the actual service beyond this is implemented in Go.

View file

@ -65,55 +65,7 @@ struct SHA384Hash
{ return (memcmp(data, b.data, 48) >= 0); }
};
/**
* Blob type for 128-bit GUIDs, UUIDs, etc.
*/
struct UniqueID
{
uint64_t data[2];
ZT_INLINE UniqueID() noexcept
{ Utils::zero< sizeof(data) >(data); }
ZT_INLINE UniqueID(const uint64_t a, const uint64_t b) noexcept
{
data[0] = a;
data[1] = b;
}
explicit ZT_INLINE UniqueID(const void *const d) noexcept
{ Utils::copy< 16 >(data, d); }
ZT_INLINE const uint8_t *bytes() const noexcept
{ return reinterpret_cast<const uint8_t *>(data); }
ZT_INLINE unsigned long hashCode() const noexcept
{ return (unsigned long)(data[0] ^ data[1]); }
ZT_INLINE operator bool() const noexcept
{ return ((data[0] != 0) && (data[1] != 0)); }
ZT_INLINE bool operator==(const UniqueID &b) const noexcept
{ return ((data[0] == b.data[0]) && (data[1] == b.data[1])); }
ZT_INLINE bool operator!=(const UniqueID &b) const noexcept
{ return !(*this == b); }
ZT_INLINE bool operator<(const UniqueID &b) const noexcept
{ return (memcmp(data, b.data, 16) < 0); }
ZT_INLINE bool operator>(const UniqueID &b) const noexcept
{ return (memcmp(data, b.data, 16) > 0); }
ZT_INLINE bool operator<=(const UniqueID &b) const noexcept
{ return (memcmp(data, b.data, 16) <= 0); }
ZT_INLINE bool operator>=(const UniqueID &b) const noexcept
{ return (memcmp(data, b.data, 16) >= 0); }
};
static_assert(sizeof(SHA384Hash) == 48, "SHA384Hash contains unnecessary padding");
static_assert(sizeof(UniqueID) == 16, "UniqueID contains unnecessary padding");
template< unsigned long S >
struct Blob

View file

@ -56,6 +56,7 @@ set(core_headers
Topology.hpp
Trace.hpp
TriviallyCopyable.hpp
TrustStore.hpp
Utils.hpp
VL1.hpp
VL2.hpp
@ -96,6 +97,7 @@ set(core_src
TagCredential.cpp
Topology.cpp
Trace.cpp
TrustStore.cpp
Utils.cpp
VL1.cpp
VL2.cpp

View file

@ -443,13 +443,22 @@ bool Certificate::sign(const Identity &issuer)
return false;
}
ZT_CertificateError Certificate::verify() const
ZT_CertificateError Certificate::verify(const int64_t clock, const bool checkSignatures) const
{
try {
if (this->validity[0] > this->validity[1]) {
return ZT_CERTIFICATE_ERROR_INVALID_FORMAT;
}
if ((clock >= 0) && ((this->validity[0] > clock) || (this->validity[1] < clock))) {
return ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW;
}
if (this->issuer) {
const Vector< uint8_t > enc(encode(true));
if (!reinterpret_cast<const Identity *>(this->issuer)->verify(enc.data(), (unsigned int)enc.size(), this->signature, this->signatureSize))
return ZT_CERTIFICATE_ERROR_INVALID_PRIMARY_SIGNATURE;
if (checkSignatures) {
const Vector< uint8_t > enc(encode(true));
if (!reinterpret_cast<const Identity *>(this->issuer)->verify(enc.data(), (unsigned int)enc.size(), this->signature, this->signatureSize))
return ZT_CERTIFICATE_ERROR_INVALID_PRIMARY_SIGNATURE;
}
} else {
return ZT_CERTIFICATE_ERROR_INVALID_PRIMARY_SIGNATURE;
}
@ -461,16 +470,18 @@ ZT_CertificateError Certificate::verify() const
(this->subject.uniqueId[0] != ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384))
return ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF;
Dictionary d;
m_encodeSubject(this->subject, d, true);
Vector< uint8_t > enc;
d.encode(enc);
if (checkSignatures) {
Dictionary d;
m_encodeSubject(this->subject, d, true);
Vector< uint8_t > enc;
d.encode(enc);
uint8_t h[ZT_SHA384_DIGEST_SIZE];
SHA384(h, enc.data(), (unsigned int)enc.size());
static_assert(ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE == (ZT_ECC384_PUBLIC_KEY_SIZE + 1), "incorrect size");
if (!ECC384ECDSAVerify(this->subject.uniqueId + 1, h, this->subject.uniqueIdProofSignature))
return ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF;
uint8_t h[ZT_SHA384_DIGEST_SIZE];
SHA384(h, enc.data(), (unsigned int)enc.size());
static_assert(ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE == (ZT_ECC384_PUBLIC_KEY_SIZE + 1), "incorrect size");
if (!ECC384ECDSAVerify(this->subject.uniqueId + 1, h, this->subject.uniqueIdProofSignature))
return ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF;
}
} else if (this->subject.uniqueIdSize > 0) {
return ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF;
}
@ -478,11 +489,13 @@ ZT_CertificateError Certificate::verify() const
for (unsigned int i = 0; i < this->subject.identityCount; ++i) {
if (!this->subject.identities[i].identity)
return ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS;
if (!reinterpret_cast<const Identity *>(this->subject.identities[i].identity)->locallyValidate())
return ZT_CERTIFICATE_ERROR_INVALID_IDENTITY;
if (this->subject.identities[i].locator) {
if (!reinterpret_cast<const Locator *>(this->subject.identities[i].locator)->verify(*reinterpret_cast<const Identity *>(this->subject.identities[i].identity)))
return ZT_CERTIFICATE_ERROR_INVALID_COMPONENT_SIGNATURE;
if (checkSignatures) {
if (!reinterpret_cast<const Identity *>(this->subject.identities[i].identity)->locallyValidate())
return ZT_CERTIFICATE_ERROR_INVALID_IDENTITY;
if (this->subject.identities[i].locator) {
if (!reinterpret_cast<const Locator *>(this->subject.identities[i].locator)->verify(*reinterpret_cast<const Identity *>(this->subject.identities[i].identity)))
return ZT_CERTIFICATE_ERROR_INVALID_COMPONENT_SIGNATURE;
}
}
}

View file

@ -18,7 +18,6 @@
#include "SHA512.hpp"
#include "C25519.hpp"
#include "ECC384.hpp"
#include "SharedPtr.hpp"
#include "Identity.hpp"
#include "Locator.hpp"
#include "Dictionary.hpp"
@ -47,9 +46,6 @@ namespace ZeroTier {
*/
class Certificate : public ZT_Certificate
{
friend class SharedPtr< Certificate >;
friend class SharedPtr< const Certificate >;
public:
Certificate() noexcept;
explicit Certificate(const ZT_Certificate &apiCert);
@ -159,9 +155,11 @@ public:
* This doesn't check the entire certificate chain, just the validity of
* the certificate's internal signature and fields.
*
* @param clock If non-negative, check that certificate is in valid time window
* @param checkSignatures If true, perform full signature check (which is more expensive than other checks)
* @return OK (0) or error code indicating why certificate failed verification.
*/
ZT_CertificateError verify() const;
ZT_CertificateError verify(int64_t clock, bool checkSignatures) const;
/**
* Create a CSR that encodes the subject of this certificate
@ -230,8 +228,6 @@ private:
Vector< uint8_t > m_subjectUniqueId;
Vector< uint8_t > m_subjectUniqueIdProofSignature;
Vector< uint8_t > m_signature;
std::atomic< int > __refCount;
};
} // namespace ZeroTier

View file

@ -19,7 +19,6 @@
#include "MAC.hpp"
#include "Containers.hpp"
#include "TriviallyCopyable.hpp"
#include "Blob.hpp"
namespace ZeroTier {
@ -507,34 +506,6 @@ public:
ZT_INLINE bool operator>=(const InetAddress &a) const noexcept
{ return !(*this < a); }
/**
* Generate a local unique key for this address
*
* This key is not comparable across instances or architectures.
*
* @return Local unique key
*/
ZT_INLINE UniqueID key() const noexcept
{
if (as.ss.ss_family == AF_INET) {
// For IPv4 we can just pack the IP and port into the first element.
return UniqueID(((uint64_t)as.sa_in.sin_addr.s_addr << 16U) ^ (uint64_t)as.sa_in.sin_port, 0);
} else if (likely(as.ss.ss_family == AF_INET6)) {
// The OR with (a2 == 0) is to make sure the second part of the UniqueID
// can never be zero, otherwise it could be made to collide with an IPv4
// IP address. We also construct this to make it so someone in a /64
// can't collide another address in the same /64.
const uint64_t a1 = Utils::loadMachineEndian< uint64_t >(as.sa_in6.sin6_addr.s6_addr);
const uint64_t a2 = Utils::hash64(Utils::s_mapNonce ^ Utils::loadMachineEndian< uint64_t >(as.sa_in6.sin6_addr.s6_addr + 8)) + (uint64_t)as.sa_in6.sin6_port;
return UniqueID(a1, a2 | (uint64_t)(a2 == 0));
} else if (likely(as.ss.ss_family == 0)) {
return UniqueID(0, 0);
} else {
// This should never be reached, but handle it somehow.
return UniqueID(as.ss.ss_family, Utils::fnv1a32(&as, sizeof(as)));
}
}
/**
* Compute an IPv6 link-local address
*

View file

@ -168,13 +168,16 @@
#endif
#endif
/* Right now we fail if no C++11. The core could be ported to old C++ compilers
* if a shim for <atomic> were included. */
#ifndef __CPP11__
#error TODO: to build on pre-c++11 compilers we will need to make a subset of std::atomic for integers
#define nullptr (0)
#define constexpr ZT_INLINE
#define noexcept throw()
#if defined(ZT_ARCH_X64) || defined(__aarch64__)
#ifndef ZT_ARCH_APPEARS_64BIT
#define ZT_ARCH_APPEARS_64BIT 1
#endif
#endif
#ifdef UINTPTR_MAX
#if UINTPTR_MAX == UINT64_MAX
#ifndef ZT_ARCH_APPEARS_64BIT
#define ZT_ARCH_APPEARS_64BIT 1
#endif
#endif
#endif
@ -190,6 +193,17 @@
#endif
#endif
/* Right now we fail if no C++11. The core could be ported to old C++ compilers
* if a shim for <atomic> were included. */
#ifndef __CPP11__
#error TODO: to build on pre-c++11 compilers we will need to make a subset of std::atomic for integers
#define nullptr (0)
#define constexpr ZT_INLINE
#define noexcept throw()
#define explicit
#endif
#endif
#ifndef restrict
#if defined(__GNUC__) || defined(__clang__)
#define restrict __restrict__

View file

@ -42,6 +42,93 @@ class Path
class Defragmenter;
public:
/**
* Map key for paths designed for very fast lookup
*/
class Key
{
public:
/**
* Construct key with undefined value
*/
ZT_INLINE Key() noexcept
{}
ZT_INLINE Key(const Key &k) noexcept: m_hashCode(k.m_hashCode), m_v664(k.m_v664), m_port(k.m_port)
{}
ZT_INLINE Key(const InetAddress &ip) noexcept
{
const unsigned int family = ip.as.sa.sa_family;
if (family == AF_INET) {
const uint16_t p = (uint16_t)ip.as.sa_in.sin_port;
m_hashCode = Utils::hash64((((uint64_t)ip.as.sa_in.sin_addr.s_addr) << 16U) ^ ((uint64_t)p) ^ Utils::s_mapNonce);
m_v664 = 0; // IPv6 /64 is 0 for IPv4
m_port = p;
} else {
if (likely(family == AF_INET6)) {
const uint64_t a = Utils::loadMachineEndian< uint64_t >(reinterpret_cast<const uint8_t *>(ip.as.sa_in6.sin6_addr.s6_addr));
const uint64_t b = Utils::loadMachineEndian< uint64_t >(reinterpret_cast<const uint8_t *>(ip.as.sa_in6.sin6_addr.s6_addr) + 8);
const uint16_t p = ip.as.sa_in6.sin6_port;
m_hashCode = Utils::hash64(a ^ b ^ ((uint64_t)p) ^ Utils::s_mapNonce);
m_v664 = a; // IPv6 /64
m_port = p;
} else {
// This isn't reachable since only IPv4 and IPv6 are used with InetAddress, but implement
// something here for technical completeness.
m_hashCode = Utils::fnv1a32(&ip, sizeof(InetAddress));
m_v664 = Utils::fnv1a32(ip.as.sa.sa_data, sizeof(ip.as.sa.sa_data));
m_port = (uint16_t)family;
}
}
}
ZT_INLINE Key &operator=(const Key &k) noexcept
{
m_hashCode = k.m_hashCode;
m_v664 = k.m_v664;
m_port = k.m_port;
return *this;
}
ZT_INLINE unsigned long hashCode() const noexcept
{ return (unsigned long)m_hashCode; }
ZT_INLINE bool operator==(const Key &k) const noexcept
{ return (m_hashCode == k.m_hashCode) && (m_v664 == k.m_v664) && (m_port == k.m_port); }
ZT_INLINE bool operator!=(const Key &k) const noexcept
{ return (!(*this == k)); }
ZT_INLINE bool operator<(const Key &k) const noexcept
{
if (m_hashCode < k.m_hashCode) {
return true;
} else if (m_hashCode == k.m_hashCode) {
if (m_v664 < k.m_v664) {
return true;
} else if (m_v664 == k.m_v664) {
return (m_port < k.m_port);
}
}
return false;
}
ZT_INLINE bool operator>(const Key &k) const noexcept
{ return (k < *this); }
ZT_INLINE bool operator<=(const Key &k) const noexcept
{ return !(k < *this); }
ZT_INLINE bool operator>=(const Key &k) const noexcept
{ return !(*this < k); }
private:
uint64_t m_hashCode;
uint64_t m_v664;
uint16_t m_port;
};
ZT_INLINE Path(const int64_t l, const InetAddress &r) noexcept:
m_localSocket(l),
m_lastIn(0),

View file

@ -106,17 +106,17 @@ public:
* This checks the locator's timestamp against the current locator and
* replace it if newer.
*
* SECURITY: note that this does NOT validate the locator's signature
* or structural validity. This MUST be done before calling this.
*
* @param loc Locator update
* @param verify If true, verify locator's signature and structure
* @return New locator or previous if it was not replaced.
*/
ZT_INLINE SharedPtr< const Locator > setLocator(const SharedPtr< const Locator > &loc) noexcept
ZT_INLINE SharedPtr< const Locator > setLocator(const SharedPtr< const Locator > &loc, bool verify) noexcept
{
RWMutex::Lock l(m_lock);
if ((loc) && ((!m_locator) || (m_locator->timestamp() < loc->timestamp())))
m_locator = loc;
if ((loc) && ((!m_locator) || (m_locator->timestamp() < loc->timestamp()))) {
if ((!verify) || loc->verify(m_id))
m_locator = loc;
}
return m_locator;
}

View file

@ -146,6 +146,14 @@ public:
return 0;
}
/**
* Cast this SharedPtr<> to one that holds a const instance of the type
*
* @return "this" casted in place to hold "const T"
*/
ZT_INLINE const SharedPtr<const T> &constify() const noexcept
{ return reinterpret_cast< const SharedPtr<const T> >(*this); }
ZT_INLINE unsigned long hashCode() const noexcept
{ return (unsigned long)Utils::hash64((uint64_t)((uintptr_t)m_ptr)); }

View file

@ -18,34 +18,7 @@ namespace ZeroTier {
Topology::Topology(const RuntimeEnvironment *renv, void *tPtr, const int64_t now) :
RR(renv)
{
char tmp[32];
Dictionary d;
Vector< uint8_t > trustData(RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_TRUST_STORE, Utils::ZERO256, 0));
if (trustData.empty() || (!d.decode(trustData.data(), (unsigned int)trustData.size()))) {
if (!d.decode(Defaults::CERTIFICATES, Defaults::CERTIFICATES_BYTES))
d.clear();
}
if (!d.empty()) {
const unsigned long certCount = (unsigned long)d.getUI("c$");
for (unsigned long idx = 0; idx < certCount; ++idx) {
uint64_t id[6];
const Vector< uint8_t > &serialNo = d[Dictionary::arraySubscript(tmp, sizeof(tmp), "c$.s", idx)];
if (serialNo.size() == ZT_SHA384_DIGEST_SIZE) {
Utils::copy< 48 >(id, serialNo.data());
Certificate cert;
Vector< uint8_t > enc(RR->node->stateObjectGet(tPtr, ZT_STATE_OBJECT_CERT, id, 6));
if (cert.decode(enc.data(), (unsigned int)enc.size()))
addCertificate(tPtr, cert, now, (unsigned int)d.getUI(Dictionary::arraySubscript(tmp, sizeof(tmp), "c$.lt", idx)), false, false, false);
}
}
}
m_cleanCertificates(tPtr, now);
m_updateRootPeers(tPtr, now);
}
{}
SharedPtr< Peer > Topology::add(void *tPtr, const SharedPtr< Peer > &peer)
{
@ -77,19 +50,6 @@ void Topology::allPeers(Vector< SharedPtr< Peer > > &allPeers, Vector< SharedPtr
void Topology::doPeriodicTasks(void *tPtr, const int64_t now)
{
// Clean any expired certificates, updating roots if they have changed.
{
Mutex::Lock l1(m_certs_l);
if (m_cleanCertificates(tPtr, now)) {
m_writeTrustStore(tPtr);
{
Mutex::Lock l2(m_roots_l);
RWMutex::Lock l3(m_peers_l);
m_updateRootPeers(tPtr, now);
}
}
}
// Get a list of root peer pointer addresses for filtering during peer cleanup.
Vector< uintptr_t > rootLookup;
{
@ -137,19 +97,19 @@ void Topology::doPeriodicTasks(void *tPtr, const int64_t now)
// and delete those that actually are GC'd. Write lock is aquired only briefly on delete
// just as with peers.
{
Vector< UniqueID > possibleDelete;
Vector< Path::Key > possibleDelete;
{
RWMutex::RLock l1(m_paths_l);
for (Map< UniqueID, SharedPtr< Path > >::iterator i(m_paths.begin()); i != m_paths.end(); ++i) {
for (Map< Path::Key, SharedPtr< Path > >::iterator i(m_paths.begin()); i != m_paths.end(); ++i) {
if (i->second.references() <= 1)
possibleDelete.push_back(i->first);
}
}
if (!possibleDelete.empty()) {
ZT_SPEW("garbage collecting (likely) %u orphaned paths", (unsigned int)possibleDelete.size());
for (Vector< UniqueID >::const_iterator i(possibleDelete.begin()); i != possibleDelete.end(); ++i) {
for (Vector< Path::Key >::const_iterator i(possibleDelete.begin()); i != possibleDelete.end(); ++i) {
RWMutex::Lock l1(m_paths_l);
Map< UniqueID, SharedPtr< Path > >::iterator p(m_paths.find(*i));
Map< Path::Key, SharedPtr< Path > >::iterator p(m_paths.find(*i));
if ((p != m_paths.end()) && p->second.weakGC())
m_paths.erase(p);
}
@ -159,122 +119,9 @@ void Topology::doPeriodicTasks(void *tPtr, const int64_t now)
void Topology::saveAll(void *tPtr)
{
{
RWMutex::RLock l(m_peers_l);
for (Map< Address, SharedPtr< Peer > >::iterator i(m_peers.begin()); i != m_peers.end(); ++i)
i->second->save(tPtr);
}
{
Mutex::Lock l(m_certs_l);
m_writeTrustStore(tPtr);
}
}
ZT_CertificateError Topology::addCertificate(void *tPtr, const Certificate &cert, const int64_t now, const unsigned int localTrust, const bool writeToLocalStore, const bool refreshRootSets, const bool verify)
{
{
const SHA384Hash serial(cert.serialNo);
p_CertEntry certEntry;
Mutex::Lock l1(m_certs_l);
{
Map< SHA384Hash, p_CertEntry >::iterator c(m_certs.find(serial));
if (c != m_certs.end()) {
if (c->second.localTrust == localTrust)
return ZT_CERTIFICATE_ERROR_NONE;
certEntry.certificate = c->second.certificate;
}
}
if (!certEntry.certificate) {
certEntry.certificate.set(new Certificate(cert));
if (verify) {
m_cleanCertificates(tPtr, now);
const ZT_CertificateError err = m_verifyCertificate(cert, now, localTrust, false);
if (err != ZT_CERTIFICATE_ERROR_NONE)
return err;
}
}
certEntry.localTrust = localTrust;
if ((cert.subject.uniqueId) && (cert.subject.uniqueIdSize > 0)) {
SHA384Hash uniqueIdHash;
SHA384(uniqueIdHash.data, cert.subject.uniqueId, cert.subject.uniqueIdSize);
p_CertEntry &bySubjectUniqueId = m_certsBySubjectUniqueID[uniqueIdHash];
if (bySubjectUniqueId.certificate) {
if (bySubjectUniqueId.certificate->subject.timestamp >= cert.subject.timestamp)
return ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT;
m_eraseCertificate(tPtr, bySubjectUniqueId.certificate, &uniqueIdHash);
m_certsBySubjectUniqueID[uniqueIdHash] = certEntry;
} else {
bySubjectUniqueId = certEntry;
}
}
for (unsigned int i = 0; i < cert.subject.identityCount; ++i) {
const Identity *const ii = reinterpret_cast<const Identity *>(cert.subject.identities[i].identity);
if (ii)
m_certsBySubjectIdentity[ii->fingerprint()][certEntry.certificate] = localTrust;
}
m_certs[serial] = certEntry;
if (refreshRootSets) {
Mutex::Lock l2(m_roots_l);
RWMutex::Lock l3(m_peers_l);
m_updateRootPeers(tPtr, now);
}
if (writeToLocalStore)
m_writeTrustStore(tPtr);
}
if (writeToLocalStore) {
Vector< uint8_t > certData(cert.encode());
uint64_t id[6];
Utils::copy< 48 >(id, cert.serialNo);
RR->node->stateObjectPut(tPtr, ZT_STATE_OBJECT_CERT, id, 6, certData.data(), (unsigned int)certData.size());
}
return ZT_CERTIFICATE_ERROR_NONE;
}
unsigned int Topology::deleteCertificate(void *tPtr,const uint8_t serialNo[ZT_SHA384_DIGEST_SIZE])
{
Mutex::Lock l(m_certs_l);
const unsigned long origCertCount = (unsigned long)m_certs.size();
Map< SHA384Hash, p_CertEntry >::const_iterator c(m_certs.find(SHA384Hash(serialNo)));
if (c != m_certs.end()) {
if ((c->second.certificate->subject.uniqueId) && (c->second.certificate->subject.uniqueIdSize > 0)) {
SHA384Hash uniqueIdHash;
SHA384(uniqueIdHash.data, c->second.certificate->subject.uniqueId, c->second.certificate->subject.uniqueIdSize);
m_eraseCertificate(tPtr, c->second.certificate, &uniqueIdHash);
} else {
m_eraseCertificate(tPtr, c->second.certificate, nullptr);
}
const int64_t now = RR->node->now();
m_cleanCertificates(tPtr, now);
m_writeTrustStore(tPtr);
{
Mutex::Lock l2(m_roots_l);
RWMutex::Lock l3(m_peers_l);
m_updateRootPeers(tPtr, now);
}
}
return (unsigned int)(origCertCount - (unsigned long)m_certs.size());
}
void Topology::allCerts(Vector< SharedPtr<const Certificate> > &c,Vector< unsigned int > &t) const noexcept
{
Mutex::Lock l(m_certs_l);
const unsigned long cs = (unsigned long)m_certs.size();
c.reserve(cs);
t.reserve(cs);
for(Map< SHA384Hash, p_CertEntry >::const_iterator i(m_certs.begin());i!=m_certs.end();++i) {
c.push_back(i->second.certificate);
t.push_back(i->second.localTrust);
}
RWMutex::RLock l(m_peers_l);
for (Map< Address, SharedPtr< Peer > >::iterator i(m_peers.begin()); i != m_peers.end(); ++i)
i->second->save(tPtr);
}
struct p_RootRankingComparisonOperator
@ -315,115 +162,6 @@ void Topology::m_rankRoots(const int64_t now)
}
}
void Topology::m_eraseCertificate(void *tPtr, const SharedPtr< const Certificate > &cert, const SHA384Hash *uniqueIdHash)
{
// assumes m_certs is locked for writing
const SHA384Hash serialNo(cert->serialNo);
m_certs.erase(serialNo);
if (uniqueIdHash)
m_certsBySubjectUniqueID.erase(*uniqueIdHash);
for (unsigned int i = 0; i < cert->subject.identityCount; ++i) {
const Identity *const ii = reinterpret_cast<const Identity *>(cert->subject.identities[i].identity);
Map< Fingerprint, Map< SharedPtr< const Certificate >, unsigned int > >::iterator bySubjectIdentity(m_certsBySubjectIdentity.find(ii->fingerprint()));
if (bySubjectIdentity != m_certsBySubjectIdentity.end()) {
bySubjectIdentity->second.erase(cert);
if (bySubjectIdentity->second.empty())
m_certsBySubjectIdentity.erase(bySubjectIdentity);
}
}
RR->node->stateObjectDelete(tPtr, ZT_STATE_OBJECT_CERT, serialNo.data, 6);
}
bool Topology::m_cleanCertificates(void *tPtr, int64_t now)
{
// assumes m_certs is locked for writing
bool deleted = false;
Vector< SharedPtr< const Certificate >> toDelete;
for (;;) {
for (Map< SHA384Hash, p_CertEntry >::iterator c(m_certs.begin()); c != m_certs.end(); ++c) {
// Verify, but the last boolean option tells it to skip signature checks as this would
// already have been done. This will therefore just check the path and validity times
// of the certificate.
const ZT_CertificateError err = m_verifyCertificate(*(c->second.certificate), now, c->second.localTrust, true);
if (err != ZT_CERTIFICATE_ERROR_NONE)
toDelete.push_back(c->second.certificate);
}
if (toDelete.empty())
break;
deleted = true;
SHA384Hash uniqueIdHash;
for (Vector< SharedPtr< const Certificate > >::iterator c(toDelete.begin()); c != toDelete.end(); ++c) {
if ((*c)->subject.uniqueId) {
SHA384(uniqueIdHash.data, (*c)->subject.uniqueId, (*c)->subject.uniqueIdSize);
m_eraseCertificate(tPtr, *c, &uniqueIdHash);
} else {
m_eraseCertificate(tPtr, *c, nullptr);
}
}
toDelete.clear();
}
return deleted;
}
bool Topology::m_verifyCertificateChain(const Certificate *current, const int64_t now) const
{
// assumes m_certs is at least locked for reading
Map< Fingerprint, Map< SharedPtr< const Certificate >, unsigned int > >::const_iterator c(m_certsBySubjectIdentity.find(reinterpret_cast<const Identity *>(current->issuer)->fingerprint()));
if (c != m_certsBySubjectIdentity.end()) {
for (Map< SharedPtr< const Certificate >, unsigned int >::const_iterator cc(c->second.begin()); cc != c->second.end(); ++cc) {
if (
(cc->first->maxPathLength > current->maxPathLength) &&
(cc->first->validity[0] <= now) && // not before now
(cc->first->validity[1] >= now) && // not after now
(cc->first->validity[0] <= current->timestamp) && // not before child cert's timestamp
(cc->first->validity[1] >= current->timestamp) // not after child cert's timestamp
) {
if ((cc->second & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA) != 0)
return true;
if (m_verifyCertificateChain(cc->first.ptr(), now))
return true;
}
}
}
return false;
}
ZT_CertificateError Topology::m_verifyCertificate(const Certificate &cert, const int64_t now, unsigned int localTrust, bool skipSignatureCheck) const
{
// assumes m_certs is at least locked for reading
// Check certificate time window against current time.
if ((cert.validity[0] > now) || (cert.validity[1] < now))
return ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW;
// Verify primary and internal signatures and other objects unless the caller
// elected to skip, which is done to re-check certs already in the DB.
if (!skipSignatureCheck) {
const ZT_CertificateError err = cert.verify();
if (err != ZT_CERTIFICATE_ERROR_NONE)
return err;
}
// If this is a root CA, we can skip this as we're already there. Otherwise we
// recurse up the tree until we hit a root CA.
if ((localTrust & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA) == 0) {
if (!m_verifyCertificateChain(&cert, now))
return ZT_CERTIFICATE_ERROR_INVALID_CHAIN;
}
return ZT_CERTIFICATE_ERROR_NONE;
}
void Topology::m_loadCached(void *tPtr, const Address &zta, SharedPtr< Peer > &peer)
{
// does not require any locks to be held
@ -469,7 +207,7 @@ SharedPtr< Peer > Topology::m_peerFromCached(void *tPtr, const Address &zta)
return p;
}
SharedPtr< Path > Topology::m_newPath(const int64_t l, const InetAddress &r, const UniqueID &k)
SharedPtr< Path > Topology::m_newPath(const int64_t l, const InetAddress &r, const Path::Key &k)
{
SharedPtr< Path > p(new Path(l, r));
RWMutex::Lock lck(m_paths_l);
@ -480,53 +218,4 @@ SharedPtr< Path > Topology::m_newPath(const int64_t l, const InetAddress &r, con
return p;
}
void Topology::m_updateRootPeers(void *tPtr, const int64_t now)
{
// assumes m_certs_l, m_peers_l, and m_roots_l are locked for write
Set< Identity > rootIdentities;
for (Map< Fingerprint, Map< SharedPtr< const Certificate >, unsigned int > >::const_iterator c(m_certsBySubjectIdentity.begin()); c != m_certsBySubjectIdentity.end(); ++c) {
for (Map< SharedPtr< const Certificate >, unsigned int >::const_iterator cc(c->second.begin()); cc != c->second.end(); ++cc) {
if ((cc->second & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ZEROTIER_ROOT_SET) != 0) {
for (unsigned int i = 0; i < cc->first->subject.identityCount; ++i) {
if (cc->first->subject.identities[i].identity)
rootIdentities.insert(*reinterpret_cast<const Identity *>(cc->first->subject.identities[i].identity));
}
}
}
}
m_roots.clear();
for (Set< Identity >::const_iterator i(rootIdentities.begin()); i != rootIdentities.end(); ++i) {
SharedPtr< Peer > &p = m_peers[i->address()];
if ((!p) || (p->identity() != *i)) {
p.set(new Peer(RR));
p->init(*i);
}
m_roots.push_back(p);
}
m_rankRoots(now);
}
void Topology::m_writeTrustStore(void *tPtr)
{
// assumes m_certs is locked
char tmp[32];
Dictionary d;
unsigned long idx = 0;
d.add("c$", (uint64_t)m_certs.size());
for (Map< SHA384Hash, p_CertEntry >::const_iterator c(m_certs.begin()); c != m_certs.end(); ++c) {
d[Dictionary::arraySubscript(tmp, sizeof(tmp), "c$.s", idx)].assign(c->first.data, c->first.data + ZT_SHA384_DIGEST_SIZE);
d.add(Dictionary::arraySubscript(tmp, sizeof(tmp), "c$.lt", idx), (uint64_t)c->second.localTrust);
++idx;
}
Vector< uint8_t > trustStore;
d.encode(trustStore);
RR->node->stateObjectPut(tPtr, ZT_STATE_OBJECT_TRUST_STORE, Utils::ZERO256, 0, trustStore.data(), (unsigned int)trustStore.size());
}
} // namespace ZeroTier

View file

@ -84,10 +84,10 @@ public:
*/
ZT_INLINE SharedPtr< Path > path(const int64_t l, const InetAddress &r)
{
const UniqueID k(r.key());
const Path::Key k(r);
{
RWMutex::RLock lck(m_paths_l);
Map< UniqueID, SharedPtr< Path > >::const_iterator p(m_paths.find(k));
Map< Path::Key, SharedPtr< Path > >::const_iterator p(m_paths.find(k));
if (likely(p != m_paths.end()))
return p->second;
}
@ -150,79 +150,21 @@ public:
*/
void saveAll(void *tPtr);
/**
* Add a certificate to the local certificate store
*
* @param tPtr Thread pointer
* @param cert Certificate to add (a copy will be made if added)
* @param now Current time
* @param localTrust Local trust bit flags
* @param writeToLocalStore If true, write to local object store (via API callbacks)
* @param refreshRootSets If true, refresh root sets in case a root set changed (default: true)
* @param verify If true, verify certificate and certificate chain (default: true)
* @return Error or 0 on success
*/
ZT_CertificateError addCertificate(
void *tPtr,
const Certificate &cert,
int64_t now,
unsigned int localTrust,
bool writeToLocalStore,
bool refreshRootSets = true,
bool verify = true);
/**
* Delete certificate
*
* @param tPtr Thread pointer
* @param serialNo Serial number to delete
* @return Number of deleted certificates
*/
unsigned int deleteCertificate(void *tPtr,const uint8_t serialNo[ZT_SHA384_DIGEST_SIZE]);
/**
* Fill vectors with all certificates and their corresponding local trust flags
*
* @param c Certificate vector
* @param t Local trust vector
*/
void allCerts(Vector< SharedPtr<const Certificate> > &c,Vector< unsigned int > &t) const noexcept;
private:
void m_rankRoots(int64_t now);
void m_eraseCertificate(void *tPtr, const SharedPtr< const Certificate > &cert, const SHA384Hash *uniqueIdHash);
bool m_cleanCertificates(void *tPtr, int64_t now);
bool m_verifyCertificateChain(const Certificate *current, int64_t now) const;
ZT_CertificateError m_verifyCertificate(const Certificate &cert, int64_t now, unsigned int localTrust, bool skipSignatureCheck) const;
void m_loadCached(void *tPtr, const Address &zta, SharedPtr< Peer > &peer);
SharedPtr< Peer > m_peerFromCached(void *tPtr, const Address &zta);
SharedPtr< Path > m_newPath(int64_t l, const InetAddress &r, const UniqueID &k);
void m_updateRootPeers(void *tPtr, int64_t now);
void m_writeTrustStore(void *tPtr);
SharedPtr< Path > m_newPath(int64_t l, const InetAddress &r, const Path::Key &k);
const RuntimeEnvironment *const RR;
Vector< SharedPtr< Peer > > m_roots;
Map< Address, SharedPtr< Peer > > m_peers;
Map< UniqueID, SharedPtr< Path > > m_paths;
struct p_CertEntry
{
ZT_INLINE p_CertEntry() :
certificate(),
localTrust(0)
{}
SharedPtr< const Certificate > certificate;
unsigned int localTrust;
};
Map< SHA384Hash, p_CertEntry > m_certs;
Map< SHA384Hash, p_CertEntry > m_certsBySubjectUniqueID;
Map< Fingerprint, Map< SharedPtr< const Certificate >, unsigned int > > m_certsBySubjectIdentity;
Map< Path::Key, SharedPtr< Path > > m_paths;
RWMutex m_peers_l; // m_peers
RWMutex m_paths_l; // m_paths
Mutex m_roots_l; // m_roots and m_lastRankedRoots
Mutex m_certs_l; // m_certs and friends
SharedPtr< Peer > m_bestRoot;
Spinlock l_bestRoot;

218
core/TrustStore.cpp Normal file
View file

@ -0,0 +1,218 @@
/*
* Copyright (c)2013-2021 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: 2026-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.
*/
/****/
#include "TrustStore.hpp"
#include "Topology.hpp"
namespace ZeroTier {
TrustStore::TrustStore()
{}
TrustStore::~TrustStore()
{}
SharedPtr< const TrustStore::Entry > TrustStore::get(const SHA384Hash &serial) const
{
RWMutex::RLock l(m_lock);
Map< SHA384Hash, SharedPtr< Entry > >::const_iterator i(m_bySerial.find(serial));
return (i == m_bySerial.end()) ? SharedPtr< const TrustStore::Entry >() : i->second.constify();
}
Vector< SharedPtr< Peer > > TrustStore::roots(void *const tPtr, const RuntimeEnvironment *RR)
{
RWMutex::RLock l(m_lock);
Vector< SharedPtr< Peer > > r;
r.reserve(m_bySerial.size());
for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) {
if ((c->second->localTrust() & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ZEROTIER_ROOT_SET) != 0) {
for (unsigned int j = 0; j < c->second->certificate().subject.identityCount; ++j) {
const Identity *const id = reinterpret_cast<const Identity *>(c->second->certificate().subject.identities[j].identity);
if ((id != nullptr) && (*id)) { // sanity check
SharedPtr< Peer > peer(RR->topology->peer(tPtr, id->address(), true));
if (!peer) {
peer.set(new Peer(RR));
peer->init(*id);
peer = RR->topology->add(tPtr, peer);
}
const Locator *const loc = reinterpret_cast<const Locator *>(c->second->certificate().subject.identities[j].locator);
if (loc)
peer->setLocator(SharedPtr< const Locator >(new Locator(*loc)), true);
r.push_back(peer);
}
}
}
}
return r;
}
Vector< SharedPtr< const TrustStore::Entry > > TrustStore::all() const
{
Vector< SharedPtr< const TrustStore::Entry > > r;
RWMutex::RLock l(m_lock);
r.reserve(m_bySerial.size());
for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator i(m_bySerial.begin()); i != m_bySerial.end(); ++i)
r.push_back(i->second.constify());
return r;
}
void TrustStore::add(const Certificate &cert, const unsigned int localTrust)
{
RWMutex::Lock l(m_lock);
m_addQueue.push_front(SharedPtr(new Entry(cert, localTrust)));
}
// Recursive function to trace a certificate up the chain to a CA, returning true
// if the CA is reached and the path length is less than the maximum.
static bool p_validatePath(const Map< SHA384Hash, Vector< SharedPtr< TrustStore::Entry > > > &bySignedCert, const SharedPtr< TrustStore::Entry > &entry, unsigned int pathLength)
{
if (((entry->localTrust() & ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA) != 0) && (pathLength <= entry->certificate().maxPathLength))
return true;
if (pathLength < ZT_CERTIFICATE_MAX_PATH_LENGTH) {
const Map< SHA384Hash, Vector< SharedPtr< TrustStore::Entry > > >::const_iterator signers(bySignedCert.find(SHA384Hash(entry->certificate().serialNo)));
if (signers != bySignedCert.end()) {
for (Vector< SharedPtr< TrustStore::Entry > >::const_iterator signer(signers->second.begin()); signer != signers->second.end(); ++signer) {
if ((*signer != entry) && (p_validatePath(bySignedCert, *signer, pathLength + 1)))
return true;
}
}
}
return false;
}
void TrustStore::update(const int64_t clock, Vector< std::pair< SharedPtr< Entry >, ZT_CertificateError > > *const purge)
{
RWMutex::Lock l(m_lock);
// Re-verify existing and rejected certificates, excluding signatures which
// will have already been checked (and checking these is CPU-intensive). This
// catches certificate expiry and un-expiry if the system's clock has been
// changed. When a formerly rejected cert is revived it ends up getting
// checked twice, but optimizing this out would be about as costly as just
// doing this as verify() without signature check is cheap.
for (Map< SharedPtr< Entry >, ZT_CertificateError >::iterator c(m_rejected.begin()); c != m_rejected.end();) {
const ZT_CertificateError err = c->first->m_certificate.verify(clock, false);
if (err == ZT_CERTIFICATE_ERROR_NONE) {
m_bySerial[SHA384Hash(c->first->m_certificate.serialNo)] = c->first;
m_rejected.erase(c++);
} else {
++c;
}
}
for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) {
const ZT_CertificateError err = c->second->m_certificate.verify(clock, false);
if (err == ZT_CERTIFICATE_ERROR_NONE) {
++c;
} else {
m_rejected[c->second] = err;
m_bySerial.erase(c++);
}
}
// Add new certificates to m_bySerial, which is the master certificate set. They still
// have yet to have their full certificate chains validated. Full signature checking is
// performed here.
while (!m_addQueue.empty()) {
const ZT_CertificateError err = m_addQueue.front()->m_certificate.verify(clock, true);
if (err == ZT_CERTIFICATE_ERROR_NONE) {
m_bySerial[SHA384Hash(m_addQueue.front()->m_certificate.serialNo)].move(m_addQueue.front());
} else {
m_rejected[m_addQueue.front()] = err;
}
m_addQueue.pop_front();
}
// Verify certificate paths and replace old certificates with newer certificates
// when subject unique ID mapping dictates, repeating the process until a stable
// state is achieved. A loop is needed because deleting old certs when new
// certs (with the same subject unique ID) replace them could in theory alter
// certificate validation path checking outcomes, though in practice it should
// not since mixing certificate roles this way would be strange.
for (;;) {
// Create a reverse lookup mapping from signed certs to signer certs for
// certificate path validation.
Map< SHA384Hash, Vector< SharedPtr< Entry > > > bySignedCert;
for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end(); ++c) {
for (unsigned int j = 0; j < c->second->m_certificate.subject.certificateCount; ++j)
bySignedCert[SHA384Hash(c->second->m_certificate.subject.certificates[j])].push_back(c->second);
}
// Validate certificate paths and reject any certificates that do not trace
// back to a CA.
for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) {
if (p_validatePath(bySignedCert, c->second, 0)) {
++c;
} else {
m_rejected[c->second] = ZT_CERTIFICATE_ERROR_INVALID_CHAIN;
m_bySerial.erase(c++);
}
}
// Populate mapping of subject unique IDs to certificates and reject any
// certificates that have been superseded by newly issued certificates with
// the same subject.
bool exitLoop = true;
m_bySubjectUniqueId.clear();
for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) {
const unsigned int uniqueIdSize = c->second->m_certificate.subject.uniqueIdSize;
if ((uniqueIdSize > 0) && (uniqueIdSize <= 1024)) { // 1024 is a sanity check value, actual unique IDs are <100 bytes
SharedPtr< Entry > &current = m_bySubjectUniqueId[Vector< uint8_t >(c->second->m_certificate.subject.uniqueId, c->second->m_certificate.subject.uniqueId + uniqueIdSize)];
if (current) {
if (c->second->m_certificate.subject.timestamp > current->m_certificate.subject.timestamp) {
exitLoop = false;
m_rejected[current] = ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT;
m_bySerial.erase(SHA384Hash(current->m_certificate.serialNo));
current = c->second;
}
} else {
current = c->second;
}
}
}
if (exitLoop)
break;
}
// Populate a mapping of identities to certificates whose subjects reference them.
m_bySubjectIdentity.clear();
for (Map< SHA384Hash, SharedPtr< Entry > >::const_iterator c(m_bySerial.begin()); c != m_bySerial.end();) {
for (unsigned int i = 0; i < c->second->m_certificate.subject.identityCount; ++i)
m_bySubjectIdentity[reinterpret_cast<const Identity *>(c->second->m_certificate.subject.identities[i].identity)->fingerprint()].push_back(c->second);
}
// Purge and return purged certificates if this option is selected.
if (purge) {
purge->reserve(m_rejected.size());
for (Map< SharedPtr< Entry >, ZT_CertificateError >::const_iterator c(m_rejected.begin()); c != m_rejected.end(); ++c)
purge->push_back(std::pair< SharedPtr< Entry >, ZT_CertificateError >(c->first, c->second));
m_rejected.clear();
}
}
Vector< std::pair< SharedPtr<TrustStore::Entry>, ZT_CertificateError > > TrustStore::rejects() const
{
Vector< std::pair< SharedPtr<Entry>, ZT_CertificateError > > r;
RWMutex::RLock l(m_lock);
r.reserve(m_rejected.size());
for (Map< SharedPtr< Entry >, ZT_CertificateError >::const_iterator c(m_rejected.begin()); c != m_rejected.end(); ++c)
r.push_back(std::pair< SharedPtr< Entry >, ZT_CertificateError >(c->first, c->second));
return r;
}
} // namespace ZeroTier

138
core/TrustStore.hpp Normal file
View file

@ -0,0 +1,138 @@
/*
* Copyright (c)2013-2021 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: 2026-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.
*/
/****/
#ifndef ZT_TRUSTSTORE_HPP
#define ZT_TRUSTSTORE_HPP
#include "Constants.hpp"
#include "RuntimeEnvironment.hpp"
#include "Containers.hpp"
#include "Certificate.hpp"
#include "Blob.hpp"
#include "SHA512.hpp"
#include "SharedPtr.hpp"
#include "Identity.hpp"
#include "Fingerprint.hpp"
#include "Mutex.hpp"
#include "Peer.hpp"
namespace ZeroTier {
/**
* Certificate store and chain validator
*/
class TrustStore
{
public:
/**
* An entry in the node certificate trust store
*/
class Entry
{
friend class SharedPtr< TrustStore::Entry >;
friend class SharedPtr< const TrustStore::Entry >;
friend class TrustStore;
private:
ZT_INLINE Entry(const Certificate &cert, const unsigned int lt) noexcept:
m_certificate(cert),
m_localTrust(lt)
{}
public:
ZT_INLINE const Certificate &certificate() const noexcept
{ return m_certificate; }
ZT_INLINE unsigned int localTrust() const noexcept
{ return m_localTrust.load(std::memory_order_relaxed); }
private:
Certificate m_certificate;
std::atomic< unsigned int > m_localTrust;
std::atomic< int > __refCount;
};
TrustStore();
~TrustStore();
/**
* Get certificate by certificate serial number
*
* @param serial SHA384 hash of certificate
* @return Entry or empty/nil if not found
*/
SharedPtr< const Entry > get(const SHA384Hash &serial) const;
/**
* Get current root peers based on root-enumerating certs in trust store
*
* Root peers are created or obtained via this node's Topology. This should
* never be called while relevant data structures in Topology are locked.
*
* Locators in root peers are also updated if the locator present in the
* certificate is valid and newer.
*
* @param tPtr Caller pointer
* @param RR Runtime environment
* @return All roots (sort order undefined)
*/
Vector< SharedPtr< Peer > > roots(void *tPtr, const RuntimeEnvironment *RR);
/**
* @return All certificates in asecending sort order by serial
*/
Vector< SharedPtr< const Entry > > all() const;
/**
* Add a certificate
*
* A copy is made so it's fine if the original is freed after this call.
*
* IMPORTANT: The caller MUST also call update() after calling add() one or
* more times to actually add and revalidate certificates and their signature
* chains.
*
* @param cert Certificate to add
*/
void add(const Certificate &cert, unsigned int localTrust);
/**
* Validate all certificates and their certificate chains
*
* This also processes any certificates added with add() since the last call to update().
*
* @param clock Current time in milliseconds since epoch
* @param purge If non-NULL, purge rejected certificates and return them in this vector (vector should be empty)
*/
void update(int64_t clock, Vector< std::pair< SharedPtr<Entry>, ZT_CertificateError > > *purge);
/**
* Get a copy of the current rejected certificate set.
*
* @return Rejected certificates
*/
Vector< std::pair< SharedPtr<Entry>, ZT_CertificateError > > rejects() const;
private:
Map< SHA384Hash, SharedPtr< Entry > > m_bySerial;
Map< Vector< uint8_t >, SharedPtr< Entry > > m_bySubjectUniqueId;
Map< Fingerprint, Vector< SharedPtr< Entry > > > m_bySubjectIdentity;
ForwardList< SharedPtr< Entry > > m_addQueue;
Map< SharedPtr< Entry >, ZT_CertificateError > m_rejected;
RWMutex m_lock;
};
} // namespace ZeroTier
#endif

View file

@ -322,6 +322,11 @@ typedef struct
*/
#define ZT_CERTIFICATE_MAX_STRING_LENGTH 127
/**
* Maximum certificate path length to CA (a sanity limit value)
*/
#define ZT_CERTIFICATE_MAX_PATH_LENGTH 256
/**
* Certificate is a root CA (local trust flag)
*/