mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-25 16:36:54 +02:00
Attic cleaning, some work on certs, etc.
This commit is contained in:
parent
4a60ae5736
commit
11d367d5ec
63 changed files with 532 additions and 8263 deletions
|
@ -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()
|
|
@ -1,6 +0,0 @@
|
|||
echo off
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G "MinGW Makefiles"
|
||||
make -j4
|
||||
cd ..
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
*/
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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=
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 }
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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); }
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
28
core/OS.hpp
28
core/OS.hpp
|
@ -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__
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)); }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
218
core/TrustStore.cpp
Normal 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 > ¤t = 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
138
core/TrustStore.hpp
Normal 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
|
|
@ -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)
|
||||
*/
|
||||
|
|
Loading…
Add table
Reference in a new issue