mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-25 08:27:39 +02:00
cleanup
This commit is contained in:
parent
71904d1453
commit
90469fcb2b
47 changed files with 10 additions and 7093 deletions
|
@ -7,7 +7,6 @@ pub struct AesCtr(gcrypt::cipher::Cipher);
|
|||
impl AesCtr {
|
||||
/// Construct a new AES-CTR cipher.
|
||||
/// Key must be 16, 24, or 32 bytes in length or a panic will occur.
|
||||
#[inline(always)]
|
||||
pub fn new(k: &[u8]) -> Self {
|
||||
if k.len() != 32 && k.len() != 24 && k.len() != 16 {
|
||||
panic!("AES supports 128, 192, or 256 bits keys");
|
||||
|
@ -56,7 +55,6 @@ pub struct AesGmacSiv {
|
|||
impl AesGmacSiv {
|
||||
/// Create a new keyed instance of AES-GMAC-SIV
|
||||
/// The key may be of size 16, 24, or 32 bytes (128, 192, or 256 bits). Any other size will panic.
|
||||
#[inline(always)]
|
||||
pub fn new(k0: &[u8], k1: &[u8]) -> Self {
|
||||
if k0.len() != 32 && k0.len() != 24 && k0.len() != 16 {
|
||||
panic!("AES supports 128, 192, or 256 bits keys");
|
||||
|
|
|
@ -133,7 +133,6 @@ impl AesGmacSiv {
|
|||
/// Create a new keyed instance of AES-GMAC-SIV
|
||||
/// The key may be of size 16, 24, or 32 bytes (128, 192, or 256 bits). Any other size will panic.
|
||||
/// Two keys are required: one for GMAC and one for AES-CTR.
|
||||
#[inline(always)]
|
||||
pub fn new(k0: &[u8], k1: &[u8]) -> Self {
|
||||
if k0.len() != 32 && k0.len() != 24 && k0.len() != 16 {
|
||||
panic!("AES supports 128, 192, or 256 bits keys");
|
||||
|
|
|
@ -40,7 +40,6 @@ pub struct AesCtr(Vec<u8>, Option<Crypter>);
|
|||
impl AesCtr {
|
||||
/// Construct a new AES-CTR cipher.
|
||||
/// Key must be 16, 24, or 32 bytes in length or a panic will occur.
|
||||
#[inline(always)]
|
||||
pub fn new(k: &[u8]) -> Self {
|
||||
if k.len() != 32 && k.len() != 24 && k.len() != 16 {
|
||||
panic!("AES supports 128, 192, or 256 bits keys");
|
||||
|
@ -82,7 +81,6 @@ pub struct AesGmacSiv {
|
|||
impl AesGmacSiv {
|
||||
/// Create a new keyed instance of AES-GMAC-SIV
|
||||
/// The key may be of size 16, 24, or 32 bytes (128, 192, or 256 bits). Any other size will panic.
|
||||
#[inline(always)]
|
||||
pub fn new(k0: &[u8], k1: &[u8]) -> Self {
|
||||
if k0.len() != 32 && k0.len() != 24 && k0.len() != 16 {
|
||||
panic!("AES supports 128, 192, or 256 bits keys");
|
||||
|
|
|
@ -11,10 +11,10 @@ mod impl_openssl;
|
|||
pub use impl_macos::{AesCtr, AesGmacSiv};
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "ios", target_arch = "s390x", target_arch = "powerpc64le", target_arch = "powerpc64")))]
|
||||
pub use impl_gcrypt::AesGmacSiv;
|
||||
pub use impl_gcrypt::{AesCtr, AesGmacSiv};
|
||||
|
||||
#[cfg(all(not(any(target_os = "macos", target_os = "ios")), any(target_arch = "s390x", target_arch = "powerpc64le", target_arch = "powerpc64")))]
|
||||
pub use impl_openssl::AesGmacSiv;
|
||||
pub use impl_openssl::{AesCtr, AesGmacSiv};
|
||||
|
||||
pub(crate) const ZEROES: [u8; 16] = [0_u8; 16];
|
||||
|
||||
|
|
|
@ -4,9 +4,8 @@ use std::sync::atomic::{AtomicI64, Ordering};
|
|||
pub struct IntervalGate<const FREQ: i64>(i64);
|
||||
|
||||
impl<const FREQ: i64> Default for IntervalGate<FREQ> {
|
||||
fn default() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn default() -> Self { Self(0) }
|
||||
}
|
||||
|
||||
impl<const FREQ: i64> IntervalGate<FREQ> {
|
||||
|
@ -31,6 +30,7 @@ impl<const FREQ: i64> IntervalGate<FREQ> {
|
|||
pub struct AtomicIntervalGate<const FREQ: i64>(AtomicI64);
|
||||
|
||||
impl<const FREQ: i64> Default for AtomicIntervalGate<FREQ> {
|
||||
#[inline(always)]
|
||||
fn default() -> Self { Self(AtomicI64::new(0)) }
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ impl<O, F: PoolFactory<O>> AsMut<O> for Pooled<O, F> {
|
|||
}
|
||||
|
||||
impl<O, F: PoolFactory<O>> Drop for Pooled<O, F> {
|
||||
#[inline(always)]
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let p = Weak::upgrade(&self.0.as_ref().return_pool);
|
||||
|
@ -112,6 +113,7 @@ impl<O, F: PoolFactory<O>> Pool<O, F> {
|
|||
}
|
||||
|
||||
/// Get a pooled object, or allocate one if the pool is empty.
|
||||
#[inline(always)]
|
||||
pub fn get(&self) -> Pooled<O, F> {
|
||||
unsafe {
|
||||
Pooled::<O, F>(self.0.1.lock().pop().unwrap_or_else(|| {
|
||||
|
|
|
@ -28,6 +28,7 @@ impl<const L: usize> Buffer<L> {
|
|||
pub fn new() -> Self { Self(0, [0_u8; L]) }
|
||||
|
||||
/// Get a Buffer initialized with a copy of a byte slice.
|
||||
#[inline(always)]
|
||||
pub fn from_bytes(b: &[u8]) -> std::io::Result<Self> {
|
||||
let l = b.len();
|
||||
if l <= L {
|
||||
|
|
|
@ -15,6 +15,7 @@ pub(crate) struct FragmentedPacket {
|
|||
}
|
||||
|
||||
impl FragmentedPacket {
|
||||
#[inline(always)]
|
||||
pub fn new(ts: i64) -> Self {
|
||||
Self {
|
||||
ts_ticks: ts,
|
||||
|
|
|
@ -36,6 +36,7 @@ impl MAC {
|
|||
#[inline(always)]
|
||||
pub fn to_u64(&self) -> u64 { self.0.get() }
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn marshal<const BL: usize>(&self, buf: &mut Buffer<BL>) -> std::io::Result<()> {
|
||||
buf.append_and_init_bytes_fixed(|b: &mut [u8; 6]| {
|
||||
let i = self.0.get();
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
project(zt_osdep)
|
||||
|
||||
set(src
|
||||
# ManagedRoute.cpp
|
||||
OSUtils.cpp
|
||||
rust-osdep.cpp
|
||||
)
|
||||
|
||||
set(headers
|
||||
# ManagedRoute.hpp
|
||||
OSUtils.hpp
|
||||
Thread.hpp
|
||||
rust-osdep.h
|
||||
)
|
||||
|
||||
#if(WIN32)
|
||||
# set(src ${src} WindowsEthernetTap.cpp)
|
||||
# set(headers ${headers} WindowsEthernetTap.hpp)
|
||||
#elseif(UNIX)
|
||||
# if(CMAKE_SYSTEM_NAME MATCHES "BSD")
|
||||
# set(src ${src} BSDEthernetTap.cpp)
|
||||
# set(headers ${headers} BSDEthernetTap.hpp)
|
||||
# endif()
|
||||
#
|
||||
# if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
# set(src ${src} freebsd_getifmaddrs.c)
|
||||
# set(headers ${headers} freebsd_getifmaddrs.h)
|
||||
# endif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
#endif(WIN32)
|
||||
|
||||
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,592 +0,0 @@
|
|||
/*
|
||||
* Copyright (c)2019 ZeroTier, Inc.
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file in the project's root directory.
|
||||
*
|
||||
* Change Date: 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 "../core/Constants.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#include <IPHlpApi.h>
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <netioapi.h>
|
||||
#endif
|
||||
|
||||
#ifdef __UNIX_LIKE__
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#ifndef ZT_SDK
|
||||
#include <net/route.h>
|
||||
#endif
|
||||
#include <net/if.h>
|
||||
#ifdef __BSD__
|
||||
#include <net/if_dl.h>
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
|
||||
#include "ManagedRoute.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#ifdef __LINUX__
|
||||
#include "LinuxNetLink.hpp"
|
||||
#endif
|
||||
|
||||
#define ZT_BSD_ROUTE_CMD "/sbin/route"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
namespace {
|
||||
|
||||
// Fork a target into two more specific targets e.g. 0.0.0.0/0 -> 0.0.0.0/1, 128.0.0.0/1
|
||||
// If the target is already maximally-specific, 'right' will be unchanged and 'left' will be 't'
|
||||
static void _forkTarget(const InetAddress& t, InetAddress& left, InetAddress& right)
|
||||
{
|
||||
const unsigned int bits = t.netmaskBits() + 1;
|
||||
left = t;
|
||||
if (t.ss_family == AF_INET) {
|
||||
if (bits <= 32) {
|
||||
left.setPort(bits);
|
||||
right = t;
|
||||
reinterpret_cast<struct sockaddr_in*>(&right)->sin_addr.s_addr ^= Utils::hton((uint32_t)(1 << (32 - bits)));
|
||||
right.setPort(bits);
|
||||
}
|
||||
else {
|
||||
right.zero();
|
||||
}
|
||||
}
|
||||
else if (t.ss_family == AF_INET6) {
|
||||
if (bits <= 128) {
|
||||
left.setPort(bits);
|
||||
right = t;
|
||||
uint8_t* b = reinterpret_cast<uint8_t*>(reinterpret_cast<struct sockaddr_in6*>(&right)->sin6_addr.s6_addr);
|
||||
b[bits / 8] ^= 1 << (8 - (bits % 8));
|
||||
right.setPort(bits);
|
||||
}
|
||||
else {
|
||||
right.zero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct _RTE {
|
||||
InetAddress target;
|
||||
InetAddress via;
|
||||
char device[128];
|
||||
int metric;
|
||||
bool ifscope;
|
||||
};
|
||||
|
||||
#ifdef __BSD__ // ------------------------------------------------------------
|
||||
#define ZT_ROUTING_SUPPORT_FOUND 1
|
||||
|
||||
#ifndef ZT_SDK
|
||||
static std::vector<_RTE> _getRTEs(const InetAddress& target, bool contains)
|
||||
{
|
||||
std::vector<_RTE> rtes;
|
||||
int mib[6];
|
||||
size_t needed;
|
||||
|
||||
mib[0] = CTL_NET;
|
||||
mib[1] = PF_ROUTE;
|
||||
mib[2] = 0;
|
||||
mib[3] = 0;
|
||||
mib[4] = NET_RT_DUMP;
|
||||
mib[5] = 0;
|
||||
if (! sysctl(mib, 6, NULL, &needed, NULL, 0)) {
|
||||
if (needed <= 0)
|
||||
return rtes;
|
||||
|
||||
char* buf = (char*)::malloc(needed);
|
||||
if (buf) {
|
||||
if (! sysctl(mib, 6, buf, &needed, NULL, 0)) {
|
||||
struct rt_msghdr* rtm;
|
||||
for (char *next = buf, *end = buf + needed; next < end;) {
|
||||
rtm = (struct rt_msghdr*)next;
|
||||
char* saptr = (char*)(rtm + 1);
|
||||
char* saend = next + rtm->rtm_msglen;
|
||||
|
||||
InetAddress sa_t, sa_v;
|
||||
int deviceIndex = -9999;
|
||||
|
||||
if (((rtm->rtm_flags & RTF_LLINFO) == 0) && ((rtm->rtm_flags & RTF_HOST) == 0) && ((rtm->rtm_flags & RTF_UP) != 0) && ((rtm->rtm_flags & RTF_MULTICAST) == 0)) {
|
||||
int which = 0;
|
||||
while (saptr < saend) {
|
||||
struct sockaddr* sa = (struct sockaddr*)saptr;
|
||||
unsigned int salen = sa->sa_len;
|
||||
if (! salen)
|
||||
break;
|
||||
|
||||
// Skip missing fields in rtm_addrs bit field
|
||||
while ((rtm->rtm_addrs & 1) == 0) {
|
||||
rtm->rtm_addrs >>= 1;
|
||||
++which;
|
||||
if (which > 6)
|
||||
break;
|
||||
}
|
||||
if (which > 6)
|
||||
break;
|
||||
|
||||
rtm->rtm_addrs >>= 1;
|
||||
switch (which++) {
|
||||
case 0:
|
||||
// printf("RTA_DST\n");
|
||||
if (sa->sa_family == AF_INET6) {
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa;
|
||||
if ((sin6->sin6_addr.s6_addr[0] == 0xfe) && ((sin6->sin6_addr.s6_addr[1] & 0xc0) == 0x80)) {
|
||||
// BSD uses this fucking strange in-band signaling method to encode device
|
||||
// scope IDs for IPv6 addresses... probably a holdover from very early
|
||||
// versions of the spec.
|
||||
unsigned int interfaceIndex = ((((unsigned int)sin6->sin6_addr.s6_addr[2]) << 8) & 0xff) | (((unsigned int)sin6->sin6_addr.s6_addr[3]) & 0xff);
|
||||
sin6->sin6_addr.s6_addr[2] = 0;
|
||||
sin6->sin6_addr.s6_addr[3] = 0;
|
||||
if (! sin6->sin6_scope_id)
|
||||
sin6->sin6_scope_id = interfaceIndex;
|
||||
}
|
||||
}
|
||||
sa_t = *sa;
|
||||
break;
|
||||
case 1:
|
||||
// printf("RTA_GATEWAY\n");
|
||||
switch (sa->sa_family) {
|
||||
case AF_LINK:
|
||||
deviceIndex = (int)((const struct sockaddr_dl*)sa)->sdl_index;
|
||||
break;
|
||||
case AF_INET:
|
||||
case AF_INET6:
|
||||
sa_v = *sa;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 2: {
|
||||
// printf("RTA_NETMASK\n");
|
||||
if (sa_t.ss_family == AF_INET6) {
|
||||
salen = sizeof(struct sockaddr_in6);
|
||||
unsigned int bits = 0;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
unsigned char c = (unsigned char)((const struct sockaddr_in6*)sa)->sin6_addr.s6_addr[i];
|
||||
if (c == 0xff)
|
||||
bits += 8;
|
||||
else
|
||||
break;
|
||||
}
|
||||
sa_t.setPort(bits);
|
||||
}
|
||||
else if (sa_t.ss_family == AF_INET) {
|
||||
salen = sizeof(struct sockaddr_in);
|
||||
sa_t.setPort((unsigned int)Utils::countBits((uint32_t)((const struct sockaddr_in*)sa)->sin_addr.s_addr));
|
||||
}
|
||||
} break;
|
||||
/*
|
||||
case 3:
|
||||
//printf("RTA_GENMASK\n");
|
||||
break;
|
||||
case 4:
|
||||
//printf("RTA_IFP\n");
|
||||
break;
|
||||
case 5:
|
||||
//printf("RTA_IFA\n");
|
||||
break;
|
||||
case 6:
|
||||
//printf("RTA_AUTHOR\n");
|
||||
break;
|
||||
*/
|
||||
}
|
||||
|
||||
saptr += salen;
|
||||
}
|
||||
|
||||
if (((contains) && (sa_t.containsAddress(target))) || (sa_t == target)) {
|
||||
rtes.push_back(_RTE());
|
||||
rtes.back().target = sa_t;
|
||||
rtes.back().via = sa_v;
|
||||
if (deviceIndex >= 0) {
|
||||
if_indextoname(deviceIndex, rtes.back().device);
|
||||
}
|
||||
else {
|
||||
rtes.back().device[0] = (char)0;
|
||||
}
|
||||
rtes.back().metric = ((int)rtm->rtm_rmx.rmx_hopcount < 0) ? 0 : (int)rtm->rtm_rmx.rmx_hopcount;
|
||||
}
|
||||
}
|
||||
|
||||
next = saend;
|
||||
}
|
||||
}
|
||||
|
||||
::free(buf);
|
||||
}
|
||||
}
|
||||
|
||||
return rtes;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void _routeCmd(const char* op, const InetAddress& target, const InetAddress& via, const char* ifscope, const char* localInterface)
|
||||
{
|
||||
// char f1[1024],f2[1024]; printf("%s %s %s %s
|
||||
// %s\n",op,target.toString(f1),via.toString(f2),ifscope,localInterface);
|
||||
long p = (long)fork();
|
||||
if (p > 0) {
|
||||
int exitcode = -1;
|
||||
::waitpid(p, &exitcode, 0);
|
||||
}
|
||||
else if (p == 0) {
|
||||
::close(STDOUT_FILENO);
|
||||
::close(STDERR_FILENO);
|
||||
char ttmp[64];
|
||||
char iptmp[64];
|
||||
if (via) {
|
||||
if ((ifscope) && (ifscope[0])) {
|
||||
::execl(ZT_BSD_ROUTE_CMD, ZT_BSD_ROUTE_CMD, op, "-ifscope", ifscope, ((target.ss_family == AF_INET6) ? "-inet6" : "-inet"), target.toString(ttmp), via.toIpString(iptmp), (const char*)0);
|
||||
}
|
||||
else {
|
||||
::execl(ZT_BSD_ROUTE_CMD, ZT_BSD_ROUTE_CMD, op, ((target.ss_family == AF_INET6) ? "-inet6" : "-inet"), target.toString(ttmp), via.toIpString(iptmp), (const char*)0);
|
||||
}
|
||||
}
|
||||
else if ((localInterface) && (localInterface[0])) {
|
||||
if ((ifscope) && (ifscope[0])) {
|
||||
::execl(ZT_BSD_ROUTE_CMD, ZT_BSD_ROUTE_CMD, op, "-ifscope", ifscope, ((target.ss_family == AF_INET6) ? "-inet6" : "-inet"), target.toString(ttmp), "-interface", localInterface, (const char*)0);
|
||||
}
|
||||
else {
|
||||
::execl(ZT_BSD_ROUTE_CMD, ZT_BSD_ROUTE_CMD, op, ((target.ss_family == AF_INET6) ? "-inet6" : "-inet"), target.toString(ttmp), "-interface", localInterface, (const char*)0);
|
||||
}
|
||||
}
|
||||
::_exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __BSD__ ------------------------------------------------------------
|
||||
|
||||
#ifdef __LINUX__ // ----------------------------------------------------------
|
||||
#define ZT_ROUTING_SUPPORT_FOUND 1
|
||||
|
||||
// This has been replaced by LinuxNetLink
|
||||
|
||||
#endif // __LINUX__ ----------------------------------------------------------
|
||||
|
||||
#ifdef __WINDOWS__ // --------------------------------------------------------
|
||||
#define ZT_ROUTING_SUPPORT_FOUND 1
|
||||
|
||||
static bool _winRoute(bool del, const NET_LUID& interfaceLuid, const NET_IFINDEX& interfaceIndex, const InetAddress& target, const InetAddress& via)
|
||||
{
|
||||
MIB_IPFORWARD_ROW2 rtrow;
|
||||
InitializeIpForwardEntry(&rtrow);
|
||||
rtrow.InterfaceLuid.Value = interfaceLuid.Value;
|
||||
rtrow.InterfaceIndex = interfaceIndex;
|
||||
if (target.ss_family == AF_INET) {
|
||||
rtrow.DestinationPrefix.Prefix.si_family = AF_INET;
|
||||
rtrow.DestinationPrefix.Prefix.Ipv4.sin_family = AF_INET;
|
||||
rtrow.DestinationPrefix.Prefix.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast<const struct sockaddr_in*>(&target)->sin_addr.S_un.S_addr;
|
||||
if (via.ss_family == AF_INET) {
|
||||
rtrow.NextHop.si_family = AF_INET;
|
||||
rtrow.NextHop.Ipv4.sin_family = AF_INET;
|
||||
rtrow.NextHop.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast<const struct sockaddr_in*>(&via)->sin_addr.S_un.S_addr;
|
||||
}
|
||||
}
|
||||
else if (target.ss_family == AF_INET6) {
|
||||
rtrow.DestinationPrefix.Prefix.si_family = AF_INET6;
|
||||
rtrow.DestinationPrefix.Prefix.Ipv6.sin6_family = AF_INET6;
|
||||
memcpy(rtrow.DestinationPrefix.Prefix.Ipv6.sin6_addr.u.Byte, reinterpret_cast<const struct sockaddr_in6*>(&target)->sin6_addr.u.Byte, 16);
|
||||
if (via.ss_family == AF_INET6) {
|
||||
rtrow.NextHop.si_family = AF_INET6;
|
||||
rtrow.NextHop.Ipv6.sin6_family = AF_INET6;
|
||||
memcpy(rtrow.NextHop.Ipv6.sin6_addr.u.Byte, reinterpret_cast<const struct sockaddr_in6*>(&via)->sin6_addr.u.Byte, 16);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
rtrow.DestinationPrefix.PrefixLength = target.netmaskBits();
|
||||
rtrow.SitePrefixLength = rtrow.DestinationPrefix.PrefixLength;
|
||||
rtrow.ValidLifetime = 0xffffffff;
|
||||
rtrow.PreferredLifetime = 0xffffffff;
|
||||
rtrow.Metric = -1;
|
||||
rtrow.Protocol = MIB_IPPROTO_NETMGMT;
|
||||
rtrow.Loopback = FALSE;
|
||||
rtrow.AutoconfigureAddress = FALSE;
|
||||
rtrow.Publish = FALSE;
|
||||
rtrow.Immortal = FALSE;
|
||||
rtrow.Age = 0;
|
||||
rtrow.Origin = NlroManual;
|
||||
if (del) {
|
||||
return (DeleteIpForwardEntry2(&rtrow) == NO_ERROR);
|
||||
}
|
||||
else {
|
||||
NTSTATUS r = CreateIpForwardEntry2(&rtrow);
|
||||
if (r == NO_ERROR) {
|
||||
return true;
|
||||
}
|
||||
else if (r == ERROR_OBJECT_ALREADY_EXISTS) {
|
||||
return (SetIpForwardEntry2(&rtrow) == NO_ERROR);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool _winHasRoute(const NET_LUID& interfaceLuid, const NET_IFINDEX& interfaceIndex, const InetAddress& target, const InetAddress& via)
|
||||
{
|
||||
MIB_IPFORWARD_ROW2 rtrow;
|
||||
InitializeIpForwardEntry(&rtrow);
|
||||
rtrow.InterfaceLuid.Value = interfaceLuid.Value;
|
||||
rtrow.InterfaceIndex = interfaceIndex;
|
||||
if (target.ss_family == AF_INET) {
|
||||
rtrow.DestinationPrefix.Prefix.si_family = AF_INET;
|
||||
rtrow.DestinationPrefix.Prefix.Ipv4.sin_family = AF_INET;
|
||||
rtrow.DestinationPrefix.Prefix.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast<const struct sockaddr_in*>(&target)->sin_addr.S_un.S_addr;
|
||||
if (via.ss_family == AF_INET) {
|
||||
rtrow.NextHop.si_family = AF_INET;
|
||||
rtrow.NextHop.Ipv4.sin_family = AF_INET;
|
||||
rtrow.NextHop.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast<const struct sockaddr_in*>(&via)->sin_addr.S_un.S_addr;
|
||||
}
|
||||
}
|
||||
else if (target.ss_family == AF_INET6) {
|
||||
rtrow.DestinationPrefix.Prefix.si_family = AF_INET6;
|
||||
rtrow.DestinationPrefix.Prefix.Ipv6.sin6_family = AF_INET6;
|
||||
memcpy(rtrow.DestinationPrefix.Prefix.Ipv6.sin6_addr.u.Byte, reinterpret_cast<const struct sockaddr_in6*>(&target)->sin6_addr.u.Byte, 16);
|
||||
if (via.ss_family == AF_INET6) {
|
||||
rtrow.NextHop.si_family = AF_INET6;
|
||||
rtrow.NextHop.Ipv6.sin6_family = AF_INET6;
|
||||
memcpy(rtrow.NextHop.Ipv6.sin6_addr.u.Byte, reinterpret_cast<const struct sockaddr_in6*>(&via)->sin6_addr.u.Byte, 16);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
rtrow.DestinationPrefix.PrefixLength = target.netmaskBits();
|
||||
rtrow.SitePrefixLength = rtrow.DestinationPrefix.PrefixLength;
|
||||
return (GetIpForwardEntry2(&rtrow) == NO_ERROR);
|
||||
}
|
||||
|
||||
#endif // __WINDOWS__ --------------------------------------------------------
|
||||
|
||||
#ifndef ZT_ROUTING_SUPPORT_FOUND
|
||||
#error \
|
||||
"ManagedRoute.cpp has no support for managing routes on this platform! You'll need to check and see if one of the existing ones will work and make sure proper defines are set, or write one. Please do a GitHub pull request if you do this for a new OS."
|
||||
#endif
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
ManagedRoute::ManagedRoute(const InetAddress& target, const InetAddress& via, const InetAddress& src, const char* device)
|
||||
{
|
||||
_target = target;
|
||||
_via = via;
|
||||
_src = src;
|
||||
|
||||
if (_via.ss_family == AF_INET) {
|
||||
_via.setPort(32);
|
||||
}
|
||||
else if (_via.ss_family == AF_INET6) {
|
||||
_via.setPort(128);
|
||||
}
|
||||
|
||||
if (_src.ss_family == AF_INET) {
|
||||
_src.setPort(32);
|
||||
}
|
||||
else if (_src.ss_family == AF_INET6) {
|
||||
_src.setPort(128);
|
||||
}
|
||||
|
||||
Utils::scopy(_device, sizeof(_device), device);
|
||||
_systemDevice[0] = (char)0;
|
||||
}
|
||||
|
||||
ManagedRoute::~ManagedRoute()
|
||||
{
|
||||
this->remove();
|
||||
}
|
||||
|
||||
/* Linux NOTE: for default route override, some Linux distributions will
|
||||
* require a change to the rp_filter parameter. A value of '1' will prevent
|
||||
* default route override from working properly.
|
||||
*
|
||||
* sudo sysctl -w net.ipv4.conf.all.rp_filter=2
|
||||
*
|
||||
* Add to /etc/sysctl.conf or /etc/sysctl.d/... to make permanent.
|
||||
*
|
||||
* This is true of CentOS/RHEL 6+ and possibly others. This is because
|
||||
* Linux default route override implies asymmetric routes, which then
|
||||
* trigger Linux's "martian packet" filter. */
|
||||
|
||||
#ifndef ZT_SDK
|
||||
bool ManagedRoute::sync()
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
NET_LUID interfaceLuid;
|
||||
interfaceLuid.Value = (ULONG64)Utils::hexStrToU64(_device); // on Windows we use the hex LUID as the "interface name" for ManagedRoute
|
||||
NET_IFINDEX interfaceIndex = -1;
|
||||
if (ConvertInterfaceLuidToIndex(&interfaceLuid, &interfaceIndex) != NO_ERROR)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
InetAddress leftt, rightt;
|
||||
if (_target.netmaskBits() == 0) // bifurcate only the default route
|
||||
_forkTarget(_target, leftt, rightt);
|
||||
else
|
||||
leftt = _target;
|
||||
|
||||
#ifdef __BSD__ // ------------------------------------------------------------
|
||||
|
||||
// Find lowest metric system route that this route should override (if any)
|
||||
InetAddress newSystemVia;
|
||||
char newSystemDevice[128];
|
||||
newSystemDevice[0] = (char)0;
|
||||
int systemMetric = 9999999;
|
||||
std::vector<_RTE> rtes(_getRTEs(_target, false));
|
||||
for (std::vector<_RTE>::iterator r(rtes.begin()); r != rtes.end(); ++r) {
|
||||
if (r->via) {
|
||||
if (((! newSystemVia) || (r->metric < systemMetric)) && (strcmp(r->device, _device) != 0)) {
|
||||
newSystemVia = r->via;
|
||||
Utils::scopy(newSystemDevice, sizeof(newSystemDevice), r->device);
|
||||
systemMetric = r->metric;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get device corresponding to route if we don't have that already
|
||||
if ((newSystemVia) && (! newSystemDevice[0])) {
|
||||
rtes = _getRTEs(newSystemVia, true);
|
||||
for (std::vector<_RTE>::iterator r(rtes.begin()); r != rtes.end(); ++r) {
|
||||
if ((r->device[0]) && (strcmp(r->device, _device) != 0)) {
|
||||
Utils::scopy(newSystemDevice, sizeof(newSystemDevice), r->device);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! newSystemDevice[0])
|
||||
newSystemVia.zero();
|
||||
|
||||
// Shadow system route if it exists, also delete any obsolete shadows
|
||||
// and replace them with the new state. sync() is called periodically to
|
||||
// allow us to do that if underlying connectivity changes.
|
||||
if ((_systemVia != newSystemVia) || (strcmp(_systemDevice, newSystemDevice) != 0)) {
|
||||
if (_systemVia) {
|
||||
_routeCmd("delete", leftt, _systemVia, _systemDevice, (const char*)0);
|
||||
if (rightt)
|
||||
_routeCmd("delete", rightt, _systemVia, _systemDevice, (const char*)0);
|
||||
}
|
||||
|
||||
_systemVia = newSystemVia;
|
||||
Utils::scopy(_systemDevice, sizeof(_systemDevice), newSystemDevice);
|
||||
|
||||
if (_systemVia) {
|
||||
_routeCmd("add", leftt, _systemVia, _systemDevice, (const char*)0);
|
||||
_routeCmd("change", leftt, _systemVia, _systemDevice, (const char*)0);
|
||||
if (rightt) {
|
||||
_routeCmd("add", rightt, _systemVia, _systemDevice, (const char*)0);
|
||||
_routeCmd("change", rightt, _systemVia, _systemDevice, (const char*)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! _applied.count(leftt)) {
|
||||
_applied[leftt] = false; // not ifscoped
|
||||
_routeCmd("add", leftt, _via, (const char*)0, (_via) ? (const char*)0 : _device);
|
||||
_routeCmd("change", leftt, _via, (const char*)0, (_via) ? (const char*)0 : _device);
|
||||
}
|
||||
if ((rightt) && (! _applied.count(rightt))) {
|
||||
_applied[rightt] = false; // not ifscoped
|
||||
_routeCmd("add", rightt, _via, (const char*)0, (_via) ? (const char*)0 : _device);
|
||||
_routeCmd("change", rightt, _via, (const char*)0, (_via) ? (const char*)0 : _device);
|
||||
}
|
||||
|
||||
#endif // __BSD__ ------------------------------------------------------------
|
||||
|
||||
#ifdef __LINUX__ // ----------------------------------------------------------
|
||||
|
||||
const char* const devptr = (_via) ? (const char*)0 : _device;
|
||||
if ((leftt) && (! LinuxNetLink::getInstance().routeIsSet(leftt, _via, _src, devptr))) {
|
||||
_applied[leftt] = false; // boolean unused
|
||||
LinuxNetLink::getInstance().addRoute(leftt, _via, _src, devptr);
|
||||
}
|
||||
if ((rightt) && (! LinuxNetLink::getInstance().routeIsSet(rightt, _via, _src, devptr))) {
|
||||
_applied[rightt] = false; // boolean unused
|
||||
LinuxNetLink::getInstance().addRoute(rightt, _via, _src, devptr);
|
||||
}
|
||||
|
||||
#endif // __LINUX__ ----------------------------------------------------------
|
||||
|
||||
#ifdef __WINDOWS__ // --------------------------------------------------------
|
||||
|
||||
if ((! _applied.count(leftt)) || (! _winHasRoute(interfaceLuid, interfaceIndex, leftt, _via))) {
|
||||
_applied[leftt] = false; // boolean unused
|
||||
_winRoute(false, interfaceLuid, interfaceIndex, leftt, _via);
|
||||
}
|
||||
if ((rightt) && ((! _applied.count(rightt)) || (! _winHasRoute(interfaceLuid, interfaceIndex, rightt, _via)))) {
|
||||
_applied[rightt] = false; // boolean unused
|
||||
_winRoute(false, interfaceLuid, interfaceIndex, rightt, _via);
|
||||
}
|
||||
|
||||
#endif // __WINDOWS__ --------------------------------------------------------
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ManagedRoute::remove()
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
NET_LUID interfaceLuid;
|
||||
interfaceLuid.Value = (ULONG64)Utils::hexStrToU64(_device); // on Windows we use the hex LUID as the "interface name" for ManagedRoute
|
||||
NET_IFINDEX interfaceIndex = -1;
|
||||
if (ConvertInterfaceLuidToIndex(&interfaceLuid, &interfaceIndex) != NO_ERROR)
|
||||
return;
|
||||
#endif
|
||||
|
||||
#ifdef __BSD__
|
||||
if (_systemVia) {
|
||||
InetAddress leftt, rightt;
|
||||
_forkTarget(_target, leftt, rightt);
|
||||
_routeCmd("delete", leftt, _systemVia, _systemDevice, (const char*)0);
|
||||
if (rightt)
|
||||
_routeCmd("delete", rightt, _systemVia, _systemDevice, (const char*)0);
|
||||
}
|
||||
#endif // __BSD__ ------------------------------------------------------------
|
||||
|
||||
for (std::map<InetAddress, bool>::iterator r(_applied.begin()); r != _applied.end(); ++r) {
|
||||
#ifdef __BSD__ // ------------------------------------------------------------
|
||||
_routeCmd("delete", r->first, _via, r->second ? _device : (const char*)0, (_via) ? (const char*)0 : _device);
|
||||
#endif // __BSD__ ------------------------------------------------------------
|
||||
|
||||
#ifdef __LINUX__ // ----------------------------------------------------------
|
||||
//_routeCmd("del",r->first,_via,(_via) ? (const char *)0 : _device);
|
||||
LinuxNetLink::getInstance().delRoute(r->first, _via, _src, (_via) ? (const char*)0 : _device);
|
||||
#endif // __LINUX__ ----------------------------------------------------------
|
||||
|
||||
#ifdef __WINDOWS__ // --------------------------------------------------------
|
||||
_winRoute(true, interfaceLuid, interfaceIndex, r->first, _via);
|
||||
#endif // __WINDOWS__ --------------------------------------------------------
|
||||
}
|
||||
|
||||
_target.zero();
|
||||
_via.zero();
|
||||
_systemVia.zero();
|
||||
_device[0] = (char)0;
|
||||
_systemDevice[0] = (char)0;
|
||||
_applied.clear();
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* Copyright (c)2019 ZeroTier, Inc.
|
||||
*
|
||||
* Use of this software is governed by the Business Source License included
|
||||
* in the LICENSE.TXT file in the project's root directory.
|
||||
*
|
||||
* Change Date: 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_MANAGEDROUTE_HPP
|
||||
#define ZT_MANAGEDROUTE_HPP
|
||||
|
||||
#include "../node/AtomicCounter.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../node/SharedPtr.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* A ZT-managed route that used C++ RAII semantics to automatically clean itself up on deallocate
|
||||
*/
|
||||
class ManagedRoute {
|
||||
friend class SharedPtr<ManagedRoute>;
|
||||
|
||||
public:
|
||||
ManagedRoute(const InetAddress& target, const InetAddress& via, const InetAddress& src, const char* device);
|
||||
~ManagedRoute();
|
||||
|
||||
/**
|
||||
* Set or update currently set route
|
||||
*
|
||||
* This must be called periodically for routes that shadow others so that
|
||||
* shadow routes can be updated. In some cases it has no effect
|
||||
*
|
||||
* @return True if route add/update was successful
|
||||
*/
|
||||
bool sync();
|
||||
|
||||
/**
|
||||
* Remove and clear this ManagedRoute
|
||||
*
|
||||
* This does nothing if this ManagedRoute is not set or has already been
|
||||
* removed. If this is not explicitly called it is called automatically on
|
||||
* destruct.
|
||||
*/
|
||||
void remove();
|
||||
|
||||
inline const InetAddress& target() const
|
||||
{
|
||||
return _target;
|
||||
}
|
||||
inline const InetAddress& via() const
|
||||
{
|
||||
return _via;
|
||||
}
|
||||
inline const InetAddress& src() const
|
||||
{
|
||||
return _src;
|
||||
}
|
||||
inline const char* device() const
|
||||
{
|
||||
return _device;
|
||||
}
|
||||
|
||||
private:
|
||||
ManagedRoute(const ManagedRoute&)
|
||||
{
|
||||
}
|
||||
inline ManagedRoute& operator=(const ManagedRoute&)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
InetAddress _target;
|
||||
InetAddress _via;
|
||||
InetAddress _src;
|
||||
InetAddress _systemVia; // for route overrides
|
||||
std::map<InetAddress, bool> _applied; // routes currently applied
|
||||
char _device[128];
|
||||
char _systemDevice[128]; // for route overrides
|
||||
|
||||
AtomicCounter __refCount;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
|
@ -1,339 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#include "OSUtils.hpp"
|
||||
|
||||
#include "../core/Constants.hpp"
|
||||
#include "../core/Containers.hpp"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef __WINDOWS__
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#if defined(__GCC__) || defined(__GNUC__)
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
static clock_serv_t _machGetRealtimeClock() noexcept
|
||||
{
|
||||
clock_serv_t c;
|
||||
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &c);
|
||||
return c;
|
||||
}
|
||||
|
||||
static clock_serv_t _machGetMonotonicClock() noexcept
|
||||
{
|
||||
clock_serv_t c;
|
||||
host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &c);
|
||||
return c;
|
||||
}
|
||||
|
||||
clock_serv_t OSUtils::s_machRealtimeClock = _machGetRealtimeClock();
|
||||
clock_serv_t OSUtils::s_machMonotonicClock = _machGetMonotonicClock();
|
||||
|
||||
#endif
|
||||
|
||||
Vector<String> OSUtils::listDirectory(const char* path, bool includeDirectories)
|
||||
{
|
||||
Vector<String> r;
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
HANDLE hFind;
|
||||
WIN32_FIND_DATAA ffd;
|
||||
if ((hFind = FindFirstFileA((String(path) + "\\*").c_str(), &ffd)) != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if ((strcmp(ffd.cFileName, ".")) && (strcmp(ffd.cFileName, "..")) && (((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) || (((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) && (includeDirectories))))
|
||||
r.push_back(String(ffd.cFileName));
|
||||
} while (FindNextFileA(hFind, &ffd));
|
||||
FindClose(hFind);
|
||||
}
|
||||
#else
|
||||
dirent de;
|
||||
dirent* dptr;
|
||||
DIR* d = opendir(path);
|
||||
if (! d)
|
||||
return r;
|
||||
dptr = (struct dirent*)0;
|
||||
for (;;) {
|
||||
if (readdir_r(d, &de, &dptr))
|
||||
break;
|
||||
if (dptr) {
|
||||
if ((strcmp(dptr->d_name, ".") != 0) && (strcmp(dptr->d_name, "..") != 0) && ((dptr->d_type != DT_DIR) || (includeDirectories)))
|
||||
r.push_back(String(dptr->d_name));
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
closedir(d);
|
||||
#endif
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
bool OSUtils::rmDashRf(const char* path)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
HANDLE hFind;
|
||||
WIN32_FIND_DATAA ffd;
|
||||
if ((hFind = FindFirstFileA((String(path) + "\\*").c_str(), &ffd)) != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if ((strcmp(ffd.cFileName, ".") != 0) && (strcmp(ffd.cFileName, "..") != 0)) {
|
||||
if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
||||
if (DeleteFileA((String(path) + ZT_PATH_SEPARATOR_S + ffd.cFileName).c_str()) == FALSE)
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
if (! rmDashRf((String(path) + ZT_PATH_SEPARATOR_S + ffd.cFileName).c_str()))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} while (FindNextFileA(hFind, &ffd));
|
||||
FindClose(hFind);
|
||||
}
|
||||
return (RemoveDirectoryA(path) != FALSE);
|
||||
#else
|
||||
dirent de;
|
||||
dirent* dptr;
|
||||
DIR* d = opendir(path);
|
||||
if (! d)
|
||||
return true;
|
||||
dptr = (struct dirent*)0;
|
||||
for (;;) {
|
||||
if (readdir_r(d, &de, &dptr) != 0)
|
||||
break;
|
||||
if (! dptr)
|
||||
break;
|
||||
if ((strcmp(dptr->d_name, ".") != 0) && (strcmp(dptr->d_name, "..") != 0) && (strlen(dptr->d_name) > 0)) {
|
||||
String p(path);
|
||||
p.push_back(ZT_PATH_SEPARATOR);
|
||||
p.append(dptr->d_name);
|
||||
if (unlink(p.c_str()) != 0) { // unlink first will remove symlinks instead of recursing them
|
||||
if (! rmDashRf(p.c_str()))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
return (rmdir(path) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OSUtils::lockDownFile(const char* path, bool isDir)
|
||||
{
|
||||
#ifdef __UNIX_LIKE__
|
||||
chmod(path, isDir ? 0700 : 0600);
|
||||
#else
|
||||
#ifdef __WINDOWS__
|
||||
{
|
||||
STARTUPINFOA startupInfo;
|
||||
PROCESS_INFORMATION processInfo;
|
||||
|
||||
startupInfo.cb = sizeof(startupInfo);
|
||||
memset(&startupInfo, 0, sizeof(STARTUPINFOA));
|
||||
memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
|
||||
if (CreateProcessA(NULL, (LPSTR)(String("C:\\Windows\\System32\\icacls.exe \"") + path + "\" /inheritance:d /Q").c_str(), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &processInfo)) {
|
||||
WaitForSingleObject(processInfo.hProcess, INFINITE);
|
||||
CloseHandle(processInfo.hProcess);
|
||||
CloseHandle(processInfo.hThread);
|
||||
}
|
||||
|
||||
startupInfo.cb = sizeof(startupInfo);
|
||||
memset(&startupInfo, 0, sizeof(STARTUPINFOA));
|
||||
memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
|
||||
if (CreateProcessA(NULL, (LPSTR)(String("C:\\Windows\\System32\\icacls.exe \"") + path + "\" /remove *S-1-5-32-545 /Q").c_str(), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &processInfo)) {
|
||||
WaitForSingleObject(processInfo.hProcess, INFINITE);
|
||||
CloseHandle(processInfo.hProcess);
|
||||
CloseHandle(processInfo.hThread);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
bool OSUtils::fileExists(const char* path, bool followLinks)
|
||||
{
|
||||
struct stat s;
|
||||
#ifdef __UNIX_LIKE__
|
||||
if (! followLinks)
|
||||
return (lstat(path, &s) == 0);
|
||||
#endif
|
||||
return (stat(path, &s) == 0);
|
||||
}
|
||||
|
||||
bool OSUtils::readFile(const char* path, String& buf)
|
||||
{
|
||||
char tmp[16384];
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (f) {
|
||||
for (;;) {
|
||||
long n = (long)fread(tmp, 1, sizeof(tmp), f);
|
||||
if (n > 0)
|
||||
buf.append(tmp, n);
|
||||
else
|
||||
break;
|
||||
}
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OSUtils::writeFile(const char* path, const void* buf, unsigned int len)
|
||||
{
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (f) {
|
||||
if ((long)fwrite(buf, 1, len, f) != (long)len) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector<String> OSUtils::split(const char* s, const char* const sep, const char* esc, const char* quot)
|
||||
{
|
||||
Vector<String> fields;
|
||||
String buf;
|
||||
|
||||
if (! esc)
|
||||
esc = "";
|
||||
if (! quot)
|
||||
quot = "";
|
||||
|
||||
bool escapeState = false;
|
||||
char quoteState = 0;
|
||||
while (*s) {
|
||||
if (escapeState) {
|
||||
escapeState = false;
|
||||
buf.push_back(*s);
|
||||
}
|
||||
else if (quoteState) {
|
||||
if (*s == quoteState) {
|
||||
quoteState = 0;
|
||||
fields.push_back(buf);
|
||||
buf.clear();
|
||||
}
|
||||
else
|
||||
buf.push_back(*s);
|
||||
}
|
||||
else {
|
||||
const char* quotTmp;
|
||||
if (strchr(esc, *s))
|
||||
escapeState = true;
|
||||
else if ((buf.size() <= 0) && ((quotTmp = strchr(quot, *s))))
|
||||
quoteState = *quotTmp;
|
||||
else if (strchr(sep, *s)) {
|
||||
if (buf.size() > 0) {
|
||||
fields.push_back(buf);
|
||||
buf.clear();
|
||||
} // else skip runs of separators
|
||||
}
|
||||
else
|
||||
buf.push_back(*s);
|
||||
}
|
||||
++s;
|
||||
}
|
||||
|
||||
if (buf.size())
|
||||
fields.push_back(buf);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
ZeroTier::String OSUtils::platformDefaultHomePath()
|
||||
{
|
||||
#ifdef __QNAP__
|
||||
char* cmd = "/sbin/getcfg zerotier Install_Path -f /etc/config/qpkg.conf";
|
||||
char buf[128];
|
||||
FILE* fp;
|
||||
if ((fp = popen(cmd, "r")) == NULL) {
|
||||
printf("Error opening pipe!\n");
|
||||
return NULL;
|
||||
}
|
||||
while (fgets(buf, 128, fp) != NULL) {}
|
||||
if (pclose(fp)) {
|
||||
printf("Command not found or exited with error status\n");
|
||||
return NULL;
|
||||
}
|
||||
String homeDir = String(buf);
|
||||
homeDir.erase(std::remove(homeDir.begin(), homeDir.end(), '\n'), homeDir.end());
|
||||
return homeDir;
|
||||
#endif
|
||||
|
||||
// Check for user-defined environment variable before using defaults
|
||||
#ifdef __WINDOWS__
|
||||
DWORD bufferSize = 65535;
|
||||
ZeroTier::String userDefinedPath;
|
||||
bufferSize = GetEnvironmentVariable("ZEROTIER_HOME", &userDefinedPath[0], bufferSize);
|
||||
if (bufferSize)
|
||||
return userDefinedPath;
|
||||
#else
|
||||
if (const char* userDefinedPath = getenv("ZEROTIER_HOME"))
|
||||
return String(userDefinedPath);
|
||||
#endif
|
||||
|
||||
// Finally, resort to using default paths if no user-defined path was provided
|
||||
#ifdef __UNIX_LIKE__
|
||||
|
||||
#ifdef __APPLE__
|
||||
// /Library/... on Apple
|
||||
return ZeroTier::String("/Library/Application Support/ZeroTier");
|
||||
#else
|
||||
|
||||
#ifdef __BSD__
|
||||
// BSD likes /var/db instead of /var/lib
|
||||
return ZeroTier::String("/var/db/zerotier");
|
||||
#else
|
||||
// Use /var/lib for Linux and other *nix
|
||||
return ZeroTier::String("/var/lib/zerotier");
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#else // not __UNIX_LIKE__
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
// Look up app data folder on Windows, e.g. C:\ProgramData\...
|
||||
char buf[16384];
|
||||
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_COMMON_APPDATA, NULL, 0, buf))) {
|
||||
ZeroTier::String tmp(buf);
|
||||
tmp.append("\\ZeroTier");
|
||||
return tmp;
|
||||
}
|
||||
else {
|
||||
return ZeroTier::String("C:\\ZeroTier");
|
||||
}
|
||||
#else
|
||||
return (ZeroTier::String(ZT_PATH_SEPARATOR_S) + "ZeroTier"); // UNKNOWN PLATFORM
|
||||
#endif
|
||||
|
||||
#endif // __UNIX_LIKE__ or not...
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
|
@ -1,265 +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_OSUTILS_HPP
|
||||
#define ZT_OSUTILS_HPP
|
||||
|
||||
#include "../core/Constants.hpp"
|
||||
#include "../core/Containers.hpp"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifndef __WINDOWS__
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <mach/clock.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* Miscellaneous utility functions and global constants
|
||||
*/
|
||||
class OSUtils {
|
||||
private:
|
||||
#ifdef __APPLE__
|
||||
static clock_serv_t s_machRealtimeClock;
|
||||
static clock_serv_t s_machMonotonicClock;
|
||||
#endif
|
||||
|
||||
public:
|
||||
/**
|
||||
* Variant of snprintf that is portable and throws an exception
|
||||
*
|
||||
* This just wraps the local implementation whatever it's called, while
|
||||
* performing a few other checks and adding exceptions for overflow.
|
||||
*
|
||||
* @param buf Buffer to write to
|
||||
* @param len Length of buffer in bytes
|
||||
* @param fmt Format string
|
||||
* @param ... Format arguments
|
||||
* @throws std::length_error buf[] too short (buf[] will still be left null-terminated)
|
||||
*/
|
||||
static ZT_INLINE unsigned int ztsnprintf(char* buf, unsigned int len, const char* fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int n = (int)vsnprintf(buf, len, fmt, ap);
|
||||
va_end(ap);
|
||||
if ((n >= (int)len) || (n < 0)) {
|
||||
if (len)
|
||||
buf[len - 1] = (char)0;
|
||||
throw std::length_error("buf[] overflow");
|
||||
}
|
||||
return (unsigned int)n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file
|
||||
*
|
||||
* @param path Path to delete
|
||||
* @return True if delete was successful
|
||||
*/
|
||||
static ZT_INLINE bool rm(const char* path)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
return (DeleteFileA(path) != FALSE);
|
||||
#else
|
||||
return (unlink(path) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static ZT_INLINE bool mkdir(const char* path)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
if (::PathIsDirectoryA(path))
|
||||
return true;
|
||||
return (::CreateDirectoryA(path, NULL) == TRUE);
|
||||
#else
|
||||
if (::mkdir(path, 0755) != 0)
|
||||
return (errno == EEXIST);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
static ZT_INLINE bool rename(const char* o, const char* n)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
DeleteFileA(n);
|
||||
return (::rename(o, n) == 0);
|
||||
#else
|
||||
return (::rename(o, n) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* List a directory's contents
|
||||
*
|
||||
* @param path Path to list
|
||||
* @param includeDirectories If true, include directories as well as files
|
||||
* @return Names of files in directory (without path prepended)
|
||||
*/
|
||||
static ZeroTier::Vector<ZeroTier::String> listDirectory(const char* path, bool includeDirectories = false);
|
||||
|
||||
/**
|
||||
* Delete a directory and all its files and subdirectories recursively
|
||||
*
|
||||
* @param path Path to delete
|
||||
* @return True on success
|
||||
*/
|
||||
static bool rmDashRf(const char* path);
|
||||
|
||||
/**
|
||||
* Set modes on a file to something secure
|
||||
*
|
||||
* This locks a file so that only the owner can access it. What it actually
|
||||
* does varies by platform.
|
||||
*
|
||||
* @param path Path to lock
|
||||
* @param isDir True if this is a directory
|
||||
*/
|
||||
static void lockDownFile(const char* path, bool isDir);
|
||||
|
||||
/**
|
||||
* @param path Path to check
|
||||
* @param followLinks Follow links (on platforms with that concept)
|
||||
* @return True if file or directory exists at path location
|
||||
*/
|
||||
static bool fileExists(const char* path, bool followLinks = true);
|
||||
|
||||
/**
|
||||
* @return Current time in milliseconds since epoch
|
||||
*/
|
||||
static ZT_INLINE int64_t now()
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
FILETIME ft;
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
return (((LONGLONG)ft.dwLowDateTime + ((LONGLONG)(ft.dwHighDateTime) << 32)) / 10000LL) - 116444736000000000LL;
|
||||
#else
|
||||
#ifdef __LINUX__
|
||||
timespec ts;
|
||||
#ifdef CLOCK_REALTIME_COARSE
|
||||
clock_gettime(CLOCK_REALTIME_COARSE, &ts);
|
||||
#else
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
#endif
|
||||
return ((1000LL * (int64_t)ts.tv_sec) + ((int64_t)(ts.tv_nsec / 1000000)));
|
||||
#else
|
||||
#ifdef __APPLE__
|
||||
mach_timespec_t mts;
|
||||
clock_get_time(s_machRealtimeClock, &mts);
|
||||
return ((1000LL * (int64_t)mts.tv_sec) + ((int64_t)(mts.tv_nsec / 1000000)));
|
||||
#else
|
||||
timeval tv;
|
||||
gettimeofday(&tv, (struct timezone*)0);
|
||||
return ((1000LL * (int64_t)tv.tv_sec) + (int64_t)(tv.tv_usec / 1000));
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Get monotonic time since some point in the past
|
||||
*
|
||||
* On some systems this may fall back to the same return value as now(), but
|
||||
* if a monotonic (not affected by time changes) source is available it will
|
||||
* be used.
|
||||
*
|
||||
* @return Current monotonic time in milliseconds (usually since system boot, but origin point is undefined)
|
||||
*/
|
||||
static ZT_INLINE int64_t now_monotonic()
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
return (int64_t)GetTickCount64();
|
||||
#else
|
||||
#ifdef __LINUX__
|
||||
timespec ts;
|
||||
clock_gettime(CLOCK_BOOTTIME, &ts);
|
||||
return ((1000LL * (int64_t)ts.tv_sec) + ((int64_t)(ts.tv_nsec / 1000000)));
|
||||
#else
|
||||
#ifdef __APPLE__
|
||||
mach_timespec_t mts;
|
||||
clock_get_time(s_machMonotonicClock, &mts);
|
||||
return ((1000LL * (int64_t)mts.tv_sec) + ((int64_t)(mts.tv_nsec / 1000000)));
|
||||
#else
|
||||
timeval tv;
|
||||
gettimeofday(&tv, (struct timezone*)0);
|
||||
return ((1000LL * (int64_t)tv.tv_sec) + (int64_t)(tv.tv_usec / 1000));
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Read the full contents of a file into a string buffer
|
||||
*
|
||||
* The buffer isn't cleared, so if it already contains data the file's data will
|
||||
* be appended.
|
||||
*
|
||||
* @param path Path of file to read
|
||||
* @param buf Buffer to fill
|
||||
* @return True if open and read successful
|
||||
*/
|
||||
static bool readFile(const char* path, ZeroTier::String& buf);
|
||||
|
||||
/**
|
||||
* Write a block of data to disk, replacing any current file contents
|
||||
*
|
||||
* @param path Path to write
|
||||
* @param buf Buffer containing data
|
||||
* @param len Length of buffer
|
||||
* @return True if entire file was successfully written
|
||||
*/
|
||||
static bool writeFile(const char* path, const void* buf, unsigned int len);
|
||||
|
||||
/**
|
||||
* Split a string by delimiter, with optional escape and quote characters
|
||||
*
|
||||
* @param s String to split
|
||||
* @param sep One or more separators
|
||||
* @param esc Zero or more escape characters
|
||||
* @param quot Zero or more quote characters
|
||||
* @return Vector of tokens
|
||||
*/
|
||||
static ZeroTier::Vector<ZeroTier::String> split(const char* s, const char* sep, const char* esc, const char* quot);
|
||||
|
||||
/**
|
||||
* Write a block of data to disk, replacing any current file contents
|
||||
*
|
||||
* @param path Path to write
|
||||
* @param s Data to write
|
||||
* @return True if entire file was successfully written
|
||||
*/
|
||||
static ZT_INLINE bool writeFile(const char* path, const ZeroTier::String& s)
|
||||
{
|
||||
return writeFile(path, s.data(), (unsigned int)s.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Platform default ZeroTier One home path
|
||||
*/
|
||||
static ZeroTier::String platformDefaultHomePath();
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
201
osdep/Thread.hpp
201
osdep/Thread.hpp
|
@ -1,201 +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_THREAD_HPP
|
||||
#define ZT_THREAD_HPP
|
||||
|
||||
#include "../core/Constants.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
|
||||
#include "../core/Mutex.hpp"
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
template <typename C> static DWORD WINAPI ___zt_threadMain(LPVOID lpParam)
|
||||
{
|
||||
try {
|
||||
((C*)lpParam)->threadMain();
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
class Thread {
|
||||
public:
|
||||
Thread()
|
||||
{
|
||||
_th = NULL;
|
||||
_tid = 0;
|
||||
}
|
||||
|
||||
template <typename C> static inline Thread start(C* instance)
|
||||
{
|
||||
Thread t;
|
||||
t._th = CreateThread(NULL, 0, &___zt_threadMain<C>, (LPVOID)instance, 0, &t._tid);
|
||||
if (t._th == NULL)
|
||||
throw std::runtime_error("CreateThread() failed");
|
||||
return t;
|
||||
}
|
||||
|
||||
static inline void join(const Thread& t)
|
||||
{
|
||||
if (t._th != NULL) {
|
||||
for (;;) {
|
||||
DWORD ec = STILL_ACTIVE;
|
||||
GetExitCodeThread(t._th, &ec);
|
||||
if (ec == STILL_ACTIVE)
|
||||
WaitForSingleObject(t._th, 1000);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void sleep(unsigned long ms)
|
||||
{
|
||||
Sleep((DWORD)ms);
|
||||
}
|
||||
|
||||
// Not available on *nix platforms
|
||||
static inline void cancelIO(const Thread& t)
|
||||
{
|
||||
#if ! defined(__MINGW32__) && ! defined(__MINGW64__) // CancelSynchronousIo not available in MSYS2
|
||||
if (t._th != NULL)
|
||||
CancelSynchronousIo(t._th);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline operator bool() const
|
||||
{
|
||||
return (_th != NULL);
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE _th;
|
||||
DWORD _tid;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#else
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
template <typename C> static void* ___zt_threadMain(void* instance)
|
||||
{
|
||||
try {
|
||||
((C*)instance)->threadMain();
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
/**
|
||||
* A thread identifier, and static methods to start and join threads
|
||||
*/
|
||||
class Thread {
|
||||
public:
|
||||
Thread()
|
||||
{
|
||||
memset(this, 0, sizeof(Thread));
|
||||
}
|
||||
|
||||
Thread(const Thread& t)
|
||||
{
|
||||
memcpy(this, &t, sizeof(Thread));
|
||||
}
|
||||
|
||||
inline Thread& operator=(const Thread& t)
|
||||
{
|
||||
memcpy(this, &t, sizeof(Thread));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new thread
|
||||
*
|
||||
* @param instance Instance whose threadMain() method gets called by new thread
|
||||
* @return Thread identifier
|
||||
* @throws std::runtime_error Unable to create thread
|
||||
* @tparam C Class containing threadMain()
|
||||
*/
|
||||
template <typename C> static inline Thread start(C* instance)
|
||||
{
|
||||
Thread t;
|
||||
pthread_attr_t tattr;
|
||||
pthread_attr_init(&tattr);
|
||||
// This corrects for systems with abnormally small defaults (musl) and also
|
||||
// shrinks the stack on systems with large defaults to save a bit of memory.
|
||||
pthread_attr_setstacksize(&tattr, 1048576);
|
||||
if (pthread_create(&t._tid, &tattr, &___zt_threadMain<C>, instance)) {
|
||||
pthread_attr_destroy(&tattr);
|
||||
throw std::runtime_error("pthread_create() failed, unable to create thread");
|
||||
}
|
||||
else {
|
||||
t._started = true;
|
||||
pthread_attr_destroy(&tattr);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join to a thread, waiting for it to terminate (does nothing on null Thread values)
|
||||
*
|
||||
* @param t Thread to join
|
||||
*/
|
||||
static inline void join(const Thread& t)
|
||||
{
|
||||
if (t._started)
|
||||
pthread_join(t._tid, (void**)0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep the current thread
|
||||
*
|
||||
* @param ms Number of milliseconds to sleep
|
||||
*/
|
||||
static inline void sleep(unsigned long ms)
|
||||
{
|
||||
usleep(ms * 1000);
|
||||
}
|
||||
|
||||
inline operator bool() const
|
||||
{
|
||||
return (_started);
|
||||
}
|
||||
|
||||
private:
|
||||
pthread_t _tid;
|
||||
volatile bool _started;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif // __WINDOWS__ / !__WINDOWS__
|
||||
|
||||
#endif
|
|
@ -1,169 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
/* Fix for an issue with this structure not being present on MacOS */
|
||||
#ifdef __APPLE__
|
||||
struct prf_ra {
|
||||
unsigned char onlink : 1;
|
||||
unsigned char autonomous : 1;
|
||||
unsigned char reserved : 6;
|
||||
} prf_ra;
|
||||
#endif
|
||||
|
||||
#include "rust-osdep.h"
|
||||
|
||||
#include "../core/AES.hpp"
|
||||
#include "../core/Constants.hpp"
|
||||
#include "../core/Containers.hpp"
|
||||
#include "../core/Mutex.hpp"
|
||||
#include "../core/SHA512.hpp"
|
||||
#include "OSUtils.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#ifndef SIOCAUTOCONF_START
|
||||
#define SIOCAUTOCONF_START _IOWR('i', 132, struct in6_ifreq) /* accept rtadvd on this interface */
|
||||
#endif
|
||||
#ifndef SIOCAUTOCONF_STOP
|
||||
#define SIOCAUTOCONF_STOP _IOWR('i', 133, struct in6_ifreq) /* stop accepting rtadv for this interface */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
|
||||
#ifdef __APPLE__
|
||||
const unsigned long c_BIOCSBLEN = BIOCSBLEN;
|
||||
const unsigned long c_BIOCIMMEDIATE = BIOCIMMEDIATE;
|
||||
const unsigned long c_BIOCSSEESENT = BIOCSSEESENT;
|
||||
const unsigned long c_BIOCSETIF = BIOCSETIF;
|
||||
const unsigned long c_BIOCSHDRCMPLT = BIOCSHDRCMPLT;
|
||||
const unsigned long c_BIOCPROMISC = BIOCPROMISC;
|
||||
const unsigned long c_SIOCGIFINFO_IN6 = SIOCGIFINFO_IN6;
|
||||
const unsigned long c_SIOCSIFINFO_FLAGS = SIOCSIFINFO_FLAGS;
|
||||
const unsigned long c_SIOCAUTOCONF_START = SIOCAUTOCONF_START;
|
||||
const unsigned long c_SIOCAUTOCONF_STOP = SIOCAUTOCONF_STOP;
|
||||
#endif
|
||||
|
||||
const char* platformDefaultHomePath()
|
||||
{
|
||||
static ZeroTier::Mutex s_lock;
|
||||
static ZeroTier::String s_homePath;
|
||||
|
||||
ZeroTier::Mutex::Lock l(s_lock);
|
||||
if (s_homePath.empty()) {
|
||||
#ifdef __QNAP__
|
||||
|
||||
char* cmd = "/sbin/getcfg zerotier Install_Path -f /etc/config/qpkg.conf";
|
||||
char buf[128];
|
||||
FILE* fp;
|
||||
if ((fp = popen(cmd, "r")) == NULL) {
|
||||
printf("Error opening pipe!\n");
|
||||
return NULL;
|
||||
}
|
||||
while (fgets(buf, 128, fp) != NULL) {}
|
||||
if (pclose(fp)) {
|
||||
printf("Command not found or exited with error status\n");
|
||||
return NULL;
|
||||
}
|
||||
String homeDir = String(buf);
|
||||
homeDir.erase(std::remove(homeDir.begin(), homeDir.end(), '\n'), homeDir.end());
|
||||
s_homePath = homeDir;
|
||||
|
||||
#else
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
|
||||
DWORD bufferSize = 65535;
|
||||
ZeroTier::String userDefinedPath;
|
||||
bufferSize = GetEnvironmentVariable("ZEROTIER_HOME", &userDefinedPath[0], bufferSize);
|
||||
if (bufferSize) {
|
||||
s_homePath = userDefinedPath;
|
||||
}
|
||||
else {
|
||||
char buf[16384];
|
||||
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_COMMON_APPDATA, NULL, 0, buf))) {
|
||||
ZeroTier::String tmp(buf);
|
||||
tmp.append("\\ZeroTier");
|
||||
s_homePath = tmp;
|
||||
}
|
||||
else {
|
||||
s_homePath = "C:\\ZeroTier";
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
if (const char* userDefinedPath = getenv("ZEROTIER_HOME")) {
|
||||
s_homePath = userDefinedPath;
|
||||
}
|
||||
else {
|
||||
#ifdef __APPLE__
|
||||
s_homePath = "/Library/Application Support/ZeroTier";
|
||||
#else
|
||||
#ifdef __BSD__
|
||||
s_homePath = "/var/db/zerotier";
|
||||
#else
|
||||
s_homePath = "/var/lib/zerotier";
|
||||
#endif // __BSD__ or not
|
||||
#endif // __APPLE__ or not
|
||||
}
|
||||
|
||||
#endif // __WINDOWS__ or not
|
||||
|
||||
#endif // __QNAP__ or not
|
||||
|
||||
if (s_homePath.empty())
|
||||
s_homePath = "." ZT_PATH_SEPARATOR_S;
|
||||
}
|
||||
|
||||
return s_homePath.c_str();
|
||||
}
|
||||
|
||||
int64_t msSinceEpoch()
|
||||
{
|
||||
return ZeroTier::OSUtils::now();
|
||||
}
|
||||
|
||||
int64_t msMonotonic()
|
||||
{
|
||||
return ZeroTier::OSUtils::now_monotonic();
|
||||
}
|
||||
|
||||
void lockDownFile(const char* path, int isDir)
|
||||
{
|
||||
ZeroTier::OSUtils::lockDownFile(path, isDir != 0);
|
||||
}
|
||||
|
||||
void getSecureRandom(void* buf, unsigned int len)
|
||||
{
|
||||
ZeroTier::Utils::getSecureRandom(buf, len);
|
||||
}
|
||||
|
||||
static ZT_INLINE ZeroTier::AES _makeHttpAuthCipher() noexcept
|
||||
{
|
||||
uint8_t key[32];
|
||||
ZeroTier::Utils::getSecureRandom(key, 32);
|
||||
return ZeroTier::AES(key);
|
||||
}
|
||||
static const ZeroTier::AES HTTP_AUTH_CIPHER = _makeHttpAuthCipher();
|
||||
|
||||
void encryptHttpAuthNonce(void* block)
|
||||
{
|
||||
HTTP_AUTH_CIPHER.encrypt(block, block);
|
||||
}
|
||||
|
||||
void decryptHttpAuthNonce(void* block)
|
||||
{
|
||||
HTTP_AUTH_CIPHER.decrypt(block, block);
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
|
@ -1,131 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/********************************************************************************************************************/
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/bpf.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <net/if_media.h>
|
||||
#include <net/ndrv.h>
|
||||
#include <net/route.h>
|
||||
#include <netinet/icmp6.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/in_var.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip6.h>
|
||||
#include <netinet6/in6_var.h>
|
||||
#include <netinet6/nd6.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/signal.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/* These complex macros don't translate well with Rust bindgen, so compute
|
||||
* them with the C compiler and export them. */
|
||||
extern const unsigned long c_BIOCSBLEN;
|
||||
extern const unsigned long c_BIOCIMMEDIATE;
|
||||
extern const unsigned long c_BIOCSSEESENT;
|
||||
extern const unsigned long c_BIOCSETIF;
|
||||
extern const unsigned long c_BIOCSHDRCMPLT;
|
||||
extern const unsigned long c_BIOCPROMISC;
|
||||
extern const unsigned long c_SIOCGIFINFO_IN6;
|
||||
extern const unsigned long c_SIOCSIFINFO_FLAGS;
|
||||
extern const unsigned long c_SIOCAUTOCONF_START;
|
||||
extern const unsigned long c_SIOCAUTOCONF_STOP;
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#ifndef IPV6_DONTFRAG
|
||||
#define IPV6_DONTFRAG 62
|
||||
#endif
|
||||
#endif /* __APPLE__ */
|
||||
|
||||
/********************************************************************************************************************/
|
||||
|
||||
#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux)
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_addr.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/if_tun.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#endif /* __linux__ */
|
||||
|
||||
/********************************************************************************************************************/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Get the default home path for this platform.
|
||||
extern const char* platformDefaultHomePath();
|
||||
|
||||
// This ms-since-epoch function may be faster than the one in Rust's stdlib.
|
||||
extern int64_t msSinceEpoch();
|
||||
|
||||
// This is the number of milliseconds since some time in the past, unaffected by the clock (or msSinceEpoch() if not
|
||||
// supported by host).
|
||||
extern int64_t msMonotonic();
|
||||
|
||||
// Rust glue to C code to lock down a file, which is simple on Unix-like OSes
|
||||
// and horrible on Windows.
|
||||
extern void lockDownFile(const char* path, int isDir);
|
||||
|
||||
// Rust glue to ZeroTier's secure random PRNG.
|
||||
extern void getSecureRandom(void* buf, unsigned int len);
|
||||
|
||||
// These AES encrypt and decrypt a single block using a key that is randomly
|
||||
// generated at process init and never exported. It's used to generate HTTP
|
||||
// digest authentication tokens that can just be decrypted to get and check
|
||||
// a timestamp to prevent replay attacks.
|
||||
extern void encryptHttpAuthNonce(void* block);
|
||||
extern void decryptHttpAuthNonce(void* block);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************************************************************************************************************/
|
938
service/Cargo.lock
generated
938
service/Cargo.lock
generated
|
@ -1,938 +0,0 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"term_size",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"lazy_static",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a50aab2529019abfabfa93f1e6c41ef392f91fbf179b347a7e96abb524884a08"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"regex",
|
||||
"terminal_size",
|
||||
"unicode-width",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpuid-bool"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
|
||||
|
||||
[[package]]
|
||||
name = "dialoguer"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70f807b2943dc90f9747497d9d65d7e92472149be0b88bf4ce1201b4ac979c26"
|
||||
dependencies = [
|
||||
"console",
|
||||
"lazy_static",
|
||||
"tempfile",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest_auth"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12fd5e24649b07f360f59a1e0a522d775540e2bc4b88f8d2657bcf8ca0360d74"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hex",
|
||||
"md-5",
|
||||
"rand",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"proc-macro-hack",
|
||||
"proc-macro-nested",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12219dc884514cb4a6a03737f4413c0e01c23a1b059b0156004b23f1e19dccbe"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"miow",
|
||||
"ntapi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
|
||||
dependencies = [
|
||||
"socket2",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95b70b68509f17aa2857863b6fa00bf21fc93674c7a8893de2f469f6aa7ca2f2"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caa25a6393f22ce819b0f50e0be89287292fda8d425be38ee0ca14c4931d9e71"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-nested"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bdd36f49e35b61d49efd8aa7fc068fd295961fd2286d0b2ee9a4c7a14e99cc3"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "552954ce79a059ddd5fd68c271592374bd15cab2274970380c000118aeffe1cd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if 1.0.0",
|
||||
"cpuid-bool",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"rand",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term_size"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"term_size",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca04cec6ff2474c638057b65798f60ac183e5e79d3448bb7163d36a39cff6ec"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libc",
|
||||
"mio",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"tokio-macros",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45af6a010d13e4cf5b54c94ba5a2b2eba5596b9e46bf5875612d332a1f2b3f86"
|
||||
|
||||
[[package]]
|
||||
name = "zerotier-service"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"colored",
|
||||
"dialoguer",
|
||||
"digest_auth",
|
||||
"futures",
|
||||
"hex",
|
||||
"hyper",
|
||||
"lazy_static",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"num_cpus",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"winapi",
|
||||
]
|
|
@ -1,34 +0,0 @@
|
|||
[package]
|
||||
name = "zerotier-service"
|
||||
version = "0.1.0"
|
||||
authors = ["Adam Ierymenko <adam.ierymenko@zerotier.com>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 'z'
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
|
||||
[dependencies]
|
||||
num_cpus = "*"
|
||||
tokio = { version = "1", features = ["rt", "net", "time", "signal", "macros"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
futures = "0"
|
||||
clap = { version = "2", features = ["suggestions", "wrap_help"] }
|
||||
chrono = "0"
|
||||
hex = "*"
|
||||
lazy_static = "*"
|
||||
num-traits = "0"
|
||||
num-derive = "0"
|
||||
hyper = { version = "0", features = ["http1", "runtime", "server", "client", "tcp", "stream"] }
|
||||
socket2 = { version = "0", features = ["reuseport", "unix", "pair"] }
|
||||
dialoguer = "0"
|
||||
digest_auth = "0.2.4"
|
||||
colored = "2"
|
||||
#zerotier-core = { path = "../rust-zerotier-core" }
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
winapi = { version = "0.3.9", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }
|
|
@ -1,15 +0,0 @@
|
|||
#[allow(unused_assignments)]
|
||||
#[allow(unused_mut)]
|
||||
fn main() {
|
||||
let d = env!("CARGO_MANIFEST_DIR");
|
||||
println!("cargo:rustc-link-search=native={}/../build/core", d);
|
||||
println!("cargo:rustc-link-search=native={}/../build/osdep", d);
|
||||
println!("cargo:rustc-link-lib=static=zt_core");
|
||||
println!("cargo:rustc-link-lib=static=zt_osdep");
|
||||
|
||||
let mut cpplib = "c++";
|
||||
#[cfg(target_os = "linux")] {
|
||||
cpplib = "stdc++";
|
||||
}
|
||||
println!("cargo:rustc-link-lib={}", cpplib);
|
||||
}
|
|
@ -1,46 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hyper::{Request, Body, StatusCode, Method};
|
||||
|
||||
use crate::service::Service;
|
||||
|
||||
pub(crate) fn status(service: Arc<Service>, req: Request<Body>) -> (StatusCode, Body) {
|
||||
if req.method() == Method::GET {
|
||||
service.status().map_or_else(|| {
|
||||
(StatusCode::SERVICE_UNAVAILABLE, Body::from("node shutdown in progress"))
|
||||
}, |status| {
|
||||
(StatusCode::OK, Body::from(serde_json::to_string(&status).unwrap()))
|
||||
})
|
||||
} else {
|
||||
(StatusCode::METHOD_NOT_ALLOWED, Body::from("/status allows method(s): GET"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn config(service: Arc<Service>, req: Request<Body>) -> (StatusCode, Body) {
|
||||
let config = service.local_config();
|
||||
if req.method() == Method::POST || req.method() == Method::PUT {
|
||||
// TODO: diff config
|
||||
}
|
||||
(StatusCode::OK, Body::from(serde_json::to_string(config.as_ref()).unwrap()))
|
||||
}
|
||||
|
||||
pub(crate) fn peer(service: Arc<Service>, req: Request<Body>) -> (StatusCode, Body) {
|
||||
(StatusCode::NOT_IMPLEMENTED, Body::from(""))
|
||||
}
|
||||
|
||||
pub(crate) fn network(service: Arc<Service>, req: Request<Body>) -> (StatusCode, Body) {
|
||||
(StatusCode::NOT_IMPLEMENTED, Body::from(""))
|
||||
}
|
|
@ -1,567 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use dialoguer::Input;
|
||||
use zerotier_core::*;
|
||||
|
||||
use crate::store::Store;
|
||||
use crate::utils::{read_limit, ms_since_epoch, to_json_pretty};
|
||||
use crate::GlobalFlags;
|
||||
|
||||
fn cert_usage_flags_to_string(flags: u64) -> String {
|
||||
let mut flags_str = String::new();
|
||||
for f in [
|
||||
(CERTIFICATE_USAGE_DIGITAL_SIGNATURE, "s"),
|
||||
(CERTIFICATE_USAGE_NON_REPUDIATION, "n"),
|
||||
(CERTIFICATE_USAGE_KEY_ENCIPHERMENT, "e"),
|
||||
(CERTIFICATE_USAGE_DATA_ENCIPHERMENT, "d"),
|
||||
(CERTIFICATE_USAGE_KEY_AGREEMENT, "a"),
|
||||
(CERTIFICATE_USAGE_CERTIFICATE_SIGNING, "c"),
|
||||
(CERTIFICATE_USAGE_CRL_SIGNING, "r"),
|
||||
(CERTIFICATE_USAGE_EXECUTABLE_SIGNATURE, "x"),
|
||||
(CERTIFICATE_USAGE_TIMESTAMPING, "t"),
|
||||
(CERTIFICATE_USAGE_ZEROTIER_ROOT_SET, "z"),
|
||||
].iter() {
|
||||
if (flags & (*f).0) != 0 {
|
||||
if !flags_str.is_empty() {
|
||||
flags_str.push(',');
|
||||
}
|
||||
flags_str.push_str((*f).1);
|
||||
}
|
||||
}
|
||||
flags_str
|
||||
}
|
||||
|
||||
fn cert_string_to_usage_flags(flags_str: &str) -> u64 {
|
||||
let mut flags: u64 = 0;
|
||||
for c in flags_str.chars().into_iter() {
|
||||
flags |= match c {
|
||||
's' => CERTIFICATE_USAGE_DIGITAL_SIGNATURE,
|
||||
'n' => CERTIFICATE_USAGE_NON_REPUDIATION,
|
||||
'e' => CERTIFICATE_USAGE_KEY_ENCIPHERMENT,
|
||||
'd' => CERTIFICATE_USAGE_DATA_ENCIPHERMENT,
|
||||
'a' => CERTIFICATE_USAGE_KEY_AGREEMENT,
|
||||
'c' => CERTIFICATE_USAGE_CERTIFICATE_SIGNING,
|
||||
'r' => CERTIFICATE_USAGE_CRL_SIGNING,
|
||||
'x' => CERTIFICATE_USAGE_EXECUTABLE_SIGNATURE,
|
||||
't' => CERTIFICATE_USAGE_TIMESTAMPING,
|
||||
'z' => CERTIFICATE_USAGE_ZEROTIER_ROOT_SET,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
flags
|
||||
}
|
||||
|
||||
fn cert_print(certificate: &Certificate) {
|
||||
let mut subject_identities = String::new();
|
||||
let mut subject_networks = String::new();
|
||||
let mut subject_update_urls = String::new();
|
||||
let mut usage_flags = String::new();
|
||||
|
||||
fn string_or_dash(s: &str) -> &str {
|
||||
if s.is_empty() {
|
||||
"-"
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
if certificate.subject.identities.is_empty() {
|
||||
subject_identities.push_str(": (none)");
|
||||
} else {
|
||||
for x in certificate.subject.identities.iter() {
|
||||
subject_identities.push_str("\n ");
|
||||
subject_identities.push_str(x.identity.to_string().as_str());
|
||||
if x.locator.is_some() {
|
||||
subject_identities.push_str(" ");
|
||||
subject_identities.push_str(x.locator.as_ref().unwrap().to_string().as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if certificate.subject.networks.is_empty() {
|
||||
subject_networks.push_str(": (none)");
|
||||
} else {
|
||||
for x in certificate.subject.networks.iter() {
|
||||
subject_networks.push_str("\n ");
|
||||
subject_networks.push_str(x.id.to_string().as_str());
|
||||
if x.controller.is_some() {
|
||||
subject_networks.push_str(" at ");
|
||||
subject_networks.push_str(x.controller.as_ref().unwrap().to_string().as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if certificate.subject.update_urls.is_empty() {
|
||||
subject_update_urls.push_str(": (none)");
|
||||
} else {
|
||||
for x in certificate.subject.update_urls.iter() {
|
||||
subject_update_urls.push_str("\n ");
|
||||
subject_update_urls.push_str(x.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
if certificate.usage_flags != 0 {
|
||||
usage_flags.push_str(" (");
|
||||
usage_flags.push_str(cert_usage_flags_to_string(certificate.usage_flags).as_str());
|
||||
usage_flags.push(')');
|
||||
}
|
||||
|
||||
println!(r###"Serial Number (SHA384): {}
|
||||
Usage Flags: 0x{:0>8x}{}
|
||||
Timestamp: {}
|
||||
Validity: {} to {}
|
||||
Subject
|
||||
Timestamp: {}
|
||||
Identities{}
|
||||
Networks{}
|
||||
Update URLs{}
|
||||
Name
|
||||
Serial: {}
|
||||
Common Name: {}
|
||||
Country: {}
|
||||
Organization: {}
|
||||
Unit: {}
|
||||
Locality: {}
|
||||
State/Province: {}
|
||||
Street Address: {}
|
||||
Postal Code: {}
|
||||
E-Mail: {}
|
||||
URL: {}
|
||||
Host: {}
|
||||
Unique ID: {}
|
||||
Unique ID Signature: {}
|
||||
Issuer: {}
|
||||
Issuer Public Key: {}
|
||||
Public Key: {}
|
||||
Extended Attributes: {} bytes
|
||||
Signature: {}
|
||||
Maximum Path Length: {}{}"###,
|
||||
certificate.serial_no.to_string(),
|
||||
certificate.usage_flags, usage_flags,
|
||||
certificate.timestamp,
|
||||
certificate.validity[0], certificate.validity[1],
|
||||
certificate.subject.timestamp,
|
||||
subject_identities,
|
||||
subject_networks,
|
||||
subject_update_urls,
|
||||
string_or_dash(certificate.subject.name.serial_no.as_str()),
|
||||
string_or_dash(certificate.subject.name.common_name.as_str()),
|
||||
string_or_dash(certificate.subject.name.country.as_str()),
|
||||
string_or_dash(certificate.subject.name.organization.as_str()),
|
||||
string_or_dash(certificate.subject.name.unit.as_str()),
|
||||
string_or_dash(certificate.subject.name.locality.as_str()),
|
||||
string_or_dash(certificate.subject.name.province.as_str()),
|
||||
string_or_dash(certificate.subject.name.street_address.as_str()),
|
||||
string_or_dash(certificate.subject.name.postal_code.as_str()),
|
||||
string_or_dash(certificate.subject.name.email.as_str()),
|
||||
string_or_dash(certificate.subject.name.url.as_str()),
|
||||
string_or_dash(certificate.subject.name.host.as_str()),
|
||||
string_or_dash(base64_encode(&certificate.subject.unique_id).as_str()),
|
||||
string_or_dash(base64_encode(&certificate.subject.unique_id_signature).as_str()),
|
||||
certificate.issuer.to_string(),
|
||||
string_or_dash(base64_encode(&certificate.issuer_public_key).as_str()),
|
||||
string_or_dash(base64_encode(&certificate.public_key).as_str()),
|
||||
certificate.extended_attributes.len(),
|
||||
string_or_dash(base64_encode(&certificate.signature).as_str()),
|
||||
certificate.max_path_length, if certificate.max_path_length == 0 { " (leaf)" } else { " (CA or sub-CA)" });
|
||||
}
|
||||
|
||||
fn list(store: &Arc<Store>) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn show<'a>(store: &Arc<Store>, global_flags: &GlobalFlags, cli_args: &ArgMatches<'a>) -> i32 {
|
||||
let serial_or_path = cli_args.value_of("serialorpath").unwrap().trim();
|
||||
CertificateSerialNo::new_from_string(serial_or_path).map_or_else(|| {
|
||||
read_limit(serial_or_path, 65536).map_or_else(|e| {
|
||||
println!("ERROR: unable to read certificate from '{}': {}", serial_or_path, e.to_string());
|
||||
1
|
||||
}, |cert_json| {
|
||||
serde_json::from_slice::<Certificate>(cert_json.as_ref()).map_or_else(|e| {
|
||||
println!("ERROR: unable to decode certificate from '{}': {}", serial_or_path, e.to_string());
|
||||
1
|
||||
}, |certificate| {
|
||||
if global_flags.json_output {
|
||||
println!("{}", to_json_pretty(&certificate));
|
||||
} else {
|
||||
cert_print(&certificate);
|
||||
}
|
||||
let cv = certificate.verify(ms_since_epoch());
|
||||
if cv != CertificateError::None {
|
||||
println!("\nWARNING: certificate validity check failed: {}", cv.to_str());
|
||||
}
|
||||
0
|
||||
})
|
||||
})
|
||||
}, |serial| {
|
||||
// TODO: query node
|
||||
0
|
||||
})
|
||||
}
|
||||
|
||||
fn newsuid(cli_args: Option<&ArgMatches>) -> i32 {
|
||||
let key_pair = Certificate::new_key_pair(CertificatePublicKeyAlgorithm::ECDSANistP384);
|
||||
if key_pair.is_err() {
|
||||
println!("ERROR: internal error creating key pair: {}", key_pair.err().unwrap().to_str());
|
||||
1
|
||||
} else {
|
||||
let (_, privk) = key_pair.ok().unwrap();
|
||||
let privk_base64 = base64_encode(&privk);
|
||||
let path = cli_args.map_or("", |cli_args| { cli_args.value_of("path").unwrap_or("") });
|
||||
if path.is_empty() {
|
||||
println!("{}", privk_base64);
|
||||
0
|
||||
} else {
|
||||
std::fs::write(path, privk_base64.as_bytes()).map_or_else(|e| {
|
||||
eprintln!("FATAL: error writing '{}': {}", path, e.to_string());
|
||||
e.raw_os_error().unwrap_or(1)
|
||||
}, |_| {
|
||||
println!("Subject unique ID secret written to: {} (public is included)", path);
|
||||
0
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn newcsr(cli_args: &ArgMatches) -> i32 {
|
||||
let theme = &dialoguer::theme::SimpleTheme;
|
||||
|
||||
let subject_unique_id: String = Input::with_theme(theme)
|
||||
.with_prompt("Path to subject unique ID secret key (empty to create unsigned subject)")
|
||||
.allow_empty(true)
|
||||
.interact_text()
|
||||
.unwrap_or_default();
|
||||
let subject_unique_id_private_key = if subject_unique_id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let b = crate::utils::read_limit(subject_unique_id, 1024);
|
||||
if b.is_err() {
|
||||
println!("ERROR: unable to read subject unique ID secret file: {}", b.err().unwrap().to_string());
|
||||
return 1;
|
||||
}
|
||||
let privk_encoded = String::from_utf8(b.unwrap());
|
||||
if privk_encoded.is_err() {
|
||||
println!("ERROR: invalid UTF-8 in secret");
|
||||
return 1;
|
||||
}
|
||||
let privk_encoded = privk_encoded.unwrap();
|
||||
let privk = base64_decode(&privk_encoded);
|
||||
if privk.is_err() || privk.as_ref().unwrap().is_empty() {
|
||||
println!("ERROR: invalid unique ID secret: {}", privk.err().unwrap().to_string());
|
||||
return 1;
|
||||
}
|
||||
Some(privk.unwrap())
|
||||
};
|
||||
|
||||
let timestamp: i64 = Input::with_theme(theme)
|
||||
.with_prompt("Subject timestamp (seconds since epoch)")
|
||||
.with_initial_text((crate::utils::ms_since_epoch() / 1000).to_string())
|
||||
.allow_empty(false)
|
||||
.interact_text()
|
||||
.unwrap_or(0);
|
||||
if timestamp < 0 {
|
||||
println!("ERROR: invalid timestamp");
|
||||
return 1;
|
||||
}
|
||||
|
||||
println!("Subject identities");
|
||||
let mut identities: Vec<CertificateIdentity> = Vec::new();
|
||||
loop {
|
||||
let identity: String = Input::with_theme(theme)
|
||||
.with_prompt(format!(" [{}] Identity or path to identity (empty to end)", identities.len() + 1))
|
||||
.allow_empty(true)
|
||||
.interact_text()
|
||||
.unwrap_or_default();
|
||||
if identity.is_empty() {
|
||||
break;
|
||||
}
|
||||
let identity = crate::utils::read_identity(identity.as_str(), true);
|
||||
if identity.is_err() {
|
||||
println!("ERROR: identity invalid or unable to read from file.");
|
||||
return 1;
|
||||
}
|
||||
let identity = identity.unwrap();
|
||||
if identity.has_private() {
|
||||
println!("ERROR: identity contains private key, use public only for CSR!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let locator: String = Input::with_theme(theme)
|
||||
.with_prompt(format!(" [{}] Locator or path to locator for {} (optional)", identities.len() + 1, identity.address.to_string()))
|
||||
.allow_empty(true)
|
||||
.interact_text()
|
||||
.unwrap_or_default();
|
||||
let locator = if locator.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let l = crate::utils::read_locator(locator.as_str());
|
||||
if l.is_err() {
|
||||
println!("ERROR: locator invalid: {}", l.err().unwrap());
|
||||
return 1;
|
||||
}
|
||||
let l = l.ok();
|
||||
if !l.as_ref().unwrap().verify(&identity) {
|
||||
println!("ERROR: locator was not signed by this identity.");
|
||||
return 1;
|
||||
}
|
||||
l
|
||||
};
|
||||
|
||||
identities.push(CertificateIdentity {
|
||||
identity,
|
||||
locator,
|
||||
});
|
||||
}
|
||||
|
||||
println!("Subject networks (empty to end)");
|
||||
let mut networks: Vec<CertificateNetwork> = Vec::new();
|
||||
loop {
|
||||
let nwid: String = Input::with_theme(theme)
|
||||
.with_prompt(format!(" [{}] Network ID (empty to end)", networks.len() + 1))
|
||||
.allow_empty(true)
|
||||
.interact_text()
|
||||
.unwrap_or_default();
|
||||
if nwid.len() != 16 {
|
||||
break;
|
||||
}
|
||||
let nwid = NetworkId::from(nwid.as_str());
|
||||
|
||||
let fingerprint: String = Input::with_theme(theme)
|
||||
.with_prompt(format!(" [{}] Fingerprint of primary controller (optional)", networks.len() + 1))
|
||||
.allow_empty(true)
|
||||
.interact_text()
|
||||
.unwrap_or_default();
|
||||
let fingerprint = if fingerprint.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let f = Fingerprint::new_from_string(fingerprint.as_str());
|
||||
if f.is_err() {
|
||||
println!("ERROR: fingerprint invalid: {}", f.err().unwrap().to_str());
|
||||
return 1;
|
||||
}
|
||||
f.ok()
|
||||
};
|
||||
|
||||
networks.push(CertificateNetwork {
|
||||
id: nwid,
|
||||
controller: fingerprint,
|
||||
})
|
||||
}
|
||||
|
||||
println!("Subject certificate update URLs");
|
||||
let mut update_urls: Vec<String> = Vec::new();
|
||||
loop {
|
||||
let url: String = Input::with_theme(theme)
|
||||
.with_prompt(format!(" [{}] URL (empty to end)", update_urls.len() + 1))
|
||||
.allow_empty(true)
|
||||
.interact_text()
|
||||
.unwrap_or_default();
|
||||
if url.is_empty() {
|
||||
break;
|
||||
}
|
||||
let url_parsed = hyper::Uri::from_str(url.as_str());
|
||||
if url_parsed.is_err() {
|
||||
println!("ERROR: invalid URL: {}", url_parsed.err().unwrap().to_string());
|
||||
return 1;
|
||||
}
|
||||
update_urls.push(url);
|
||||
}
|
||||
|
||||
println!("Certificate name information (all fields are optional)");
|
||||
let name = CertificateName {
|
||||
serial_no: Input::with_theme(theme).with_prompt(" Serial (user-defined)").allow_empty(true).interact_text().unwrap_or_default(),
|
||||
common_name: Input::with_theme(theme).with_prompt(" Common Name").allow_empty(true).interact_text().unwrap_or_default(),
|
||||
organization: Input::with_theme(theme).with_prompt(" Organization").allow_empty(true).interact_text().unwrap_or_default(),
|
||||
unit: Input::with_theme(theme).with_prompt(" Organizational Unit").allow_empty(true).interact_text().unwrap_or_default(),
|
||||
country: Input::with_theme(theme).with_prompt(" Country").allow_empty(true).interact_text().unwrap_or_default(),
|
||||
province: Input::with_theme(theme).with_prompt(" State/Province").allow_empty(true).interact_text().unwrap_or_default(),
|
||||
locality: Input::with_theme(theme).with_prompt(" Locality").allow_empty(true).interact_text().unwrap_or_default(),
|
||||
street_address: Input::with_theme(theme).with_prompt(" Street Address").allow_empty(true).interact_text().unwrap_or_default(),
|
||||
postal_code: Input::with_theme(theme).with_prompt(" Postal Code").allow_empty(true).interact_text().unwrap_or_default(),
|
||||
email: Input::with_theme(theme).with_prompt(" E-Mail").allow_empty(true).interact_text().unwrap_or_default(),
|
||||
url: Input::with_theme(theme).with_prompt(" URL (informational)").allow_empty(true).interact_text().unwrap_or_default(),
|
||||
host: Input::with_theme(theme).with_prompt(" Host").allow_empty(true).interact_text().unwrap_or_default(),
|
||||
};
|
||||
|
||||
let subject = CertificateSubject {
|
||||
timestamp,
|
||||
identities,
|
||||
networks,
|
||||
update_urls,
|
||||
name,
|
||||
unique_id: Vec::new(),
|
||||
unique_id_signature: Vec::new(),
|
||||
};
|
||||
|
||||
let (_, privk) = Certificate::new_key_pair(CertificatePublicKeyAlgorithm::ECDSANistP384).ok().unwrap();
|
||||
subject.new_csr(privk.as_ref(), subject_unique_id_private_key.as_ref().map(|k| k.as_ref())).map_or_else(|e| {
|
||||
println!("ERROR: error creating CRL: {}", e.to_str());
|
||||
1
|
||||
}, |csr| {
|
||||
let csr_path = cli_args.value_of("csrpath").unwrap();
|
||||
std::fs::write(csr_path, csr).map_or_else(|e| {
|
||||
println!("ERROR: unable to write CSR: {}", e.to_string());
|
||||
1
|
||||
}, |_| {
|
||||
let secret_path = cli_args.value_of("secretpath").unwrap();
|
||||
std::fs::write(secret_path, base64_encode(&privk)).map_or_else(|e| {
|
||||
let _ = std::fs::remove_file(csr_path);
|
||||
println!("ERROR: unable to write secret: {}", e.to_string());
|
||||
1
|
||||
}, |_| {
|
||||
println!("CSR written to '{}', certificate secret to '{}'", csr_path, secret_path);
|
||||
0
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn sign<'a>(store: &Arc<Store>, cli_args: &ArgMatches<'a>) -> i32 {
|
||||
let now = ms_since_epoch();
|
||||
let csr_path = cli_args.value_of("csr").unwrap().trim(); // required
|
||||
let certout_path = cli_args.value_of("certout").unwrap().trim(); // required
|
||||
let usage = cert_string_to_usage_flags(cli_args.value_of("usage").unwrap_or(""));
|
||||
let timestamp = cli_args.value_of("timestamp").map_or_else(|| now, |ts| i64::from_str(ts.trim()).unwrap_or(now));
|
||||
let start_time = cli_args.value_of("start").map_or_else(|| timestamp, |s| i64::from_str(s.trim()).unwrap_or(now));
|
||||
let end_time = cli_args.value_of("ttl").map_or(i64::MAX, |e| i64::from_str(e.trim()).map_or(i64::MAX, |e| timestamp + e));
|
||||
let issuer_path = cli_args.value_of("issuer").unwrap().trim(); // required
|
||||
let issuer_secret_path = cli_args.value_of("issuersecret").unwrap().trim(); // required
|
||||
|
||||
let csr = crate::utils::read_limit(csr_path, 131072);
|
||||
if csr.is_err() {
|
||||
println!("ERROR: unable to read CSR from '{}': {}", csr_path, csr.err().unwrap().to_string());
|
||||
return 1;
|
||||
}
|
||||
let csr = csr.unwrap();
|
||||
|
||||
let cert = Certificate::new_from_bytes(csr.as_slice(), false);
|
||||
if cert.is_err() {
|
||||
println!("ERROR: error decoding CSR read from '{}': {}", csr_path, cert.err().unwrap().to_str());
|
||||
return 1;
|
||||
}
|
||||
let mut cert = cert.ok().unwrap();
|
||||
|
||||
cert.usage_flags = usage;
|
||||
cert.timestamp = timestamp;
|
||||
cert.validity[0] = start_time;
|
||||
cert.validity[1] = end_time;
|
||||
|
||||
let issuer = if issuer_path == "self" {
|
||||
cert.issuer_public_key.clear();
|
||||
cert.subject_signature.clear();
|
||||
cert.issuer.clone()
|
||||
} else {
|
||||
let issuer = crate::utils::read_limit(issuer_path, 131072);
|
||||
if issuer.is_err() {
|
||||
println!("ERROR: unable to read issuer from '{}': {}", issuer_path, issuer.err().unwrap().to_string());
|
||||
return 1;
|
||||
}
|
||||
let issuer = issuer.unwrap();
|
||||
|
||||
let issuer_cert = Certificate::new_from_bytes(issuer.as_slice(), true);
|
||||
if issuer_cert.is_err() {
|
||||
println!("ERROR: issuer at '{}' is invalid: {}", issuer_path, issuer_cert.err().unwrap().to_str());
|
||||
return 1;
|
||||
}
|
||||
let issuer_cert = issuer_cert.ok().unwrap();
|
||||
|
||||
if (issuer_cert.usage_flags & CERTIFICATE_USAGE_CERTIFICATE_SIGNING) == 0 {
|
||||
println!("ERROR: issuer at '{}' usage does not permit certificate signing.", issuer_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
issuer_cert.issuer.clone()
|
||||
};
|
||||
|
||||
let issuer_secret = crate::utils::read_limit(issuer_secret_path, 1024);
|
||||
if issuer_secret.is_err() {
|
||||
println!("ERROR: unable to read issuer secret from '{}': {}", issuer_secret_path, issuer_secret.err().unwrap().to_string());
|
||||
return 1;
|
||||
}
|
||||
let issuer_secret = issuer_secret.unwrap();
|
||||
let issuer_secret = base64_decode(&issuer_secret);
|
||||
if issuer_secret.is_err() {
|
||||
println!("ERROR: invalid issuer secret in '{}': invalid base64: {}", issuer_secret_path, issuer_secret.err().unwrap().to_string());
|
||||
return 1;
|
||||
}
|
||||
let issuer_secret = issuer_secret.unwrap();
|
||||
|
||||
let signed_cert = cert.sign(&issuer, issuer_secret.as_slice());
|
||||
if signed_cert.is_err() {
|
||||
println!("ERROR: error signing certificate: {}", signed_cert.err().unwrap().to_str());
|
||||
return 1;
|
||||
}
|
||||
let signed_cert = signed_cert.ok().unwrap();
|
||||
|
||||
let signed_cert_check = signed_cert.verify(-1);
|
||||
if signed_cert_check != CertificateError::None {
|
||||
println!("ERROR: error signing certificate: failed verify after sign: {}", signed_cert_check.to_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
cert_print(&signed_cert);
|
||||
println!();
|
||||
|
||||
let signed_cert = signed_cert.to_bytes().ok().unwrap();
|
||||
let signed_write_result = std::fs::write(certout_path, signed_cert.as_slice());
|
||||
if signed_write_result.is_err() {
|
||||
println!("ERROR: unable to write result to '{}': {}", certout_path, signed_write_result.err().unwrap().to_string());
|
||||
return 1;
|
||||
}
|
||||
println!("Signed certificate written to {}", certout_path);
|
||||
0
|
||||
}
|
||||
|
||||
fn verify<'a>(store: &Arc<Store>, cli_args: &ArgMatches<'a>) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn import<'a>(store: &Arc<Store>, cli_args: &ArgMatches<'a>) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn factoryreset(store: &Arc<Store>) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn export<'a>(store: &Arc<Store>, cli_args: &ArgMatches<'a>) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn delete<'a>(store: &Arc<Store>, cli_args: &ArgMatches<'a>) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
pub(crate) fn run(store: Arc<Store>, global_flags: GlobalFlags, cli_args: &ArgMatches) -> i32 {
|
||||
match cli_args.subcommand() {
|
||||
("list", None) => list(&store),
|
||||
("show", Some(sub_cli_args)) => show(&store, &global_flags, sub_cli_args),
|
||||
("newsuid", sub_cli_args) => newsuid(sub_cli_args),
|
||||
("newcsr", Some(sub_cli_args)) => newcsr(sub_cli_args),
|
||||
("sign", Some(sub_cli_args)) => sign(&store, sub_cli_args),
|
||||
("verify", Some(sub_cli_args)) => verify(&store, sub_cli_args),
|
||||
("import", Some(sub_cli_args)) => import(&store, sub_cli_args),
|
||||
("export", Some(sub_cli_args)) => export(&store, sub_cli_args),
|
||||
("delete", Some(sub_cli_args)) => delete(&store, sub_cli_args),
|
||||
("factoryreset", None) => factoryreset(&store),
|
||||
_ => {
|
||||
crate::print_help(true);
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +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.
|
||||
*/
|
||||
/****/
|
||||
|
|
@ -1,134 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
||||
use zerotier_core::{Identity, IdentityType};
|
||||
|
||||
fn new_(cli_args: &ArgMatches) -> i32 {
|
||||
let id_type = cli_args.value_of("type").map_or(IdentityType::Curve25519, |idt| {
|
||||
match idt {
|
||||
"p384" => IdentityType::NistP384,
|
||||
_ => IdentityType::Curve25519,
|
||||
}
|
||||
});
|
||||
let id = Identity::new_generate(id_type);
|
||||
if id.is_err() {
|
||||
println!("ERROR: identity generation failed: {}", id.err().unwrap().to_str());
|
||||
return 1;
|
||||
}
|
||||
println!("{}", id.ok().unwrap().to_secret_string());
|
||||
0
|
||||
}
|
||||
|
||||
fn getpublic(cli_args: &ArgMatches) -> i32 {
|
||||
let identity = crate::utils::read_identity(cli_args.value_of("identity").unwrap_or(""), false);
|
||||
identity.map_or_else(|e| {
|
||||
println!("ERROR: identity invalid: {}", e.to_string());
|
||||
1
|
||||
}, |id| {
|
||||
println!("{}", id.to_string());
|
||||
0
|
||||
})
|
||||
}
|
||||
|
||||
fn fingerprint(cli_args: &ArgMatches) -> i32 {
|
||||
let identity = crate::utils::read_identity(cli_args.value_of("identity").unwrap_or(""), false);
|
||||
identity.map_or_else(|e| {
|
||||
println!("ERROR: identity invalid: {}", e.to_string());
|
||||
1
|
||||
}, |id| {
|
||||
println!("{}", id.fingerprint().to_string());
|
||||
0
|
||||
})
|
||||
}
|
||||
|
||||
fn validate(cli_args: &ArgMatches) -> i32 {
|
||||
crate::utils::read_identity(cli_args.value_of("identity").unwrap_or(""), false).map_or_else(|e| {
|
||||
println!("FAILED");
|
||||
1
|
||||
}, |id| {
|
||||
if id.validate() {
|
||||
println!("OK");
|
||||
0
|
||||
} else {
|
||||
println!("FAILED");
|
||||
1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn sign(cli_args: &ArgMatches) -> i32 {
|
||||
crate::utils::read_identity(cli_args.value_of("identity").unwrap_or(""), false).map_or_else(|e| {
|
||||
println!("ERROR: invalid or unreadable identity: {}", e.as_str());
|
||||
1
|
||||
}, |id| {
|
||||
if id.has_private() {
|
||||
std::fs::read(cli_args.value_of("path").unwrap()).map_or_else(|e| {
|
||||
println!("ERROR: unable to read file: {}", e.to_string());
|
||||
1
|
||||
}, |data| {
|
||||
id.sign(data.as_slice()).map_or_else(|e| {
|
||||
println!("ERROR: failed to sign: {}", e.to_str());
|
||||
1
|
||||
}, |sig| {
|
||||
println!("{}", hex::encode(sig.as_ref()));
|
||||
0
|
||||
})
|
||||
})
|
||||
} else {
|
||||
println!("ERROR: identity must include secret key to sign.");
|
||||
1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn verify(cli_args: &ArgMatches) -> i32 {
|
||||
crate::utils::read_identity(cli_args.value_of("identity").unwrap_or(""), false).map_or_else(|e| {
|
||||
println!("ERROR: invalid or unreadable identity: {}", e.as_str());
|
||||
1
|
||||
}, |id| {
|
||||
std::fs::read(cli_args.value_of("path").unwrap()).map_or_else(|e| {
|
||||
println!("ERROR: unable to read file: {}", e.to_string());
|
||||
1
|
||||
}, |data| {
|
||||
hex::decode(cli_args.value_of("signature").unwrap()).map_or_else(|e| {
|
||||
println!("FAILED");
|
||||
1
|
||||
}, |sig| {
|
||||
if id.verify(data.as_slice(), sig.as_slice()) {
|
||||
println!("OK");
|
||||
0
|
||||
} else {
|
||||
println!("FAILED");
|
||||
1
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn run<'a>(cli_args: &ArgMatches<'a>) -> i32 {
|
||||
match cli_args.subcommand() {
|
||||
("new", Some(sub_cli_args)) => new_(sub_cli_args),
|
||||
("getpublic", Some(sub_cli_args)) => getpublic(sub_cli_args),
|
||||
("fingerprint", Some(sub_cli_args)) => fingerprint(sub_cli_args),
|
||||
("validate", Some(sub_cli_args)) => validate(sub_cli_args),
|
||||
("sign", Some(sub_cli_args)) => sign(sub_cli_args),
|
||||
("verify", Some(sub_cli_args)) => verify(sub_cli_args),
|
||||
_ => {
|
||||
crate::print_help(true);
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +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.
|
||||
*/
|
||||
/****/
|
||||
|
|
@ -1,13 +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.
|
||||
*/
|
||||
/****/
|
||||
|
|
@ -1,116 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
||||
use zerotier_core::*;
|
||||
|
||||
fn new_(cli_args: &ArgMatches) -> i32 {
|
||||
let revision = cli_args.value_of("revision").map_or(crate::utils::ms_since_epoch(), |ts| {
|
||||
if ts.is_empty() {
|
||||
0_i64
|
||||
} else {
|
||||
i64::from_str_radix(ts, 10).unwrap_or(0)
|
||||
}
|
||||
});
|
||||
if revision < 0 {
|
||||
println!("ERROR: invalid revision (must be >= 0).");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let identity = crate::utils::read_identity(cli_args.value_of("identity").unwrap(), true);
|
||||
if identity.is_err() {
|
||||
println!("ERROR: identity invalid: {}", identity.err().unwrap());
|
||||
return 1;
|
||||
}
|
||||
let identity = identity.unwrap();
|
||||
if !identity.has_private() {
|
||||
println!("ERROR: identity must include secret key to create and sign a locator.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let endpoints_cli = cli_args.values_of("endpoint");
|
||||
if endpoints_cli.is_none() {
|
||||
println!("ERROR: at least one endpoint required.");
|
||||
return 1;
|
||||
}
|
||||
let mut endpoints: Vec<Endpoint> = Vec::new();
|
||||
let mut endpoint_bad = false;
|
||||
endpoints_cli.unwrap().for_each(|ep_str| {
|
||||
Endpoint::new_from_string(ep_str).map_or_else(|e| {
|
||||
println!("ERROR: endpoint {} invalid: {}", ep_str, e.to_str());
|
||||
endpoint_bad = true;
|
||||
}, |ep| {
|
||||
endpoints.push(ep);
|
||||
});
|
||||
});
|
||||
if endpoint_bad {
|
||||
return 1;
|
||||
}
|
||||
|
||||
Locator::new(&identity, revision, &endpoints).map_or_else(|e| {
|
||||
println!("ERROR: failure creating locator: {}", e.to_str());
|
||||
1
|
||||
}, |loc| {
|
||||
println!("{}", loc.to_string());
|
||||
0
|
||||
})
|
||||
}
|
||||
|
||||
fn verify(cli_args: &ArgMatches) -> i32 {
|
||||
let identity = crate::utils::read_identity(cli_args.value_of("identity").unwrap(), true);
|
||||
if identity.is_err() {
|
||||
println!("ERROR: identity invalid: {}", identity.err().unwrap());
|
||||
return 1;
|
||||
}
|
||||
let identity = identity.unwrap();
|
||||
let locator = crate::utils::read_locator(cli_args.value_of("locator").unwrap());
|
||||
if locator.is_err() {
|
||||
println!("ERROR: locator invalid: {}", locator.err().unwrap());
|
||||
return 1;
|
||||
}
|
||||
if locator.unwrap().verify(&identity) {
|
||||
println!("OK");
|
||||
0
|
||||
} else {
|
||||
println!("FAILED");
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
fn show(cli_args: &ArgMatches) -> i32 {
|
||||
let locator = crate::utils::read_locator(cli_args.value_of("locator").unwrap());
|
||||
if locator.is_err() {
|
||||
println!("ERROR: locator invalid: {}", locator.err().unwrap());
|
||||
return 1;
|
||||
}
|
||||
let locator = locator.unwrap();
|
||||
println!("{} revision {}", locator.signer().to_string(), locator.revision());
|
||||
let endpoints = locator.endpoints();
|
||||
for ep in endpoints.iter() {
|
||||
println!(" {}", (*ep).to_string())
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
pub(crate) fn run(cli_args: &ArgMatches) -> i32 {
|
||||
match cli_args.subcommand() {
|
||||
("new", Some(sub_cli_args)) => new_(sub_cli_args),
|
||||
("verify", Some(sub_cli_args)) => verify(sub_cli_args),
|
||||
("show", Some(sub_cli_args)) => show(sub_cli_args),
|
||||
_ => {
|
||||
crate::print_help(true);
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
pub(crate) mod status;
|
||||
pub(crate) mod set;
|
||||
pub(crate) mod peer;
|
||||
pub(crate) mod network;
|
||||
pub(crate) mod join;
|
||||
pub(crate) mod leave;
|
||||
pub(crate) mod controller;
|
||||
pub(crate) mod identity;
|
||||
pub(crate) mod locator;
|
||||
pub(crate) mod cert;
|
|
@ -1,13 +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.
|
||||
*/
|
||||
/****/
|
||||
|
|
@ -1,13 +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.
|
||||
*/
|
||||
/****/
|
||||
|
|
@ -1,13 +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.
|
||||
*/
|
||||
/****/
|
||||
|
|
@ -1,51 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
use hyper::{Uri, Method, StatusCode};
|
||||
use colored::*;
|
||||
|
||||
use crate::store::Store;
|
||||
use crate::httpclient::*;
|
||||
use crate::service::ServiceStatus;
|
||||
use crate::{GlobalFlags, HTTP_API_OBJECT_SIZE_LIMIT};
|
||||
|
||||
pub(crate) async fn run(store: Arc<Store>, global_flags: GlobalFlags, client: HttpClient, api_base_uri: Uri, auth_token: String) -> Result<i32, Box<dyn Error>> {
|
||||
let uri = append_uri_path(api_base_uri, "/status").unwrap();
|
||||
let mut res = request(&client, Method::GET, uri, None, auth_token.as_str()).await?;
|
||||
|
||||
match res.status() {
|
||||
StatusCode::OK => {
|
||||
let status = read_object_limited::<ServiceStatus>(res.body_mut(), HTTP_API_OBJECT_SIZE_LIMIT).await?;
|
||||
|
||||
if global_flags.json_output {
|
||||
println!("{}", serde_json::to_string_pretty(&status).unwrap())
|
||||
} else {
|
||||
println!("address {} version {} status {}",
|
||||
status.address.to_string().as_str().bright_white(),
|
||||
status.version.as_str().bright_white(),
|
||||
if status.online {
|
||||
"ONLINE".bright_green()
|
||||
} else {
|
||||
"OFFLINE".bright_red()
|
||||
});
|
||||
// TODO: print more detailed status information
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
},
|
||||
_ => Err(Box::new(UnexpectedStatusCodeError(res.status(), "")))
|
||||
}
|
||||
}
|
|
@ -1,349 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::os::raw::c_int;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use num_traits::cast::AsPrimitive;
|
||||
|
||||
use zerotier_core::{Buffer, InetAddress, InetAddressFamily};
|
||||
|
||||
use crate::osdep as osdep;
|
||||
|
||||
/*
|
||||
* This is a threaded UDP socket listener for high performance. The fastest way to receive UDP
|
||||
* (without heroic efforts like kernel bypass) on most platforms is to create a separate socket
|
||||
* for each thread using options like SO_REUSEPORT and concurrent packet listening.
|
||||
*/
|
||||
|
||||
#[cfg(windows)]
|
||||
use winapi::um::winsock2 as winsock2;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) type FastUDPRawOsSocket = winsock2::SOCKET;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) type FastUDPRawOsSocket = c_int;
|
||||
|
||||
#[cfg(unix)]
|
||||
fn bind_udp_socket(_device_name: &str, address: &InetAddress) -> Result<FastUDPRawOsSocket, &'static str> {
|
||||
unsafe {
|
||||
let (af, sa_len) = match address.family() {
|
||||
InetAddressFamily::IPv4 => (osdep::AF_INET, std::mem::size_of::<osdep::sockaddr_in>() as osdep::socklen_t),
|
||||
InetAddressFamily::IPv6 => (osdep::AF_INET6, std::mem::size_of::<osdep::sockaddr_in6>() as osdep::socklen_t),
|
||||
_ => {
|
||||
return Err("unrecognized address family");
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let s = osdep::socket(af.as_(), osdep::SOCK_DGRAM.as_(), 0);
|
||||
#[cfg(target_os = "linux")]
|
||||
let s = osdep::socket(af.as_(), 2, 0);
|
||||
|
||||
if s < 0 {
|
||||
return Err("unable to create socket");
|
||||
}
|
||||
|
||||
let mut fl: c_int;
|
||||
let fl_size = std::mem::size_of::<c_int>() as osdep::socklen_t;
|
||||
let mut setsockopt_results: c_int = 0;
|
||||
|
||||
fl = 1;
|
||||
setsockopt_results |= osdep::setsockopt(s, osdep::SOL_SOCKET.as_(), osdep::SO_REUSEPORT.as_(), (&mut fl as *mut c_int).cast(), fl_size);
|
||||
//fl = 1;
|
||||
//setsockopt_results |= osdep::setsockopt(s, osdep::SOL_SOCKET, osdep::SO_REUSEADDR, (&mut fl as *mut c_int).cast(), fl_size);
|
||||
fl = 1;
|
||||
setsockopt_results |= osdep::setsockopt(s, osdep::SOL_SOCKET.as_(), osdep::SO_BROADCAST.as_(), (&mut fl as *mut c_int).cast(), fl_size);
|
||||
if af == osdep::AF_INET6 {
|
||||
fl = 1;
|
||||
setsockopt_results |= osdep::setsockopt(s, osdep::IPPROTO_IPV6.as_(), osdep::IPV6_V6ONLY.as_(), (&mut fl as *mut c_int).cast(), fl_size);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))] {
|
||||
fl = 1;
|
||||
setsockopt_results |= osdep::setsockopt(s, osdep::SOL_SOCKET.as_(), osdep::SO_NOSIGPIPE.as_(), (&mut fl as *mut c_int).cast(), fl_size)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")] {
|
||||
if !_device_name.is_empty() {
|
||||
let _ = std::ffi::CString::new(_device_name).map(|dn| {
|
||||
let dnb = dn.as_bytes_with_nul();
|
||||
let _ = osdep::setsockopt(s.as_(), osdep::SOL_SOCKET.as_(), osdep::SO_BINDTODEVICE.as_(), dnb.as_ptr().cast(), (dnb.len() - 1).as_());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if setsockopt_results != 0 {
|
||||
osdep::close(s);
|
||||
return Err("setsockopt() failed");
|
||||
}
|
||||
|
||||
if af == osdep::AF_INET {
|
||||
#[cfg(not(target_os = "linux"))] {
|
||||
fl = 0;
|
||||
osdep::setsockopt(s, osdep::IPPROTO_IP.as_(), osdep::IP_DF.as_(), (&mut fl as *mut c_int).cast(), fl_size);
|
||||
}
|
||||
#[cfg(target_os = "linux")] {
|
||||
fl = osdep::IP_PMTUDISC_DONT as c_int;
|
||||
osdep::setsockopt(s, osdep::IPPROTO_IP.as_(), osdep::IP_MTU_DISCOVER.as_(), (&mut fl as *mut c_int).cast(), fl_size);
|
||||
}
|
||||
}
|
||||
|
||||
if af == osdep::AF_INET6 {
|
||||
fl = 0;
|
||||
osdep::setsockopt(s, osdep::IPPROTO_IPV6.as_(), osdep::IPV6_DONTFRAG.as_(), (&mut fl as *mut c_int).cast(), fl_size);
|
||||
}
|
||||
|
||||
fl = 1048576;
|
||||
while fl >= 131072 {
|
||||
if osdep::setsockopt(s, osdep::SOL_SOCKET.as_(), osdep::SO_RCVBUF.as_(), (&mut fl as *mut c_int).cast(), fl_size) == 0 {
|
||||
break;
|
||||
}
|
||||
fl -= 65536;
|
||||
}
|
||||
fl = 1048576;
|
||||
while fl >= 131072 {
|
||||
if osdep::setsockopt(s, osdep::SOL_SOCKET.as_(), osdep::SO_SNDBUF.as_(), (&mut fl as *mut c_int).cast(), fl_size) == 0 {
|
||||
break;
|
||||
}
|
||||
fl -= 65536;
|
||||
}
|
||||
|
||||
if osdep::bind(s, (address as *const InetAddress).cast(), sa_len) != 0 {
|
||||
osdep::close(s);
|
||||
return Err("bind to address failed");
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// A multi-threaded (or otherwise fast) UDP socket that binds to both IPv4 and IPv6 addresses.
|
||||
pub(crate) struct FastUDPSocket {
|
||||
threads: Vec<std::thread::JoinHandle<()>>,
|
||||
thread_run: Arc<AtomicBool>,
|
||||
sockets: Vec<FastUDPRawOsSocket>,
|
||||
pub bind_address: InetAddress,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[inline(always)]
|
||||
fn fast_udp_socket_close(socket: &FastUDPRawOsSocket) {
|
||||
unsafe {
|
||||
osdep::close(*socket);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[inline(always)]
|
||||
fn fast_udp_socket_close(socket: &FastUDPRawOsSocket) {
|
||||
unsafe {
|
||||
osdep::close(*socket);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn fast_udp_socket_to_i64(socket: &FastUDPRawOsSocket) -> i64 {
|
||||
(*socket) as i64
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn fast_udp_socket_from_i64(socket: i64) -> Option<FastUDPRawOsSocket> {
|
||||
if socket >= 0 {
|
||||
return Some(socket as FastUDPRawOsSocket);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Send to a raw UDP socket with optional packet TTL.
|
||||
/// If the packet_ttl option is <=0, packet is sent with the default TTL. TTL setting is only used
|
||||
/// in ZeroTier right now to do escalating TTL probes for IPv4 NAT traversal.
|
||||
#[cfg(unix)]
|
||||
#[inline(always)]
|
||||
pub(crate) fn fast_udp_socket_sendto(socket: &FastUDPRawOsSocket, to_address: &InetAddress, data: *const u8, len: usize, packet_ttl: i32) {
|
||||
unsafe {
|
||||
if packet_ttl <= 0 {
|
||||
osdep::sendto(*socket, data.cast(), len.as_(), 0, (to_address as *const InetAddress).cast(), std::mem::size_of::<InetAddress>().as_());
|
||||
} else {
|
||||
let mut ttl = packet_ttl as c_int;
|
||||
osdep::setsockopt(*socket, osdep::IPPROTO_IP.as_(), osdep::IP_TTL.as_(), (&mut ttl as *mut c_int).cast(), std::mem::size_of::<c_int>().as_());
|
||||
osdep::sendto(*socket, data.cast(), len.as_(), 0, (to_address as *const InetAddress).cast(), std::mem::size_of::<InetAddress>().as_());
|
||||
ttl = 255;
|
||||
osdep::setsockopt(*socket, osdep::IPPROTO_IP.as_(), osdep::IP_TTL.as_(), (&mut ttl as *mut c_int).cast(), std::mem::size_of::<c_int>().as_());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[inline(always)]
|
||||
pub(crate) fn fast_udp_socket_sendto(socket: &FastUDPRawOsSocket, to_address: &InetAddress, data: &[u8], packet_ttl: i32) {}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[inline(always)]
|
||||
fn fast_udp_socket_recvfrom(socket: &FastUDPRawOsSocket, buf: &mut Buffer, from_address: &mut InetAddress) -> i32 {
|
||||
unsafe {
|
||||
let mut addrlen = std::mem::size_of::<InetAddress>() as osdep::socklen_t;
|
||||
osdep::recvfrom(*socket, buf.as_mut_ptr().cast(), Buffer::CAPACITY.as_(), 0, (from_address as *mut InetAddress).cast(), &mut addrlen) as i32
|
||||
}
|
||||
}
|
||||
|
||||
impl FastUDPSocket {
|
||||
pub fn new<F: Fn(&FastUDPRawOsSocket, &InetAddress, Buffer) + Send + Sync + Clone + 'static>(device_name: &str, address: &InetAddress, handler: F) -> Result<FastUDPSocket, String> {
|
||||
let thread_count = num_cpus::get_physical().min(num_cpus::get());
|
||||
|
||||
let mut s = FastUDPSocket {
|
||||
thread_run: Arc::new(AtomicBool::new(true)),
|
||||
threads: Vec::new(),
|
||||
sockets: Vec::new(),
|
||||
bind_address: address.clone(),
|
||||
};
|
||||
s.threads.reserve(thread_count);
|
||||
s.sockets.reserve(thread_count);
|
||||
|
||||
let mut bind_failed_reason: &'static str = "";
|
||||
for _ in 0..thread_count {
|
||||
let thread_socket = bind_udp_socket(device_name, address);
|
||||
if thread_socket.is_ok() {
|
||||
let thread_socket = thread_socket.unwrap();
|
||||
s.sockets.push(thread_socket);
|
||||
|
||||
let thread_run = s.thread_run.clone();
|
||||
let handler_copy = handler.clone();
|
||||
s.threads.push(std::thread::Builder::new().stack_size(zerotier_core::RECOMMENDED_THREAD_STACK_SIZE).spawn(move || {
|
||||
let mut from_address = InetAddress::new();
|
||||
while thread_run.load(Ordering::Relaxed) {
|
||||
let mut buf = Buffer::new();
|
||||
let read_length = fast_udp_socket_recvfrom(&thread_socket, &mut buf, &mut from_address);
|
||||
if read_length > 0 {
|
||||
buf.set_len(read_length as usize);
|
||||
handler_copy(&thread_socket, &from_address, buf);
|
||||
} else if read_length < 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}).unwrap());
|
||||
} else {
|
||||
bind_failed_reason = thread_socket.err().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// This is successful if it is able to bind successfully once and launch at least one thread,
|
||||
// since in a few cases it may be impossible to do multithreaded binding such as old Linux
|
||||
// kernels or emulation layers.
|
||||
if s.sockets.is_empty() {
|
||||
return Err(format!("unable to bind to address for IPv4 or IPv6 ({})", bind_failed_reason));
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// Get a slice of all raw sockets used.
|
||||
#[inline(always)]
|
||||
pub fn all_sockets(&self) -> &[FastUDPRawOsSocket] {
|
||||
self.sockets.as_slice()
|
||||
}
|
||||
|
||||
/// Send from this socket.
|
||||
/// This actually picks a thread's socket and sends from it. Since all
|
||||
/// are bound to the same IP:port which one is chosen doesn't matter.
|
||||
/// Sockets are thread safe.
|
||||
#[inline(always)]
|
||||
pub fn send(&self, to_address: &InetAddress, data: *const u8, len: usize, packet_ttl: i32) {
|
||||
fast_udp_socket_sendto(self.sockets.get(0).unwrap(), to_address, data, len, packet_ttl);
|
||||
}
|
||||
|
||||
/// Get a raw socket that can be used to send UDP packets.
|
||||
#[inline(always)]
|
||||
pub fn raw_socket(&self) -> FastUDPRawOsSocket {
|
||||
*self.sockets.get(0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FastUDPSocket {
|
||||
#[cfg(windows)]
|
||||
fn drop(&mut self) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn drop(&mut self) {
|
||||
let tmp: [u8; 1] = [0];
|
||||
self.thread_run.store(false, Ordering::Relaxed);
|
||||
for s in self.sockets.iter() {
|
||||
unsafe {
|
||||
osdep::sendto(*s, tmp.as_ptr().cast(), 0, 0, (&self.bind_address as *const InetAddress).cast(), std::mem::size_of::<InetAddress>() as osdep::socklen_t);
|
||||
}
|
||||
}
|
||||
for s in self.sockets.iter() {
|
||||
unsafe {
|
||||
osdep::shutdown(*s, osdep::SHUT_RDWR.as_());
|
||||
}
|
||||
}
|
||||
for s in self.sockets.iter() {
|
||||
unsafe {
|
||||
osdep::close(*s);
|
||||
}
|
||||
}
|
||||
while !self.threads.is_empty() {
|
||||
let _ = self.threads.pop().unwrap().join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use zerotier_core::{Buffer, InetAddress};
|
||||
|
||||
use crate::fastudpsocket::*;
|
||||
|
||||
#[test]
|
||||
fn test_udp_bind_and_transfer() {
|
||||
{
|
||||
let ba0 = InetAddress::new_from_string("127.0.0.1/23333");
|
||||
assert!(ba0.is_some());
|
||||
let ba0 = ba0.unwrap();
|
||||
let cnt0 = Arc::new(AtomicU32::new(0));
|
||||
let cnt0c = cnt0.clone();
|
||||
let s0 = FastUDPSocket::new("", &ba0, move |sock: &FastUDPRawOsSocket, _: &InetAddress, data: Buffer| {
|
||||
cnt0c.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
assert!(s0.is_ok());
|
||||
let s0 = s0.unwrap();
|
||||
|
||||
let ba1 = InetAddress::new_from_string("127.0.0.1/23334");
|
||||
assert!(ba1.is_some());
|
||||
let ba1 = ba1.unwrap();
|
||||
let cnt1 = Arc::new(AtomicU32::new(0));
|
||||
let cnt1c = cnt1.clone();
|
||||
let s1 = FastUDPSocket::new("", &ba1, move |sock: &FastUDPRawOsSocket, _: &InetAddress, data: Buffer| {
|
||||
cnt1c.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
assert!(s1.is_ok());
|
||||
let s1 = s1.unwrap();
|
||||
|
||||
let data_bytes = [0_u8; 1024];
|
||||
loop {
|
||||
s0.send(&ba1, data_bytes.as_ptr(), data_bytes.len(), 0);
|
||||
s1.send(&ba0, data_bytes.as_ptr(), data_bytes.len(), 0);
|
||||
if cnt0.load(Ordering::Relaxed) > 10000 && cnt1.load(Ordering::Relaxed) > 10000 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//println!("FastUDPSocket shutdown successful");
|
||||
}
|
||||
}
|
|
@ -1,103 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::mem::size_of;
|
||||
use std::ptr::{copy_nonoverlapping, null_mut};
|
||||
|
||||
use zerotier_core::InetAddress;
|
||||
|
||||
use crate::osdep as osdep;
|
||||
|
||||
fn s6_addr_as_ptr<A>(a: &A) -> *const A {
|
||||
a as *const A
|
||||
}
|
||||
|
||||
/// Call supplied function or closure for each physical IP address in the system.
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn for_each_address<F: FnMut(&InetAddress, &str)>(mut f: F) {
|
||||
unsafe {
|
||||
let mut ifa_name = [0_u8; osdep::IFNAMSIZ as usize];
|
||||
let mut ifap: *mut osdep::ifaddrs = null_mut();
|
||||
if osdep::getifaddrs((&mut ifap as *mut *mut osdep::ifaddrs).cast()) == 0 {
|
||||
let mut i = ifap;
|
||||
while !i.is_null() {
|
||||
if !(*i).ifa_addr.is_null() {
|
||||
let mut a = InetAddress::new();
|
||||
|
||||
let sa_family = (*(*i).ifa_addr).sa_family as u8;
|
||||
if sa_family == osdep::AF_INET as u8 {
|
||||
copy_nonoverlapping((*i).ifa_addr.cast::<u8>(), (&mut a as *mut InetAddress).cast::<u8>(), size_of::<osdep::sockaddr_in>());
|
||||
} else if sa_family == osdep::AF_INET6 as u8 {
|
||||
copy_nonoverlapping((*i).ifa_addr.cast::<u8>(), (&mut a as *mut InetAddress).cast::<u8>(), size_of::<osdep::sockaddr_in6>());
|
||||
} else {
|
||||
i = (*i).ifa_next;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut netmask_bits: u16 = 0;
|
||||
if !(*i).ifa_netmask.is_null() {
|
||||
if sa_family == osdep::AF_INET as u8 {
|
||||
let a = (*(*i).ifa_netmask.cast::<osdep::sockaddr_in>()).sin_addr.s_addr as u32;
|
||||
netmask_bits = a.leading_ones() as u16;
|
||||
} else if sa_family == osdep::AF_INET6 as u8 {
|
||||
let a = s6_addr_as_ptr(&((*(*i).ifa_netmask.cast::<osdep::sockaddr_in6>()).sin6_addr)).cast::<u8>();
|
||||
for i in 0..16 as isize {
|
||||
let b = *a.offset(i);
|
||||
if b == 0xff {
|
||||
netmask_bits += 8;
|
||||
} else {
|
||||
netmask_bits += b.leading_ones() as u16;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
a.set_port(netmask_bits);
|
||||
|
||||
let mut namlen: usize = 0;
|
||||
while namlen < (osdep::IFNAMSIZ as usize) {
|
||||
let c = *(*i).ifa_name.offset(namlen as isize);
|
||||
if c != 0 {
|
||||
ifa_name[namlen] = c as u8;
|
||||
namlen += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if namlen > 0 {
|
||||
let dev = String::from_utf8_lossy(&ifa_name[0..namlen]);
|
||||
if dev.len() > 0 {
|
||||
f(&a, dev.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
i = (*i).ifa_next;
|
||||
}
|
||||
osdep::freeifaddrs(ifap.cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use zerotier_core::InetAddress;
|
||||
|
||||
#[test]
|
||||
fn test_getifaddrs() {
|
||||
println!("starting getifaddrs...");
|
||||
crate::getifaddrs::for_each_address(|a: &InetAddress, dev: &str| {
|
||||
println!(" {} {}", dev, a.to_string())
|
||||
});
|
||||
println!("done.")
|
||||
}
|
||||
}
|
|
@ -1,212 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::error::Error;
|
||||
use std::future::Future;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::stream::StreamExt;
|
||||
use hyper::{Body, Method, Request, Response, StatusCode, Uri};
|
||||
use hyper::http::uri::{Authority, PathAndQuery, Scheme};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::GlobalFlags;
|
||||
use crate::store::Store;
|
||||
|
||||
pub(crate) type HttpClient = Rc<hyper::Client<hyper::client::HttpConnector, Body>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct IncorrectAuthTokenError;
|
||||
|
||||
impl Error for IncorrectAuthTokenError {}
|
||||
|
||||
impl std::fmt::Display for IncorrectAuthTokenError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "401 UNAUTHORIZED (incorrect authorization token or not allowed to read token)")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UnexpectedStatusCodeError(pub StatusCode, pub &'static str);
|
||||
|
||||
impl Error for UnexpectedStatusCodeError {}
|
||||
|
||||
impl std::fmt::Display for UnexpectedStatusCodeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
if self.1.is_empty() {
|
||||
write!(f, "{} {} (???)", self.0.as_str(), self.0.canonical_reason().unwrap_or("???"))
|
||||
} else {
|
||||
write!(f, "{} {} ({})", self.0.as_str(), self.0.canonical_reason().unwrap_or("???"), self.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Launch the supplied function with a ready to go HTTP client, the auth token, and the API URI.
|
||||
/// This is boilerplate code for CLI commands that invoke the HTTP API. Since it instantiates and
|
||||
/// then kills a tokio runtime, it's not for use in the service code that runs in a long-running
|
||||
/// tokio runtime.
|
||||
pub(crate) fn run_command<
|
||||
R: Future<Output = Result<i32, Box<dyn Error>>>,
|
||||
F: FnOnce(Arc<Store>, GlobalFlags, HttpClient, Uri, String) -> R
|
||||
>(store: Arc<Store>, global_flags: GlobalFlags, func: F) -> i32 {
|
||||
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
|
||||
let code = rt.block_on(async move {
|
||||
let uri = store.load_uri();
|
||||
if uri.is_err() {
|
||||
println!("ERROR: 'zerotier.uri' not found in '{}', unable to get service API endpoint.", store.base_path.to_str().unwrap());
|
||||
1
|
||||
} else {
|
||||
let auth_token = store.auth_token(false);
|
||||
if auth_token.is_err() {
|
||||
println!("ERROR: unable to read API authorization token from '{}': {}", store.base_path.to_str().unwrap(), auth_token.err().unwrap().to_string());
|
||||
1
|
||||
} else {
|
||||
let uri = uri.unwrap();
|
||||
let uri_str = uri.to_string();
|
||||
func(store, global_flags, Rc::new(hyper::Client::builder().http1_max_buf_size(65536).build_http()), uri, auth_token.unwrap()).await.map_or_else(|e| {
|
||||
println!("ERROR: service API HTTP request ({}) failed: {}", uri_str, e);
|
||||
println!();
|
||||
println!("Common causes: service is not running, authorization token incorrect");
|
||||
println!("or not readable, or a local firewall is blocking loopback connections.");
|
||||
1
|
||||
}, |code| {
|
||||
code
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
rt.shutdown_timeout(Duration::from_millis(1)); // all tasks should be done in a command anyway, this is just a sanity check
|
||||
code
|
||||
}
|
||||
|
||||
/// Send a request to the API with support for HTTP digest authentication.
|
||||
/// The data option is for PUT and POST requests. For GET it is ignored. This will try to
|
||||
/// authenticate if a WWW-Authorized header is sent in an unauthorized response. If authentication
|
||||
/// with auth_token fails, IncorrectAuthTokenError is returned as an error. If the request is
|
||||
/// unauthorizred and no WWW-Authorired header is present, a normal response is returned. The
|
||||
/// caller must always check the response status code.
|
||||
pub(crate) async fn request(client: &HttpClient, method: Method, uri: Uri, data: Option<&[u8]>, auth_token: &str) -> Result<Response<Body>, Box<dyn Error>> {
|
||||
let body: Vec<u8> = data.map_or_else(|| Vec::new(), |data| data.to_vec());
|
||||
|
||||
let req = Request::builder().method(&method).version(hyper::Version::HTTP_11).uri(&uri).body(Body::from(body.clone()));
|
||||
if req.is_err() {
|
||||
return Err(Box::new(req.err().unwrap()));
|
||||
}
|
||||
let res = client.request(req.unwrap()).await;
|
||||
if res.is_err() {
|
||||
return Err(Box::new(res.err().unwrap()));
|
||||
}
|
||||
let res = res.unwrap();
|
||||
|
||||
if res.status() == StatusCode::UNAUTHORIZED {
|
||||
let auth = res.headers().get(hyper::header::WWW_AUTHENTICATE);
|
||||
if auth.is_none() {
|
||||
return Ok(res);
|
||||
}
|
||||
let auth = auth.unwrap().to_str();
|
||||
if auth.is_err() {
|
||||
return Err(Box::new(auth.err().unwrap()));
|
||||
}
|
||||
let auth = digest_auth::parse(auth.unwrap());
|
||||
if auth.is_err() {
|
||||
return Err(Box::new(auth.err().unwrap()));
|
||||
}
|
||||
let ac = digest_auth::AuthContext::new_with_method("", auth_token, uri.to_string(), None::<&[u8]>, match method {
|
||||
Method::GET => digest_auth::HttpMethod::GET,
|
||||
Method::POST => digest_auth::HttpMethod::POST,
|
||||
Method::HEAD => digest_auth::HttpMethod::HEAD,
|
||||
Method::PUT => digest_auth::HttpMethod::OTHER("PUT"),
|
||||
Method::DELETE => digest_auth::HttpMethod::OTHER("DELETE"),
|
||||
_ => digest_auth::HttpMethod::OTHER(""),
|
||||
});
|
||||
let auth = auth.unwrap().respond(&ac);
|
||||
if auth.is_err() {
|
||||
return Err(Box::new(auth.err().unwrap()));
|
||||
}
|
||||
|
||||
let req = Request::builder().method(&method).version(hyper::Version::HTTP_11).uri(&uri).header(hyper::header::AUTHORIZATION, auth.unwrap().to_header_string()).body(Body::from(body));
|
||||
if req.is_err() {
|
||||
return Err(Box::new(req.err().unwrap()));
|
||||
}
|
||||
let res = client.request(req.unwrap()).await;
|
||||
if res.is_err() {
|
||||
return Err(Box::new(res.err().unwrap()));
|
||||
}
|
||||
let res = res.unwrap();
|
||||
|
||||
if res.status() == StatusCode::UNAUTHORIZED {
|
||||
return Err(Box::new(IncorrectAuthTokenError));
|
||||
}
|
||||
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
/// Append to a URI path, returning None on error or a new Uri.
|
||||
pub(crate) fn append_uri_path(uri: Uri, new_path: &str) -> Option<Uri> {
|
||||
let parts = uri.into_parts();
|
||||
let mut path = parts.path_and_query.map_or_else(|| String::new(), |pq| pq.to_string());
|
||||
while path.ends_with("/") {
|
||||
let _ = path.pop();
|
||||
}
|
||||
path.push_str(new_path);
|
||||
let path = PathAndQuery::from_str(path.as_str());
|
||||
if path.is_err() {
|
||||
None
|
||||
} else {
|
||||
Uri::builder()
|
||||
.scheme(parts.scheme.unwrap_or(Scheme::HTTP))
|
||||
.authority(parts.authority.unwrap_or(Authority::from_static("127.0.0.1")))
|
||||
.path_and_query(path.unwrap())
|
||||
.build()
|
||||
.map_or_else(|_| None, |uri| Some(uri))
|
||||
}
|
||||
}
|
||||
|
||||
/// Read HTTP body with a size limit.
|
||||
pub(crate) async fn read_body_limited(body: &mut Body, max_size: usize) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let mut data: Vec<u8> = Vec::new();
|
||||
loop {
|
||||
let blk = body.next().await;
|
||||
if blk.is_some() {
|
||||
let blk = blk.unwrap();
|
||||
if blk.is_err() {
|
||||
return Err(Box::new(blk.err().unwrap()));
|
||||
}
|
||||
for b in blk.unwrap().iter() {
|
||||
data.push(*b);
|
||||
if data.len() >= max_size {
|
||||
return Ok(data);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub(crate) async fn read_object_limited<O: DeserializeOwned>(body: &mut Body, max_size: usize) -> Result<O, Box<dyn Error>> {
|
||||
let data = read_body_limited(body, max_size).await?;
|
||||
let obj = serde_json::from_slice(data.as_slice());
|
||||
if obj.is_err() {
|
||||
Err(Box::new(obj.err().unwrap()))
|
||||
} else {
|
||||
Ok(obj.unwrap())
|
||||
}
|
||||
}
|
|
@ -1,206 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::convert::Infallible;
|
||||
use std::sync::Arc;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use hyper::{Body, Request, Response, StatusCode, Method};
|
||||
use hyper::server::Server;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use tokio::task::JoinHandle;
|
||||
use digest_auth::{AuthContext, AuthorizationHeader, Charset, WwwAuthenticateHeader};
|
||||
|
||||
use crate::service::Service;
|
||||
use crate::api;
|
||||
use crate::utils::{decrypt_http_auth_nonce, ms_since_epoch, create_http_auth_nonce};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
const HTTP_MAX_NONCE_AGE_MS: i64 = 30000;
|
||||
|
||||
/// Listener for http connections to the API or for TCP P2P.
|
||||
/// Dropping a listener initiates shutdown of the background hyper Server instance,
|
||||
/// but it might not shut down instantly as this occurs asynchronously.
|
||||
pub(crate) struct HttpListener {
|
||||
pub address: SocketAddr,
|
||||
shutdown_tx: Cell<Option<tokio::sync::oneshot::Sender<()>>>,
|
||||
server: JoinHandle<hyper::Result<()>>,
|
||||
}
|
||||
|
||||
async fn http_handler(service: Arc<Service>, req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
let req_path = req.uri().path();
|
||||
|
||||
let mut authorized = false;
|
||||
let mut stale = false;
|
||||
|
||||
let auth_token = service.store().auth_token(false);
|
||||
if auth_token.is_err() {
|
||||
return Ok(Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR).body(Body::from("authorization token unreadable")).unwrap());
|
||||
}
|
||||
let auth_context = AuthContext::new_with_method("", auth_token.unwrap(), req.uri().to_string(), None::<&[u8]>, match *req.method() {
|
||||
Method::GET => digest_auth::HttpMethod::GET,
|
||||
Method::POST => digest_auth::HttpMethod::POST,
|
||||
Method::HEAD => digest_auth::HttpMethod::HEAD,
|
||||
Method::PUT => digest_auth::HttpMethod::OTHER("PUT"),
|
||||
Method::DELETE => digest_auth::HttpMethod::OTHER("DELETE"),
|
||||
_ => {
|
||||
return Ok(Response::builder().status(StatusCode::METHOD_NOT_ALLOWED).body(Body::from("unrecognized method")).unwrap());
|
||||
}
|
||||
});
|
||||
|
||||
let auth_header = req.headers().get(hyper::header::AUTHORIZATION);
|
||||
if auth_header.is_some() {
|
||||
let auth_header = AuthorizationHeader::parse(auth_header.unwrap().to_str().unwrap_or(""));
|
||||
if auth_header.is_err() {
|
||||
return Ok(Response::builder().status(StatusCode::BAD_REQUEST).body(Body::from(format!("invalid authorization header: {}", auth_header.err().unwrap().to_string()))).unwrap());
|
||||
}
|
||||
let auth_header = auth_header.unwrap();
|
||||
|
||||
let mut expected = AuthorizationHeader {
|
||||
realm: "zerotier-service-api".to_owned(),
|
||||
nonce: auth_header.nonce.clone(),
|
||||
opaque: None,
|
||||
userhash: false,
|
||||
algorithm: digest_auth::Algorithm::new(digest_auth::AlgorithmType::SHA2_512_256, false),
|
||||
response: String::new(),
|
||||
username: String::new(),
|
||||
uri: req.uri().to_string(),
|
||||
qop: Some(digest_auth::Qop::AUTH),
|
||||
cnonce: auth_header.cnonce.clone(),
|
||||
nc: auth_header.nc,
|
||||
};
|
||||
expected.digest(&auth_context);
|
||||
if auth_header.response == expected.response {
|
||||
if (ms_since_epoch() - decrypt_http_auth_nonce(auth_header.nonce.as_str())) <= HTTP_MAX_NONCE_AGE_MS {
|
||||
authorized = true;
|
||||
} else {
|
||||
stale = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if authorized {
|
||||
let (status, body) =
|
||||
if req_path == "/status" {
|
||||
api::status(service, req)
|
||||
} else if req_path == "/config" {
|
||||
api::config(service, req)
|
||||
} else if req_path.starts_with("/peer") {
|
||||
api::peer(service, req)
|
||||
} else if req_path.starts_with("/network") {
|
||||
api::network(service, req)
|
||||
} else if req_path.starts_with("/controller") {
|
||||
(StatusCode::NOT_IMPLEMENTED, Body::from("not implemented yet"))
|
||||
} else if req_path == "/teapot" {
|
||||
(StatusCode::IM_A_TEAPOT, Body::from("I'm a little teapot short and stout!"))
|
||||
} else {
|
||||
(StatusCode::NOT_FOUND, Body::from("not found"))
|
||||
};
|
||||
Ok(Response::builder().header("Content-Type", "application/json").status(status).body(body).unwrap())
|
||||
} else {
|
||||
Ok(Response::builder().header(hyper::header::WWW_AUTHENTICATE, WwwAuthenticateHeader {
|
||||
domain: None,
|
||||
realm: "zerotier-service-api".to_owned(),
|
||||
nonce: create_http_auth_nonce(ms_since_epoch()),
|
||||
opaque: None,
|
||||
stale,
|
||||
algorithm: digest_auth::Algorithm::new(digest_auth::AlgorithmType::SHA2_512_256, false),
|
||||
qop: Some(vec![digest_auth::Qop::AUTH]),
|
||||
userhash: false,
|
||||
charset: Charset::ASCII,
|
||||
nc: 0,
|
||||
}.to_string()).status(StatusCode::UNAUTHORIZED).body(Body::empty()).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpListener {
|
||||
/// Create a new "background" TCP WebListener using the current tokio reactor async runtime.
|
||||
pub async fn new(_device_name: &str, address: SocketAddr, service: &Arc<Service>) -> Result<HttpListener, Box<dyn std::error::Error>> {
|
||||
let listener = if address.is_ipv4() {
|
||||
let listener = socket2::Socket::new(socket2::Domain::ipv4(), socket2::Type::stream(), Some(socket2::Protocol::tcp()));
|
||||
if listener.is_err() {
|
||||
return Err(Box::new(listener.err().unwrap()));
|
||||
}
|
||||
let listener = listener.unwrap();
|
||||
#[cfg(unix)] {
|
||||
let _ = listener.set_reuse_port(true);
|
||||
}
|
||||
listener
|
||||
} else {
|
||||
let listener = socket2::Socket::new(socket2::Domain::ipv6(), socket2::Type::stream(), Some(socket2::Protocol::tcp()));
|
||||
if listener.is_err() {
|
||||
return Err(Box::new(listener.err().unwrap()));
|
||||
}
|
||||
let listener = listener.unwrap();
|
||||
#[cfg(unix)] {
|
||||
let _ = listener.set_reuse_port(true);
|
||||
}
|
||||
let _ = listener.set_only_v6(true);
|
||||
listener
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")] {
|
||||
if !_device_name.is_empty() {
|
||||
let sock = listener.as_raw_fd();
|
||||
unsafe {
|
||||
let _ = std::ffi::CString::new(_device_name).map(|dn| {
|
||||
let dnb = dn.as_bytes_with_nul();
|
||||
let _ = crate::osdep::setsockopt(sock as std::os::raw::c_int, crate::osdep::SOL_SOCKET as std::os::raw::c_int, crate::osdep::SO_BINDTODEVICE as std::os::raw::c_int, dnb.as_ptr().cast(), (dnb.len() - 1) as crate::osdep::socklen_t);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let addr = socket2::SockAddr::from(address);
|
||||
if let Err(e) = listener.bind(&addr) {
|
||||
return Err(Box::new(e));
|
||||
}
|
||||
if let Err(e) = listener.listen(128) {
|
||||
return Err(Box::new(e));
|
||||
}
|
||||
let listener = listener.into_tcp_listener();
|
||||
|
||||
let builder = Server::from_tcp(listener);
|
||||
if builder.is_err() {
|
||||
return Err(Box::new(builder.err().unwrap()));
|
||||
}
|
||||
let builder = builder.unwrap().http1_half_close(false).http1_keepalive(true).http1_max_buf_size(131072);
|
||||
|
||||
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
|
||||
let service = service.clone();
|
||||
let server = tokio::task::spawn(builder.serve(make_service_fn(move |_| {
|
||||
let service = service.clone();
|
||||
async move {
|
||||
Ok::<_, Infallible>(service_fn(move |req: Request<Body>| http_handler(service.clone(), req)))
|
||||
}
|
||||
})).with_graceful_shutdown(async { let _ = shutdown_rx.await; }));
|
||||
|
||||
Ok(HttpListener {
|
||||
address,
|
||||
shutdown_tx: Cell::new(Some(shutdown_tx)),
|
||||
server,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for HttpListener {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.shutdown_tx.take().map(|tx| {
|
||||
let _ = tx.send(());
|
||||
self.server.abort();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,230 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use zerotier_core::{InetAddress, Address, NetworkId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const UNASSIGNED_PRIVILEGED_PORTS: [u16; 299] = [
|
||||
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,
|
||||
];
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct LocalConfigPhysicalPathConfig {
|
||||
pub blacklist: bool
|
||||
}
|
||||
|
||||
impl Default for LocalConfigPhysicalPathConfig {
|
||||
fn default() -> Self {
|
||||
LocalConfigPhysicalPathConfig {
|
||||
blacklist: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct LocalConfigVirtualConfig {
|
||||
#[serde(rename = "try")]
|
||||
pub try_: Vec<InetAddress>
|
||||
}
|
||||
|
||||
impl Default for LocalConfigVirtualConfig {
|
||||
fn default() -> Self {
|
||||
LocalConfigVirtualConfig {
|
||||
try_: Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct LocalConfigNetworkSettings {
|
||||
#[serde(rename = "allowManagedIPs")]
|
||||
pub allow_managed_ips: bool,
|
||||
#[serde(rename = "allowGlobalIPs")]
|
||||
pub allow_global_ips: bool,
|
||||
#[serde(rename = "allowManagedRoutes")]
|
||||
pub allow_managed_routes: bool,
|
||||
#[serde(rename = "allowGlobalRoutes")]
|
||||
pub allow_global_routes: bool,
|
||||
#[serde(rename = "allowDefaultRouteOverride")]
|
||||
pub allow_default_route_override: bool,
|
||||
}
|
||||
|
||||
impl Default for LocalConfigNetworkSettings {
|
||||
fn default() -> Self {
|
||||
LocalConfigNetworkSettings {
|
||||
allow_managed_ips: true,
|
||||
allow_global_ips: false,
|
||||
allow_managed_routes: true,
|
||||
allow_global_routes: false,
|
||||
allow_default_route_override: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct LocalConfigLogSettings {
|
||||
pub path: Option<String>,
|
||||
#[serde(rename = "maxSize")]
|
||||
pub max_size: usize,
|
||||
pub vl1: bool,
|
||||
pub vl2: bool,
|
||||
#[serde(rename = "vl2TraceRules")]
|
||||
pub vl2_trace_rules: bool,
|
||||
#[serde(rename = "vl2TraceMulticast")]
|
||||
pub vl2_trace_multicast: bool,
|
||||
pub debug: bool,
|
||||
pub stderr: bool,
|
||||
}
|
||||
|
||||
impl Default for LocalConfigLogSettings {
|
||||
fn default() -> Self {
|
||||
// TODO: change before release to saner defaults
|
||||
LocalConfigLogSettings {
|
||||
path: None,
|
||||
max_size: 131072,
|
||||
vl1: true,
|
||||
vl2: true,
|
||||
vl2_trace_rules: true,
|
||||
vl2_trace_multicast: true,
|
||||
debug: true,
|
||||
stderr: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct LocalConfigSettings {
|
||||
#[serde(rename = "primaryPort")]
|
||||
pub primary_port: u16,
|
||||
#[serde(rename = "secondaryPort")]
|
||||
pub secondary_port: Option<u16>,
|
||||
#[serde(rename = "autoPortSearch")]
|
||||
pub auto_port_search: bool,
|
||||
#[serde(rename = "portMapping")]
|
||||
pub port_mapping: bool,
|
||||
#[serde(rename = "log")]
|
||||
pub log: LocalConfigLogSettings,
|
||||
#[serde(rename = "interfacePrefixBlacklist")]
|
||||
pub interface_prefix_blacklist: Vec<String>,
|
||||
#[serde(rename = "explicitAddresses")]
|
||||
pub explicit_addresses: Vec<InetAddress>,
|
||||
}
|
||||
|
||||
impl Default for LocalConfigSettings {
|
||||
fn default() -> Self {
|
||||
let mut bl: Vec<String> = Vec::new();
|
||||
bl.reserve(LocalConfigSettings::DEFAULT_PREFIX_BLACKLIST.len());
|
||||
for n in LocalConfigSettings::DEFAULT_PREFIX_BLACKLIST.iter() {
|
||||
bl.push(String::from(*n));
|
||||
}
|
||||
|
||||
LocalConfigSettings {
|
||||
primary_port: zerotier_core::DEFAULT_PORT,
|
||||
secondary_port: Some(zerotier_core::DEFAULT_SECONDARY_PORT),
|
||||
auto_port_search: true,
|
||||
port_mapping: true,
|
||||
log: LocalConfigLogSettings::default(),
|
||||
interface_prefix_blacklist: bl,
|
||||
explicit_addresses: Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalConfigSettings {
|
||||
#[cfg(target_os = "macos")]
|
||||
const DEFAULT_PREFIX_BLACKLIST: [&'static str; 8] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt"];
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt"];
|
||||
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_PREFIX_BLACKLIST: [&'static str; 0] = [];
|
||||
|
||||
pub fn is_interface_blacklisted(&self, ifname: &str) -> bool {
|
||||
for p in self.interface_prefix_blacklist.iter() {
|
||||
if ifname.starts_with(p.as_str()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct LocalConfig {
|
||||
pub physical: BTreeMap<InetAddress, LocalConfigPhysicalPathConfig>,
|
||||
#[serde(rename = "virtual")]
|
||||
pub virtual_: BTreeMap<Address, LocalConfigVirtualConfig>,
|
||||
pub network: BTreeMap<NetworkId, LocalConfigNetworkSettings>,
|
||||
pub settings: LocalConfigSettings,
|
||||
}
|
||||
|
||||
impl Default for LocalConfig {
|
||||
fn default() -> Self {
|
||||
LocalConfig {
|
||||
physical: BTreeMap::new(),
|
||||
virtual_: BTreeMap::new(),
|
||||
network: BTreeMap::new(),
|
||||
settings: LocalConfigSettings::default()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,170 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Seek, SeekFrom, Write, stderr};
|
||||
use std::sync::Mutex;
|
||||
|
||||
struct LogIntl {
|
||||
prefix: String,
|
||||
path: String,
|
||||
file: Option<File>,
|
||||
cur_size: u64,
|
||||
max_size: usize,
|
||||
log_to_stderr: bool,
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
/// It's big it's heavy it's wood.
|
||||
pub(crate) struct Log {
|
||||
inner: Mutex<LogIntl>,
|
||||
}
|
||||
|
||||
impl Log {
|
||||
const MIN_MAX_SIZE: usize = 1024;
|
||||
|
||||
/// Construct a new logger.
|
||||
/// If path is empty logs will not be written to files. If log_to_stderr is also
|
||||
/// false then no logs will be output at all.
|
||||
pub fn new(path: &str, max_size: usize, log_to_stderr: bool, debug: bool, prefix: &str) -> Log {
|
||||
let mut p = String::from(prefix);
|
||||
if !p.is_empty() {
|
||||
p.push(' ');
|
||||
}
|
||||
Log{
|
||||
inner: Mutex::new(LogIntl {
|
||||
prefix: p,
|
||||
path: String::from(path),
|
||||
file: None,
|
||||
cur_size: 0,
|
||||
max_size: if max_size < Log::MIN_MAX_SIZE { Log::MIN_MAX_SIZE } else { max_size },
|
||||
log_to_stderr,
|
||||
debug,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_max_size(&self, new_max_size: usize) {
|
||||
self.inner.lock().unwrap().max_size = if new_max_size < Log::MIN_MAX_SIZE { Log::MIN_MAX_SIZE } else { new_max_size };
|
||||
}
|
||||
|
||||
pub fn set_log_to_stderr(&self, log_to_stderr: bool) {
|
||||
self.inner.lock().unwrap().log_to_stderr = log_to_stderr;
|
||||
}
|
||||
|
||||
pub fn set_debug(&self, debug: bool) {
|
||||
self.inner.lock().unwrap().debug = debug;
|
||||
}
|
||||
|
||||
fn log_internal(&self, l: &mut LogIntl, s: &str, pfx: &'static str) {
|
||||
if !s.is_empty() {
|
||||
let log_line = format!("{}[{}] {}{}\n", l.prefix.as_str(), chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(), pfx, s);
|
||||
if !l.path.is_empty() {
|
||||
if l.file.is_none() {
|
||||
let f = OpenOptions::new().read(true).write(true).create(true).open(l.path.as_str());
|
||||
if f.is_err() {
|
||||
return;
|
||||
}
|
||||
let mut f = f.unwrap();
|
||||
let eof = f.seek(SeekFrom::End(0));
|
||||
if eof.is_err() {
|
||||
return;
|
||||
}
|
||||
l.cur_size = eof.unwrap();
|
||||
l.file = Some(f);
|
||||
}
|
||||
|
||||
if l.max_size > 0 && l.cur_size > l.max_size as u64 {
|
||||
l.file = None;
|
||||
l.cur_size = 0;
|
||||
|
||||
let mut old_path = l.path.clone();
|
||||
old_path.push_str(".old");
|
||||
let _ = std::fs::remove_file(old_path.as_str());
|
||||
let _ = std::fs::rename(l.path.as_str(), old_path.as_str());
|
||||
let _ = std::fs::remove_file(l.path.as_str()); // should fail
|
||||
|
||||
let f = OpenOptions::new().read(true).write(true).create(true).open(l.path.as_str());
|
||||
if f.is_err() {
|
||||
return;
|
||||
}
|
||||
l.file = Some(f.unwrap());
|
||||
}
|
||||
|
||||
let f = l.file.as_mut().unwrap();
|
||||
let e = f.write_all(log_line.as_bytes());
|
||||
if e.is_err() {
|
||||
eprintln!("ERROR: I/O error writing to log: {}", e.err().unwrap().to_string());
|
||||
l.file = None;
|
||||
} else {
|
||||
let _ = f.flush();
|
||||
l.cur_size += log_line.len() as u64;
|
||||
}
|
||||
}
|
||||
|
||||
if l.log_to_stderr {
|
||||
let _ = stderr().write_all(log_line.as_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log<S: AsRef<str>>(&self, s: S) {
|
||||
let mut l = self.inner.lock().unwrap();
|
||||
self.log_internal(&mut (*l), s.as_ref(), "");
|
||||
}
|
||||
|
||||
pub fn debug<S: AsRef<str>>(&self, s: S) {
|
||||
let mut l = self.inner.lock().unwrap();
|
||||
if l.debug {
|
||||
self.log_internal(&mut (*l), s.as_ref(), "DEBUG: ");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fatal<S: AsRef<str>>(&self, s: S) {
|
||||
let mut l = self.inner.lock().unwrap();
|
||||
let ss = s.as_ref();
|
||||
self.log_internal(&mut (*l), ss, "FATAL: ");
|
||||
eprintln!("FATAL: {}", ss);
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! l(
|
||||
($logger:expr, $($arg:tt)*) => {
|
||||
$logger.log(format!($($arg)*))
|
||||
}
|
||||
);
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! d(
|
||||
($logger:expr, $($arg:tt)*) => {
|
||||
$logger.debug(format!($($arg)*))
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl Sync for Log {}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::log::Log;
|
||||
|
||||
#[test]
|
||||
fn test_log() {
|
||||
let l = Log::new("/tmp/ztlogtest.log", 65536, "");
|
||||
for i in 0..100000 {
|
||||
l.log(format!("line {}", i))
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -1,382 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
mod api;
|
||||
mod commands;
|
||||
mod fastudpsocket;
|
||||
mod localconfig;
|
||||
mod getifaddrs;
|
||||
#[macro_use]
|
||||
mod log;
|
||||
mod store;
|
||||
mod network;
|
||||
mod vnic;
|
||||
mod service;
|
||||
mod utils;
|
||||
mod httplistener;
|
||||
mod httpclient;
|
||||
|
||||
#[allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, improper_ctypes)]
|
||||
mod osdep; // bindgen generated
|
||||
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::{App, Arg, ArgMatches, ErrorKind};
|
||||
|
||||
use crate::store::Store;
|
||||
|
||||
pub const HTTP_API_OBJECT_SIZE_LIMIT: usize = 131072;
|
||||
|
||||
fn make_help(long_help: bool) -> String {
|
||||
let ver = zerotier_core::version();
|
||||
format!(r###"ZeroTier Network Hypervisor Service Version {}.{}.{}
|
||||
(c)2013-2021 ZeroTier, Inc.
|
||||
Licensed under the ZeroTier BSL (see LICENSE.txt)
|
||||
|
||||
Usage: zerotier [-...] <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 token on command line
|
||||
|
||||
Common Operations:
|
||||
|
||||
help Show this help
|
||||
longhelp Show help with advanced commands
|
||||
oldhelp Show v1.x legacy commands
|
||||
version Print version (of this binary)
|
||||
|
||||
· status Show node status and configuration
|
||||
|
||||
· set [setting] [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> [Un]blacklist interface prefix
|
||||
· portmap <boolean> Toggle use of uPnP and NAT-PMP
|
||||
|
||||
· peer <command> [option]
|
||||
· show <address> Show detailed peer information
|
||||
· list List peers
|
||||
· listroots List root peers
|
||||
· try <address> <endpoint> [...] Try peer at explicit endpoint
|
||||
|
||||
· network <command> [option]
|
||||
· show <network ID> Show detailed network information
|
||||
· list List networks
|
||||
· set <network ID> [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 routes be set?
|
||||
· defaultroute <boolean> Can default route be overridden?
|
||||
|
||||
· join <network> Join a virtual network
|
||||
· leave <network> Leave a virtual network
|
||||
{}"###,
|
||||
ver.0, ver.1, ver.2, if long_help {
|
||||
r###"
|
||||
Advanced Operations:
|
||||
|
||||
service Start node
|
||||
(usually not invoked directly)
|
||||
|
||||
controller <command> [option]
|
||||
· list List networks on controller
|
||||
· new Create a new network
|
||||
· set <network> [setting] [value] Show or modify network settings
|
||||
· show <network> [<address>] Show network or member status
|
||||
· auth <address> Authorize a peer
|
||||
· deauth <address> Deauthorize a peer
|
||||
|
||||
identity <command> [args]
|
||||
new [c25519 | p384] Create identity (default: c25519)
|
||||
getpublic <?identity> Extract 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
|
||||
-r <revision> Revision number (default: time)
|
||||
verify <?identity> <?locator> Verify locator signature
|
||||
show <?locator> Show contents of a locator
|
||||
|
||||
cert <command> [args]
|
||||
· list List certificates at local node
|
||||
show <@cert|·serial> Show certificate details
|
||||
newsuid [@secret out] Create a subject unique ID secret
|
||||
newcsr <@csr out> <@secret out> Create a CSR (interactive)
|
||||
sign <@csr> <@cert out> Sign a CSR to create a certificate
|
||||
-u <usage flags (ex: sedacr)> Set usage flags (recommended)
|
||||
s Usage: digital signature
|
||||
n Usage: non-repudiation
|
||||
e Usage: key encipherment
|
||||
d Usage: key decipherment
|
||||
a Usage: key agreement
|
||||
c Usage: certificate signing (CA)
|
||||
r Usage: CRL signing (CA)
|
||||
x Usage: executable signing
|
||||
t Usage: timestamping
|
||||
z Usage: ZeroTier root set
|
||||
-t <timestamp, seconds since epoch> Timestamp (default: current time)
|
||||
-s <validity start time, seconds> Start time (default: timestamp)
|
||||
-l <#y|d|h|m|s... (ex: 1y, 3d12h)> Time to live (default: forever)
|
||||
-i <@issuer | self> Issuer or self-sign (required)
|
||||
-k <@issuer secret> Secret key for issuer (required)
|
||||
verify <@cert> Internally verify certificate
|
||||
· import <@cert> [trust,trust,...] Import certificate into this node
|
||||
ca Trust: root CA or self-signed
|
||||
config Trust: node configuration
|
||||
· export <serial> [@cert] Export a certificate from this node
|
||||
· delete <serial|ALL> Delete certificate from this node
|
||||
· factoryreset Re-import compiled-in default certs
|
||||
|
||||
· Command (or command with argument type) requires a running node.
|
||||
@ Argument is the path to a file containing the object.
|
||||
? Argument can be either the object or a path to it (auto-detected).
|
||||
"###
|
||||
} else { "" })
|
||||
}
|
||||
|
||||
pub(crate) fn print_help(long_help: bool) {
|
||||
let h = make_help(long_help);
|
||||
let _ = std::io::stdout().write_all(h.as_bytes());
|
||||
}
|
||||
|
||||
pub(crate) fn parse_bool(v: &str) -> Result<bool, String> {
|
||||
if !v.is_empty() {
|
||||
match v.chars().next().unwrap() {
|
||||
'y' | 'Y' | '1' | 't' | 'T' => { return Ok(true); }
|
||||
'n' | 'N' | '0' | 'f' | 'F' => { return Ok(false); }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Err(format!("invalid boolean value: '{}'", v))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_valid_bool(v: String) -> Result<(), String> {
|
||||
parse_bool(v.as_str()).map(|_| ())
|
||||
}
|
||||
|
||||
fn is_valid_port(v: String) -> Result<(), String> {
|
||||
let i = u16::from_str(v.as_str()).unwrap_or(0);
|
||||
if i >= 1 {
|
||||
return Ok(());
|
||||
}
|
||||
Err(format!("invalid TCP/IP port number: {}", v))
|
||||
}
|
||||
|
||||
fn make_store(cli_args: &ArgMatches) -> Arc<Store> {
|
||||
let zerotier_path = cli_args.value_of("path").map_or_else(|| unsafe { zerotier_core::cstr_to_string(osdep::platformDefaultHomePath(), -1) }, |ztp| ztp.to_string());
|
||||
let store = Store::new(zerotier_path.as_str(), cli_args.value_of("token_path").map_or(None, |tp| Some(tp.to_string())), cli_args.value_of("token").map_or(None, |tok| Some(tok.trim().to_string())));
|
||||
if store.is_err() {
|
||||
eprintln!("FATAL: error accessing directory '{}': {}", zerotier_path, store.err().unwrap().to_string());
|
||||
std::process::exit(1);
|
||||
}
|
||||
Arc::new(store.unwrap())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct GlobalFlags {
|
||||
pub json_output: bool,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_global_flags(cli_args: &ArgMatches) -> GlobalFlags {
|
||||
GlobalFlags {
|
||||
json_output: cli_args.is_present("json")
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli_args = {
|
||||
let help = make_help(false);
|
||||
let args = App::new("zerotier")
|
||||
.arg(Arg::with_name("json").short("j"))
|
||||
.arg(Arg::with_name("path").short("p").takes_value(true))
|
||||
.arg(Arg::with_name("token_path").short("t").takes_value(true))
|
||||
.arg(Arg::with_name("token").short("T").takes_value(true))
|
||||
.subcommand(App::new("help"))
|
||||
.subcommand(App::new("version"))
|
||||
.subcommand(App::new("status"))
|
||||
.subcommand(App::new("set")
|
||||
.subcommand(App::new("port")
|
||||
.arg(Arg::with_name("port#").index(1).validator(is_valid_port)))
|
||||
.subcommand(App::new("secondaryport")
|
||||
.arg(Arg::with_name("port#").index(1).validator(is_valid_port)))
|
||||
.subcommand(App::new("blacklist")
|
||||
.subcommand(App::new("cidr")
|
||||
.arg(Arg::with_name("ip_bits").index(1))
|
||||
.arg(Arg::with_name("boolean").index(2).validator(is_valid_bool)))
|
||||
.subcommand(App::new("if")
|
||||
.arg(Arg::with_name("prefix").index(1))
|
||||
.arg(Arg::with_name("boolean").index(2).validator(is_valid_bool))))
|
||||
.subcommand(App::new("portmap")
|
||||
.arg(Arg::with_name("boolean").index(1).validator(is_valid_bool))))
|
||||
.subcommand(App::new("peer")
|
||||
.subcommand(App::new("show")
|
||||
.arg(Arg::with_name("address").index(1).required(true)))
|
||||
.subcommand(App::new("list"))
|
||||
.subcommand(App::new("listroots"))
|
||||
.subcommand(App::new("try")))
|
||||
.subcommand(App::new("network")
|
||||
.subcommand(App::new("show")
|
||||
.arg(Arg::with_name("nwid").index(1).required(true)))
|
||||
.subcommand(App::new("list"))
|
||||
.subcommand(App::new("set")
|
||||
.arg(Arg::with_name("nwid").index(1).required(true))
|
||||
.arg(Arg::with_name("setting").index(2).required(false))
|
||||
.arg(Arg::with_name("value").index(3).required(false))))
|
||||
.subcommand(App::new("join")
|
||||
.arg(Arg::with_name("nwid").index(1).required(true)))
|
||||
.subcommand(App::new("leave")
|
||||
.arg(Arg::with_name("nwid").index(1).required(true)))
|
||||
.subcommand(App::new("service"))
|
||||
.subcommand(App::new("controller")
|
||||
.subcommand(App::new("list"))
|
||||
.subcommand(App::new("new"))
|
||||
.subcommand(App::new("set")
|
||||
.arg(Arg::with_name("id").index(1).required(true))
|
||||
.arg(Arg::with_name("setting").index(2))
|
||||
.arg(Arg::with_name("value").index(3)))
|
||||
.subcommand(App::new("show")
|
||||
.arg(Arg::with_name("id").index(1).required(true))
|
||||
.arg(Arg::with_name("member").index(2)))
|
||||
.subcommand(App::new("auth")
|
||||
.arg(Arg::with_name("member").index(1).required(true)))
|
||||
.subcommand(App::new("deauth")
|
||||
.arg(Arg::with_name("member").index(1).required(true))))
|
||||
.subcommand(App::new("identity")
|
||||
.subcommand(App::new("new")
|
||||
.arg(Arg::with_name("type").possible_value("p384").possible_value("c25519").default_value("c25519").index(1)))
|
||||
.subcommand(App::new("getpublic")
|
||||
.arg(Arg::with_name("identity").index(1).required(true)))
|
||||
.subcommand(App::new("fingerprint")
|
||||
.arg(Arg::with_name("identity").index(1).required(true)))
|
||||
.subcommand(App::new("validate")
|
||||
.arg(Arg::with_name("identity").index(1).required(true)))
|
||||
.subcommand(App::new("sign")
|
||||
.arg(Arg::with_name("identity").index(1).required(true))
|
||||
.arg(Arg::with_name("path").index(2).required(true)))
|
||||
.subcommand(App::new("verify")
|
||||
.arg(Arg::with_name("identity").index(1).required(true))
|
||||
.arg(Arg::with_name("path").index(2).required(true))
|
||||
.arg(Arg::with_name("signature").index(3).required(true))))
|
||||
.subcommand(App::new("locator")
|
||||
.subcommand(App::new("new")
|
||||
.arg(Arg::with_name("revision").short("r").required(false))
|
||||
.arg(Arg::with_name("identity").index(1).required(true))
|
||||
.arg(Arg::with_name("endpoint").index(2).multiple(true).required(true)))
|
||||
.subcommand(App::new("verify")
|
||||
.arg(Arg::with_name("identity").index(1).required(true))
|
||||
.arg(Arg::with_name("locator").index(2).required(true)))
|
||||
.subcommand(App::new("show")
|
||||
.arg(Arg::with_name("locator").index(1).required(true))))
|
||||
.subcommand(App::new("cert")
|
||||
.subcommand(App::new("list"))
|
||||
.subcommand(App::new("show")
|
||||
.arg(Arg::with_name("serialorpath").index(1).required(true)))
|
||||
.subcommand(App::new("newsuid")
|
||||
.arg(Arg::with_name("path").index(1).required(false)))
|
||||
.subcommand(App::new("newcsr")
|
||||
.arg(Arg::with_name("csrpath").index(1).required(true))
|
||||
.arg(Arg::with_name("secretpath").index(2).required(true)))
|
||||
.subcommand(App::new("sign")
|
||||
.arg(Arg::with_name("csr").index(1).required(true))
|
||||
.arg(Arg::with_name("certout").index(2).required(true))
|
||||
.arg(Arg::with_name("usage").short("u").required(false))
|
||||
.arg(Arg::with_name("timestamp").short("t").required(false))
|
||||
.arg(Arg::with_name("start").short("s").required(false))
|
||||
.arg(Arg::with_name("ttl").short("l").required(false))
|
||||
.arg(Arg::with_name("issuer").short("i").required(true))
|
||||
.arg(Arg::with_name("issuersecret").short("k").required(true)))
|
||||
.subcommand(App::new("verify")
|
||||
.arg(Arg::with_name("cert").index(1).required(true)))
|
||||
.subcommand(App::new("dump")
|
||||
.arg(Arg::with_name("cert").index(1).required(true)))
|
||||
.subcommand(App::new("import")
|
||||
.arg(Arg::with_name("cert").index(1).required(true))
|
||||
.arg(Arg::with_name("trust").index(2).required(false)))
|
||||
.subcommand(App::new("factoryreset"))
|
||||
.subcommand(App::new("export")
|
||||
.arg(Arg::with_name("serial").index(1).required(true))
|
||||
.arg(Arg::with_name("path").index(2).required(false)))
|
||||
.subcommand(App::new("delete")
|
||||
.arg(Arg::with_name("serial").index(1).required(true))))
|
||||
.help(help.as_str())
|
||||
.get_matches_from_safe(std::env::args());
|
||||
if args.is_err() {
|
||||
let e = args.err().unwrap();
|
||||
if e.kind != ErrorKind::HelpDisplayed {
|
||||
print_help(false);
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
let args = args.unwrap();
|
||||
if args.subcommand_name().is_none() {
|
||||
print_help(false);
|
||||
std::process::exit(1);
|
||||
}
|
||||
args
|
||||
};
|
||||
|
||||
std::process::exit({
|
||||
match cli_args.subcommand() {
|
||||
("help", None) => {
|
||||
print_help(false);
|
||||
0
|
||||
}
|
||||
("longhelp", None) => {
|
||||
print_help(true);
|
||||
0
|
||||
}
|
||||
("oldhelp", None) => {
|
||||
// TODO
|
||||
0
|
||||
}
|
||||
("version", None) => {
|
||||
let ver = zerotier_core::version();
|
||||
println!("{}.{}.{}", ver.0, ver.1, ver.2);
|
||||
0
|
||||
}
|
||||
("status", None) => crate::httpclient::run_command(make_store(&cli_args), get_global_flags(&cli_args), crate::commands::status::run),
|
||||
("set", Some(sub_cli_args)) => { 0 }
|
||||
("peer", Some(sub_cli_args)) => { 0 }
|
||||
("network", Some(sub_cli_args)) => { 0 }
|
||||
("join", Some(sub_cli_args)) => { 0 }
|
||||
("leave", Some(sub_cli_args)) => { 0 }
|
||||
("service", None) => {
|
||||
let store = make_store(&cli_args);
|
||||
drop(cli_args); // free no longer needed memory before entering service
|
||||
service::run(store)
|
||||
}
|
||||
("controller", Some(sub_cli_args)) => { 0 }
|
||||
("identity", Some(sub_cli_args)) => crate::commands::identity::run(sub_cli_args),
|
||||
("locator", Some(sub_cli_args)) => crate::commands::locator::run(sub_cli_args),
|
||||
("cert", Some(sub_cli_args)) => crate::commands::cert::run(make_store(&cli_args), get_global_flags(&cli_args), sub_cli_args),
|
||||
_ => {
|
||||
print_help(false);
|
||||
1
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,17 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
pub struct Network {}
|
||||
|
||||
impl Network {
|
||||
}
|
|
@ -1,515 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::{SocketAddr, Ipv4Addr, IpAddr, Ipv6Addr};
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
use zerotier_core::*;
|
||||
use zerotier_core::trace::{TraceEvent, TraceEventLayer};
|
||||
use futures::StreamExt;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use crate::fastudpsocket::*;
|
||||
use crate::getifaddrs;
|
||||
use crate::localconfig::*;
|
||||
use crate::log::Log;
|
||||
use crate::network::Network;
|
||||
use crate::store::Store;
|
||||
use crate::utils::{ms_since_epoch, ms_monotonic};
|
||||
use crate::httplistener::HttpListener;
|
||||
|
||||
/// How often to check for major configuration changes. This shouldn't happen
|
||||
/// too often since it uses a bit of CPU.
|
||||
const CONFIG_CHECK_INTERVAL: i64 = 5000;
|
||||
|
||||
/// ServiceStatus is the object returned by the API /status endpoint
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct ServiceStatus {
|
||||
#[serde(rename = "objectType")]
|
||||
pub object_type: String,
|
||||
pub address: Address,
|
||||
pub clock: i64,
|
||||
#[serde(rename = "startTime")]
|
||||
pub start_time: i64,
|
||||
pub uptime: i64,
|
||||
pub config: LocalConfig,
|
||||
pub online: bool,
|
||||
#[serde(rename = "publicIdentity")]
|
||||
pub public_identity: Identity,
|
||||
pub version: String,
|
||||
#[serde(rename = "versionMajor")]
|
||||
pub version_major: i32,
|
||||
#[serde(rename = "versionMinor")]
|
||||
pub version_minor: i32,
|
||||
#[serde(rename = "versionRev")]
|
||||
pub version_revision: i32,
|
||||
#[serde(rename = "versionBuild")]
|
||||
pub version_build: i32,
|
||||
#[serde(rename = "udpLocalEndpoints")]
|
||||
pub udp_local_endpoints: Vec<InetAddress>,
|
||||
#[serde(rename = "httpLocalEndpoints")]
|
||||
pub http_local_endpoints: Vec<InetAddress>,
|
||||
}
|
||||
|
||||
/// Core ZeroTier service, which is sort of just a container for all the things.
|
||||
pub(crate) struct Service {
|
||||
pub(crate) log: Log,
|
||||
_node: Cell<Weak<Node<Arc<Service>, Network, Service>>>, // never modified after node is created
|
||||
udp_local_endpoints: Mutex<Vec<InetAddress>>,
|
||||
http_local_endpoints: Mutex<Vec<InetAddress>>,
|
||||
interrupt: Mutex<futures::channel::mpsc::Sender<()>>,
|
||||
local_config: Mutex<Arc<LocalConfig>>,
|
||||
store: Arc<Store>,
|
||||
startup_time: i64,
|
||||
startup_time_monotonic: i64,
|
||||
run: AtomicBool,
|
||||
online: AtomicBool,
|
||||
}
|
||||
|
||||
impl NodeEventHandler<Network> for Service {
|
||||
#[inline(always)]
|
||||
fn virtual_network_config(&self, network_id: NetworkId, network_obj: &Network, config_op: VirtualNetworkConfigOperation, config: Option<&VirtualNetworkConfig>) {}
|
||||
|
||||
#[inline(always)]
|
||||
fn virtual_network_frame(&self, network_id: NetworkId, network_obj: &Network, source_mac: MAC, dest_mac: MAC, ethertype: u16, vlan_id: u16, data: &[u8]) {}
|
||||
|
||||
#[inline(always)]
|
||||
fn event(&self, event: Event, event_data: &[u8]) {
|
||||
match event {
|
||||
Event::Up => {
|
||||
d!(self.log, "node startup event received.");
|
||||
}
|
||||
|
||||
Event::Down => {
|
||||
d!(self.log, "node shutdown event received.");
|
||||
self.online.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
Event::Online => {
|
||||
d!(self.log, "node is online.");
|
||||
self.online.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
Event::Offline => {
|
||||
d!(self.log, "node is offline.");
|
||||
self.online.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
Event::Trace => {
|
||||
if !event_data.is_empty() {
|
||||
let _ = Dictionary::new_from_bytes(event_data).map(|tm| {
|
||||
let tm = TraceEvent::parse_message(&tm);
|
||||
let _ = tm.map(|tm: TraceEvent| {
|
||||
let local_config = self.local_config();
|
||||
if match tm.layer() {
|
||||
TraceEventLayer::VL1 => local_config.settings.log.vl1,
|
||||
TraceEventLayer::VL2 => local_config.settings.log.vl2,
|
||||
TraceEventLayer::VL2Filter => local_config.settings.log.vl2_trace_rules,
|
||||
TraceEventLayer::VL2Multicast => local_config.settings.log.vl2_trace_multicast,
|
||||
_ => true,
|
||||
} {
|
||||
self.log.log(tm.to_string());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Event::UserMessage => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn state_put(&self, obj_type: StateObjectType, obj_id: &[u64], obj_data: &[u8]) -> std::io::Result<()> {
|
||||
if !obj_data.is_empty() {
|
||||
self.store.store_object(&obj_type, obj_id, obj_data)
|
||||
} else {
|
||||
self.store.erase_object(&obj_type, obj_id);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn state_get(&self, obj_type: StateObjectType, obj_id: &[u64]) -> std::io::Result<Vec<u8>> {
|
||||
self.store.load_object(&obj_type, obj_id)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn wire_packet_send(&self, local_socket: i64, sock_addr: &InetAddress, data: &[u8], packet_ttl: u32) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn path_check(&self, _: Address, _: &Identity, _: i64, _: &InetAddress) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn path_lookup(&self, address: Address, id: &Identity, desired_family: InetAddressFamily) -> Option<InetAddress> {
|
||||
let lc = self.local_config();
|
||||
lc.virtual_.get(&address).map_or(None, |c: &LocalConfigVirtualConfig| {
|
||||
if c.try_.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let t = c.try_.get((zerotier_core::random() as usize) % c.try_.len());
|
||||
t.map_or(None, |v: &InetAddress| {
|
||||
d!(self.log, "path lookup for {} returned {}", address.to_string(), v.to_string());
|
||||
Some(v.clone())
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub fn local_config(&self) -> Arc<LocalConfig> {
|
||||
self.local_config.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn set_local_config(&self, new_lc: LocalConfig) {
|
||||
*(self.local_config.lock().unwrap()) = Arc::new(new_lc);
|
||||
}
|
||||
|
||||
/// Get the node running with this service.
|
||||
/// This can return None during shutdown because Service holds a weak
|
||||
/// reference to Node to avoid circular Arc<> pointers. This will only
|
||||
/// return None during shutdown, in which case whatever is happening
|
||||
/// should abort as quietly as possible.
|
||||
pub fn node(&self) -> Option<Arc<Node<Arc<Service>, Network, Service>>> {
|
||||
unsafe { &*self._node.as_ptr() }.upgrade()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn store(&self) -> &Arc<Store> {
|
||||
&self.store
|
||||
}
|
||||
|
||||
pub fn online(&self) -> bool {
|
||||
self.online.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) {
|
||||
self.run.store(false, Ordering::Relaxed);
|
||||
let _ = self.interrupt.lock().unwrap().try_send(());
|
||||
}
|
||||
|
||||
/// Get service status for API, or None if a shutdown is in progress.
|
||||
pub fn status(&self) -> Option<ServiceStatus> {
|
||||
let ver = zerotier_core::version();
|
||||
self.node().map(|node| {
|
||||
ServiceStatus {
|
||||
object_type: "status".to_owned(),
|
||||
address: node.address(),
|
||||
clock: ms_since_epoch(),
|
||||
start_time: self.startup_time,
|
||||
uptime: ms_monotonic() - self.startup_time_monotonic,
|
||||
config: (*self.local_config()).clone(),
|
||||
online: self.online(),
|
||||
public_identity: node.identity().clone(),
|
||||
version: format!("{}.{}.{}", ver.0, ver.1, ver.2),
|
||||
version_major: ver.0,
|
||||
version_minor: ver.1,
|
||||
version_revision: ver.2,
|
||||
version_build: ver.3,
|
||||
udp_local_endpoints: self.udp_local_endpoints.lock().unwrap().clone(),
|
||||
http_local_endpoints: self.http_local_endpoints.lock().unwrap().clone(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Service {}
|
||||
|
||||
unsafe impl Sync for Service {}
|
||||
|
||||
async fn run_async(store: Arc<Store>, local_config: Arc<LocalConfig>) -> i32 {
|
||||
let process_exit_value: i32 = 0;
|
||||
|
||||
let mut udp_sockets: BTreeMap<InetAddress, FastUDPSocket> = BTreeMap::new();
|
||||
let mut http_listeners: BTreeMap<InetAddress, HttpListener> = BTreeMap::new();
|
||||
let mut loopback_http_listeners: (Option<HttpListener>, Option<HttpListener>) = (None, None); // 127.0.0.1, ::1
|
||||
|
||||
let (interrupt_tx, mut interrupt_rx) = futures::channel::mpsc::channel::<()>(1);
|
||||
let service = Arc::new(Service {
|
||||
log: Log::new(
|
||||
if local_config.settings.log.path.as_ref().is_some() {
|
||||
local_config.settings.log.path.as_ref().unwrap().as_str()
|
||||
} else {
|
||||
store.default_log_path.to_str().unwrap()
|
||||
},
|
||||
local_config.settings.log.max_size,
|
||||
local_config.settings.log.stderr,
|
||||
local_config.settings.log.debug,
|
||||
"",
|
||||
),
|
||||
_node: Cell::new(Weak::new()),
|
||||
udp_local_endpoints: Mutex::new(Vec::new()),
|
||||
http_local_endpoints: Mutex::new(Vec::new()),
|
||||
interrupt: Mutex::new(interrupt_tx),
|
||||
local_config: Mutex::new(local_config),
|
||||
store: store.clone(),
|
||||
startup_time: ms_since_epoch(),
|
||||
startup_time_monotonic: ms_monotonic(),
|
||||
run: AtomicBool::new(true),
|
||||
online: AtomicBool::new(false),
|
||||
});
|
||||
|
||||
let node = Node::new(service.clone(), ms_since_epoch(), ms_monotonic());
|
||||
if node.is_err() {
|
||||
service.log.fatal(format!("error initializing node: {}", node.err().unwrap().to_str()));
|
||||
return 1;
|
||||
}
|
||||
let node = Arc::new(node.ok().unwrap());
|
||||
service._node.replace(Arc::downgrade(&node));
|
||||
|
||||
let mut local_config = service.local_config();
|
||||
|
||||
let mut ticks: i64 = ms_monotonic();
|
||||
let mut loop_delay = zerotier_core::NODE_BACKGROUND_TASKS_MAX_INTERVAL;
|
||||
let mut last_checked_config: i64 = 0;
|
||||
while service.run.load(Ordering::Relaxed) {
|
||||
let loop_delay_start = ms_monotonic();
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep(Duration::from_millis(loop_delay as u64)) => {
|
||||
ticks = ms_monotonic();
|
||||
let actual_delay = ticks - loop_delay_start;
|
||||
if actual_delay > ((loop_delay as i64) * 4_i64) {
|
||||
l!(service.log, "likely sleep/wake detected due to excessive loop delay, cycling links...");
|
||||
// TODO: handle likely sleep/wake or other system interruption
|
||||
}
|
||||
},
|
||||
_ = interrupt_rx.next() => {
|
||||
d!(service.log, "inner loop delay interrupted!");
|
||||
if !service.run.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
ticks = ms_monotonic();
|
||||
},
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
l!(service.log, "exit signal received, shutting down...");
|
||||
service.run.store(false, Ordering::Relaxed);
|
||||
break;
|
||||
},
|
||||
}
|
||||
|
||||
if (ticks - last_checked_config) >= CONFIG_CHECK_INTERVAL {
|
||||
last_checked_config = ticks;
|
||||
|
||||
let mut bindings_changed = false;
|
||||
|
||||
let _ = store.read_local_conf(true).map(|new_config| new_config.map(|new_config| {
|
||||
d!(service.log, "local.conf changed on disk, reloading.");
|
||||
service.set_local_config(new_config);
|
||||
}));
|
||||
|
||||
let next_local_config = service.local_config();
|
||||
if local_config.settings.primary_port != next_local_config.settings.primary_port {
|
||||
loopback_http_listeners.0 = None;
|
||||
loopback_http_listeners.1 = None;
|
||||
bindings_changed = true;
|
||||
}
|
||||
if local_config.settings.log.max_size != next_local_config.settings.log.max_size {
|
||||
service.log.set_max_size(next_local_config.settings.log.max_size);
|
||||
}
|
||||
if local_config.settings.log.stderr != next_local_config.settings.log.stderr {
|
||||
service.log.set_log_to_stderr(next_local_config.settings.log.stderr);
|
||||
}
|
||||
if local_config.settings.log.debug != next_local_config.settings.log.debug {
|
||||
service.log.set_debug(next_local_config.settings.log.debug);
|
||||
}
|
||||
local_config = next_local_config;
|
||||
|
||||
let mut loopback_dev_name = String::new();
|
||||
let mut system_addrs: BTreeMap<InetAddress, String> = BTreeMap::new();
|
||||
getifaddrs::for_each_address(|addr: &InetAddress, dev: &str| {
|
||||
match addr.ip_scope() {
|
||||
IpScope::Global | IpScope::Private | IpScope::PseudoPrivate | IpScope::Shared => {
|
||||
if !local_config.settings.is_interface_blacklisted(dev) {
|
||||
let mut a = addr.clone();
|
||||
a.set_port(local_config.settings.primary_port);
|
||||
system_addrs.insert(a, String::from(dev));
|
||||
if local_config.settings.secondary_port.is_some() {
|
||||
let mut a = addr.clone();
|
||||
a.set_port(local_config.settings.secondary_port.unwrap());
|
||||
system_addrs.insert(a, String::from(dev));
|
||||
}
|
||||
}
|
||||
},
|
||||
IpScope::Loopback => {
|
||||
if loopback_dev_name.is_empty() {
|
||||
loopback_dev_name.push_str(dev);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: need to also inform the core about these IPs...
|
||||
|
||||
for k in udp_sockets.keys().filter_map(|a| if system_addrs.contains_key(a) { None } else { Some(a.clone()) }).collect::<Vec<InetAddress>>().iter() {
|
||||
l!(service.log, "unbinding UDP socket at {} (address no longer exists on system or port has changed)", k.to_string());
|
||||
udp_sockets.remove(k);
|
||||
bindings_changed = true;
|
||||
}
|
||||
for a in system_addrs.iter() {
|
||||
if !udp_sockets.contains_key(a.0) {
|
||||
let _ = FastUDPSocket::new(a.1.as_str(), a.0, |raw_socket: &FastUDPRawOsSocket, from_address: &InetAddress, data: Buffer| {
|
||||
// TODO: incoming packet handler
|
||||
}).map_or_else(|e| {
|
||||
l!(service.log, "error binding UDP socket to {}: {}", a.0.to_string(), e.to_string());
|
||||
}, |s| {
|
||||
l!(service.log, "bound UDP socket at {}", a.0.to_string());
|
||||
udp_sockets.insert(a.0.clone(), s);
|
||||
bindings_changed = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let mut udp_primary_port_bind_failure = true;
|
||||
let mut udp_secondary_port_bind_failure = local_config.settings.secondary_port.is_some();
|
||||
for s in udp_sockets.iter() {
|
||||
if s.0.port() == local_config.settings.primary_port {
|
||||
udp_primary_port_bind_failure = false;
|
||||
if !udp_secondary_port_bind_failure {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if s.0.port() == local_config.settings.secondary_port.unwrap() {
|
||||
udp_secondary_port_bind_failure = false;
|
||||
if !udp_primary_port_bind_failure {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if udp_primary_port_bind_failure {
|
||||
if local_config.settings.auto_port_search {
|
||||
// TODO: port hunting
|
||||
} else {
|
||||
l!(service.log, "WARNING: failed to bind to any address at primary port {}", local_config.settings.primary_port);
|
||||
}
|
||||
}
|
||||
if udp_secondary_port_bind_failure {
|
||||
if local_config.settings.auto_port_search {
|
||||
// TODO: port hunting
|
||||
} else {
|
||||
l!(service.log, "WARNING: failed to bind to any address at secondary port {}", local_config.settings.secondary_port.unwrap_or(0));
|
||||
}
|
||||
}
|
||||
|
||||
for k in http_listeners.keys().filter_map(|a| if system_addrs.contains_key(a) { None } else { Some(a.clone()) }).collect::<Vec<InetAddress>>().iter() {
|
||||
l!(service.log, "closing HTTP listener at {} (address no longer exists on system or port has changed)", k.to_string());
|
||||
http_listeners.remove(k);
|
||||
bindings_changed = true;
|
||||
}
|
||||
for a in system_addrs.iter() {
|
||||
if !http_listeners.contains_key(a.0) {
|
||||
let sa = a.0.to_socketaddr();
|
||||
if sa.is_some() {
|
||||
let wl = HttpListener::new(a.1.as_str(), sa.unwrap(), &service).await.map_or_else(|e| {
|
||||
l!(service.log, "error creating HTTP listener at {}: {}", a.0.to_string(), e.to_string());
|
||||
}, |l| {
|
||||
l!(service.log, "created HTTP listener at {}", a.0.to_string());
|
||||
http_listeners.insert(a.0.clone(), l);
|
||||
bindings_changed = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if loopback_http_listeners.0.is_none() {
|
||||
let _ = HttpListener::new(loopback_dev_name.as_str(), SocketAddr::new(IpAddr::from(Ipv4Addr::LOCALHOST), local_config.settings.primary_port), &service).await.map(|wl| {
|
||||
loopback_http_listeners.0 = Some(wl);
|
||||
let _ = store.write_uri(format!("http://127.0.0.1:{}/", local_config.settings.primary_port).as_str());
|
||||
bindings_changed = true;
|
||||
});
|
||||
}
|
||||
if loopback_http_listeners.1.is_none() {
|
||||
let _ = HttpListener::new(loopback_dev_name.as_str(), SocketAddr::new(IpAddr::from(Ipv6Addr::LOCALHOST), local_config.settings.primary_port), &service).await.map(|wl| {
|
||||
loopback_http_listeners.1 = Some(wl);
|
||||
if loopback_http_listeners.0.is_none() {
|
||||
let _ = store.write_uri(format!("http://[::1]:{}/", local_config.settings.primary_port).as_str());
|
||||
}
|
||||
bindings_changed = true;
|
||||
});
|
||||
}
|
||||
if loopback_http_listeners.0.is_none() && loopback_http_listeners.1.is_none() {
|
||||
// TODO: port hunting
|
||||
l!(service.log, "CRITICAL: unable to create HTTP endpoint on 127.0.0.1/{} or ::1/{}, service control API will not work!", local_config.settings.primary_port, local_config.settings.primary_port);
|
||||
}
|
||||
|
||||
if bindings_changed {
|
||||
{
|
||||
let mut udp_local_endpoints = service.udp_local_endpoints.lock().unwrap();
|
||||
udp_local_endpoints.clear();
|
||||
for ep in udp_sockets.iter() {
|
||||
udp_local_endpoints.push(ep.0.clone());
|
||||
}
|
||||
udp_local_endpoints.sort();
|
||||
}
|
||||
{
|
||||
let mut http_local_endpoints = service.http_local_endpoints.lock().unwrap();
|
||||
http_local_endpoints.clear();
|
||||
for ep in http_listeners.iter() {
|
||||
http_local_endpoints.push(ep.0.clone());
|
||||
}
|
||||
if loopback_http_listeners.0.is_some() {
|
||||
http_local_endpoints.push(InetAddress::new_ipv4_loopback(loopback_http_listeners.0.as_ref().unwrap().address.port()));
|
||||
}
|
||||
if loopback_http_listeners.1.is_some() {
|
||||
http_local_endpoints.push(InetAddress::new_ipv6_loopback(loopback_http_listeners.1.as_ref().unwrap().address.port()));
|
||||
}
|
||||
http_local_endpoints.sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run background task handler in ZeroTier core.
|
||||
loop_delay = node.process_background_tasks(ms_since_epoch(), ticks);
|
||||
}
|
||||
|
||||
l!(service.log, "shutting down normally.");
|
||||
|
||||
drop(udp_sockets);
|
||||
drop(http_listeners);
|
||||
drop(loopback_http_listeners);
|
||||
drop(node);
|
||||
drop(service);
|
||||
|
||||
process_exit_value
|
||||
}
|
||||
|
||||
pub(crate) fn run(store: Arc<Store>) -> i32 {
|
||||
let local_config = Arc::new(store.read_local_conf_or_default());
|
||||
|
||||
if store.auth_token(true).is_err() {
|
||||
eprintln!("FATAL: error writing new web API authorization token (likely permission problem).");
|
||||
return 1;
|
||||
}
|
||||
if store.write_pid().is_err() {
|
||||
eprintln!("FATAL: error writing to directory '{}': unable to write zerotier.pid (likely permission problem).", store.base_path.to_str().unwrap());
|
||||
return 1;
|
||||
}
|
||||
|
||||
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
|
||||
let store2 = store.clone();
|
||||
let process_exit_value = rt.block_on(async move { run_async(store2, local_config).await });
|
||||
rt.shutdown_timeout(Duration::from_millis(500));
|
||||
|
||||
store.erase_pid();
|
||||
|
||||
process_exit_value
|
||||
}
|
|
@ -1,300 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
use std::str::FromStr;
|
||||
use std::ffi::CString;
|
||||
|
||||
use zerotier_core::{StateObjectType, NetworkId};
|
||||
|
||||
use crate::localconfig::LocalConfig;
|
||||
|
||||
const ZEROTIER_PID: &'static str = "zerotier.pid";
|
||||
const ZEROTIER_URI: &'static str = "zerotier.uri";
|
||||
const LOCAL_CONF: &'static str = "local.conf";
|
||||
const AUTHTOKEN_SECRET: &'static str = "authtoken.secret";
|
||||
const SERVICE_LOG: &'static str = "service.log";
|
||||
|
||||
/// In-filesystem data store for configuration and objects.
|
||||
pub(crate) struct Store {
|
||||
pub base_path: Box<Path>,
|
||||
pub default_log_path: Box<Path>,
|
||||
prev_local_config: Mutex<String>,
|
||||
peers_path: Box<Path>,
|
||||
controller_path: Box<Path>,
|
||||
networks_path: Box<Path>,
|
||||
auth_token_path: Mutex<Box<Path>>,
|
||||
auth_token: Mutex<String>,
|
||||
}
|
||||
|
||||
/// Restrict file permissions using OS-specific code in osdep/OSUtils.cpp.
|
||||
pub fn lock_down_file(path: &str) {
|
||||
let p = CString::new(path.as_bytes());
|
||||
if p.is_ok() {
|
||||
let p = p.unwrap();
|
||||
unsafe {
|
||||
crate::osdep::lockDownFile(p.as_ptr(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Store {
|
||||
const MAX_OBJECT_SIZE: usize = 262144; // sanity limit
|
||||
|
||||
pub fn new(base_path: &str, auth_token_path_override: Option<String>, auth_token_override: Option<String>) -> std::io::Result<Store> {
|
||||
let bp = Path::new(base_path);
|
||||
let _ = std::fs::create_dir_all(bp);
|
||||
let md = bp.metadata()?;
|
||||
if !md.is_dir() || md.permissions().readonly() {
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "base path does not exist or is not writable"));
|
||||
}
|
||||
|
||||
let s = Store {
|
||||
base_path: bp.to_path_buf().into_boxed_path(),
|
||||
default_log_path: bp.join(SERVICE_LOG).into_boxed_path(),
|
||||
prev_local_config: Mutex::new(String::new()),
|
||||
peers_path: bp.join("peers.d").into_boxed_path(),
|
||||
controller_path: bp.join("controller.d").into_boxed_path(),
|
||||
networks_path: bp.join("networks.d").into_boxed_path(),
|
||||
auth_token_path: Mutex::new(auth_token_path_override.map_or_else(|| {
|
||||
bp.join(AUTHTOKEN_SECRET).into_boxed_path()
|
||||
}, |auth_token_path_override| {
|
||||
PathBuf::from(auth_token_path_override).into_boxed_path()
|
||||
})),
|
||||
auth_token: Mutex::new(auth_token_override.map_or_else(|| {
|
||||
String::new()
|
||||
}, |auth_token_override| {
|
||||
auth_token_override
|
||||
})),
|
||||
};
|
||||
|
||||
let _ = std::fs::create_dir_all(&s.peers_path);
|
||||
let _ = std::fs::create_dir_all(&s.controller_path);
|
||||
let _ = std::fs::create_dir_all(&s.networks_path);
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn make_obj_path_internal(&self, obj_type: &StateObjectType, obj_id: &[u64]) -> Option<PathBuf> {
|
||||
match obj_type {
|
||||
StateObjectType::IdentityPublic => Some(self.base_path.join("identity.public")),
|
||||
StateObjectType::IdentitySecret => Some(self.base_path.join("identity.secret")),
|
||||
StateObjectType::TrustStore => Some(self.base_path.join("truststore")),
|
||||
StateObjectType::Locator => Some(self.base_path.join("locator")),
|
||||
StateObjectType::NetworkConfig => {
|
||||
if obj_id.len() < 1 {
|
||||
None
|
||||
} else {
|
||||
Some(self.networks_path.join(format!("{:0>16x}.conf", obj_id[0])))
|
||||
}
|
||||
},
|
||||
StateObjectType::Peer => {
|
||||
if obj_id.len() < 1 {
|
||||
None
|
||||
} else {
|
||||
Some(self.peers_path.join(format!("{:0>10x}.peer", obj_id[0])))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_internal(&self, path: PathBuf) -> std::io::Result<Vec<u8>> {
|
||||
let fmd = path.metadata()?;
|
||||
if fmd.is_file() {
|
||||
let flen = fmd.len();
|
||||
if flen <= Store::MAX_OBJECT_SIZE as u64 {
|
||||
let mut f = std::fs::File::open(path)?;
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
buf.reserve(flen as usize);
|
||||
let rs = f.read_to_end(&mut buf)?;
|
||||
buf.resize(rs as usize, 0);
|
||||
return Ok(buf);
|
||||
}
|
||||
}
|
||||
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "does not exist or is not readable"))
|
||||
}
|
||||
|
||||
pub fn auth_token(&self, generate_if_missing: bool) -> std::io::Result<String> {
|
||||
let mut token = self.auth_token.lock().unwrap();
|
||||
if token.is_empty() {
|
||||
let p = self.auth_token_path.lock().unwrap();
|
||||
let ps = p.to_str().unwrap();
|
||||
|
||||
let token2 = self.read_file(ps).map_or(String::new(), |sb| { String::from_utf8(sb).unwrap_or(String::new()).trim().to_string() });
|
||||
if token2.is_empty() {
|
||||
if generate_if_missing {
|
||||
let mut rb = [0_u8; 32];
|
||||
unsafe { crate::osdep::getSecureRandom(rb.as_mut_ptr().cast(), 64) };
|
||||
token.reserve(rb.len());
|
||||
for b in rb.iter() {
|
||||
if *b > 127_u8 {
|
||||
token.push((65 + (*b % 26)) as char); // A..Z
|
||||
} else {
|
||||
token.push((97 + (*b % 26)) as char); // a..z
|
||||
}
|
||||
}
|
||||
let res = self.write_file(ps, token.as_bytes());
|
||||
if res.is_err() {
|
||||
token.clear();
|
||||
Err(res.err().unwrap())
|
||||
} else {
|
||||
lock_down_file(ps);
|
||||
Ok(token.clone())
|
||||
}
|
||||
} else {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::NotFound, ""))
|
||||
}
|
||||
} else {
|
||||
*token = token2;
|
||||
Ok(token.clone())
|
||||
}
|
||||
} else {
|
||||
Ok(token.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_joined_networks(&self) -> Vec<NetworkId> {
|
||||
let mut list: Vec<NetworkId> = Vec::new();
|
||||
let d = std::fs::read_dir(self.networks_path.as_ref());
|
||||
if d.is_ok() {
|
||||
for de in d.unwrap() {
|
||||
if de.is_ok() {
|
||||
let nn = de.unwrap().file_name();
|
||||
let n = nn.to_str().unwrap_or("");
|
||||
if n.len() == 21 && n.ends_with(".conf") { // ################.conf
|
||||
let nwid = u64::from_str_radix(&n[0..16], 16);
|
||||
if nwid.is_ok() {
|
||||
list.push(NetworkId(nwid.unwrap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
list
|
||||
}
|
||||
|
||||
pub fn read_file(&self, fname: &str) -> std::io::Result<Vec<u8>> {
|
||||
self.read_internal(self.base_path.join(fname))
|
||||
}
|
||||
|
||||
pub fn read_file_str(&self, fname: &str) -> std::io::Result<String> {
|
||||
let data = self.read_file(fname)?;
|
||||
let data = String::from_utf8(data);
|
||||
if data.is_err() {
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, data.err().unwrap().to_string()));
|
||||
}
|
||||
Ok(data.unwrap())
|
||||
}
|
||||
|
||||
pub fn write_file(&self, fname: &str, data: &[u8]) -> std::io::Result<()> {
|
||||
std::fs::OpenOptions::new().write(true).truncate(true).create(true).open(self.base_path.join(fname))?.write_all(data)
|
||||
}
|
||||
|
||||
pub fn read_local_conf(&self, skip_if_unchanged: bool) -> Option<std::io::Result<LocalConfig>> {
|
||||
let data = self.read_file_str(LOCAL_CONF);
|
||||
if data.is_err() {
|
||||
return Some(Err(data.err().unwrap()));
|
||||
}
|
||||
let data = data.unwrap();
|
||||
if skip_if_unchanged {
|
||||
let mut prev = self.prev_local_config.lock().unwrap();
|
||||
if prev.eq(&data) {
|
||||
return None;
|
||||
}
|
||||
*prev = data.clone();
|
||||
} else {
|
||||
*(self.prev_local_config.lock().unwrap()) = data.clone();
|
||||
}
|
||||
let lc = serde_json::from_str::<LocalConfig>(data.as_str());
|
||||
if lc.is_err() {
|
||||
return Some(Err(std::io::Error::new(std::io::ErrorKind::InvalidData, lc.err().unwrap())));
|
||||
}
|
||||
Some(Ok(lc.unwrap()))
|
||||
}
|
||||
|
||||
pub fn read_local_conf_or_default(&self) -> LocalConfig {
|
||||
let lc = self.read_local_conf(false);
|
||||
if lc.is_some() {
|
||||
let lc = lc.unwrap();
|
||||
if lc.is_ok() {
|
||||
return lc.unwrap();
|
||||
}
|
||||
}
|
||||
LocalConfig::default()
|
||||
}
|
||||
|
||||
pub fn write_local_conf(&self, lc: &LocalConfig) -> std::io::Result<()> {
|
||||
let json = serde_json::to_string(lc).unwrap();
|
||||
self.write_file(LOCAL_CONF, json.as_bytes())
|
||||
}
|
||||
|
||||
pub fn write_pid(&self) -> std::io::Result<()> {
|
||||
let pid = unsafe { crate::osdep::getpid() }.to_string();
|
||||
self.write_file(ZEROTIER_PID, pid.as_bytes())
|
||||
}
|
||||
|
||||
pub fn erase_pid(&self) {
|
||||
let _ = std::fs::remove_file(self.base_path.join(ZEROTIER_PID));
|
||||
}
|
||||
|
||||
pub fn write_uri(&self, uri: &str) -> std::io::Result<()> {
|
||||
self.write_file(ZEROTIER_URI, uri.as_bytes())
|
||||
}
|
||||
|
||||
pub fn load_uri(&self) -> std::io::Result<hyper::Uri> {
|
||||
let uri = String::from_utf8(self.read_file(ZEROTIER_URI)?);
|
||||
uri.map_or_else(|e| {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
|
||||
}, |uri| {
|
||||
let uri = hyper::Uri::from_str(uri.trim());
|
||||
uri.map_or_else(|e| {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
|
||||
}, |uri| {
|
||||
Ok(uri)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_object(&self, obj_type: &StateObjectType, obj_id: &[u64]) -> std::io::Result<Vec<u8>> {
|
||||
let obj_path = self.make_obj_path_internal(&obj_type, obj_id);
|
||||
if obj_path.is_some() {
|
||||
return self.read_internal(obj_path.unwrap());
|
||||
}
|
||||
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "does not exist or is not readable"))
|
||||
}
|
||||
|
||||
pub fn erase_object(&self, obj_type: &StateObjectType, obj_id: &[u64]) {
|
||||
let obj_path = self.make_obj_path_internal(obj_type, obj_id);
|
||||
if obj_path.is_some() {
|
||||
let _ = std::fs::remove_file(obj_path.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store_object(&self, obj_type: &StateObjectType, obj_id: &[u64], obj_data: &[u8]) -> std::io::Result<()> {
|
||||
let obj_path = self.make_obj_path_internal(obj_type, obj_id);
|
||||
if obj_path.is_some() {
|
||||
let obj_path = obj_path.unwrap();
|
||||
std::fs::OpenOptions::new().write(true).truncate(true).create(true).open(&obj_path)?.write_all(obj_data)?;
|
||||
|
||||
if obj_type.is_secret() {
|
||||
lock_down_file(obj_path.to_str().unwrap());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "object type or ID not valid"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,182 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
||||
use zerotier_core::{Identity, Locator};
|
||||
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::osdep;
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn ms_since_epoch() -> i64 {
|
||||
unsafe { osdep::msSinceEpoch() }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn ms_monotonic() -> i64 {
|
||||
unsafe { osdep::msMonotonic() }
|
||||
}
|
||||
|
||||
/// Convenience function to read up to limit bytes from a file.
|
||||
/// If the file is larger than limit, the excess is not read.
|
||||
pub(crate) fn read_limit<P: AsRef<Path>>(path: P, limit: usize) -> std::io::Result<Vec<u8>> {
|
||||
let mut v: Vec<u8> = Vec::new();
|
||||
let _ = File::open(path)?.take(limit as u64).read_to_end(&mut v)?;
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
/// Read an identity as either a literal or from a file.
|
||||
pub(crate) fn read_identity(input: &str, validate: bool) -> Result<Identity, String> {
|
||||
let parse_func = |s: &str| {
|
||||
Identity::new_from_string(s).map_or_else(|e| {
|
||||
Err(format!("invalid identity: {}", e.to_str()))
|
||||
}, |id| {
|
||||
if !validate || id.validate() {
|
||||
Ok(id)
|
||||
} else {
|
||||
Err(String::from("invalid identity: local validation failed"))
|
||||
}
|
||||
})
|
||||
};
|
||||
if Path::new(input).exists() {
|
||||
read_limit(input, 16384).map_or_else(|e| {
|
||||
Err(e.to_string())
|
||||
}, |v| {
|
||||
String::from_utf8(v).map_or_else(|e| {
|
||||
Err(e.to_string())
|
||||
}, |s| {
|
||||
parse_func(s.as_str())
|
||||
})
|
||||
})
|
||||
} else {
|
||||
parse_func(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a locator as either a literal or from a file.
|
||||
pub(crate) fn read_locator(input: &str) -> Result<Locator, String> {
|
||||
let parse_func = |s: &str| {
|
||||
Locator::new_from_string(s).map_or_else(|e| {
|
||||
Err(format!("invalid locator: {}", e.to_str()))
|
||||
}, |loc| {
|
||||
Ok(loc)
|
||||
})
|
||||
};
|
||||
if Path::new(input).exists() {
|
||||
read_limit(input, 16384).map_or_else(|e| {
|
||||
Err(e.to_string())
|
||||
}, |v| {
|
||||
String::from_utf8(v).map_or_else(|e| {
|
||||
Err(e.to_string())
|
||||
}, |s| {
|
||||
parse_func(s.as_str())
|
||||
})
|
||||
})
|
||||
} else {
|
||||
parse_func(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new HTTP authorization nonce by encrypting the current time.
|
||||
/// The key used to encrypt the current time is random and is re-created for
|
||||
/// each execution of the process. By decrypting this nonce when it is returned,
|
||||
/// the client and server may check the age of a digest auth exchange.
|
||||
pub(crate) fn create_http_auth_nonce(timestamp: i64) -> String {
|
||||
let mut nonce_plaintext: [u64; 2] = [timestamp as u64, timestamp as u64];
|
||||
unsafe {
|
||||
osdep::encryptHttpAuthNonce(nonce_plaintext.as_mut_ptr().cast());
|
||||
hex::encode(*nonce_plaintext.as_ptr().cast::<[u8; 16]>())
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrypt HTTP auth nonce encrypted by this process and return the timestamp.
|
||||
/// This returns zero if the input was not valid.
|
||||
pub(crate) fn decrypt_http_auth_nonce(nonce: &str) -> i64 {
|
||||
let nonce = hex::decode(nonce.trim());
|
||||
if !nonce.is_err() {
|
||||
let mut nonce = nonce.unwrap();
|
||||
if nonce.len() == 16 {
|
||||
unsafe {
|
||||
osdep::decryptHttpAuthNonce(nonce.as_mut_ptr().cast());
|
||||
let nonce = *nonce.as_ptr().cast::<[u64; 2]>();
|
||||
if nonce[0] == nonce[1] {
|
||||
return nonce[0] as i64;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Shortcut to use serde_json to serialize an object, returns "null" on error.
|
||||
pub(crate) fn to_json<O: serde::Serialize>(o: &O) -> String {
|
||||
serde_json::to_string(o).unwrap_or("null".into())
|
||||
}
|
||||
|
||||
/// Shortcut to use serde_json to serialize an object, returns "null" on error.
|
||||
pub(crate) fn to_json_pretty<O: serde::Serialize>(o: &O) -> String {
|
||||
serde_json::to_string_pretty(o).unwrap_or("null".into())
|
||||
}
|
||||
|
||||
/// Recursively patch a JSON object.
|
||||
/// This is slightly different from a usual JSON merge. For objects in the target their fields
|
||||
/// are updated by recursively calling json_patch if the same field is present in the source.
|
||||
/// If the source tries to set an object to something other than another object, this is ignored.
|
||||
/// Other fields are replaced. This is used for RESTful config object updates. The depth limit
|
||||
/// field is to prevent stack overflows via the API.
|
||||
pub(crate) fn json_patch(target: &mut serde_json::value::Value, source: &serde_json::value::Value, depth_limit: usize) {
|
||||
if target.is_object() {
|
||||
if source.is_object() {
|
||||
let mut target = target.as_object_mut().unwrap();
|
||||
let source = source.as_object().unwrap();
|
||||
for kv in target.iter_mut() {
|
||||
let _ = source.get(kv.0).map(|new_value| {
|
||||
if depth_limit > 0 {
|
||||
json_patch(kv.1, new_value, depth_limit - 1)
|
||||
}
|
||||
});
|
||||
}
|
||||
for kv in source.iter() {
|
||||
if !target.contains_key(kv.0) && !kv.1.is_null() {
|
||||
target.insert(kv.0.clone(), kv.1.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if *target != *source {
|
||||
*target = source.clone();
|
||||
}
|
||||
}
|
||||
|
||||
/// Patch a serializable object with the fields present in a JSON object.
|
||||
/// If there are no changes, None is returned. The depth limit is passed through to json_patch and
|
||||
/// should be set to a sanity check value to prevent overflows.
|
||||
pub(crate) fn json_patch_object<O: Serialize + DeserializeOwned + Eq>(obj: O, patch: &str, depth_limit: usize) -> Result<Option<O>, serde_json::Error> {
|
||||
serde_json::from_str::<serde_json::value::Value>(patch).map_or_else(|e| Err(e), |patch| {
|
||||
serde_json::value::to_value(obj.borrow()).map_or_else(|e| Err(e), |mut obj_value| {
|
||||
json_patch(&mut obj_value, &patch, depth_limit);
|
||||
serde_json::value::from_value::<O>(obj_value).map_or_else(|e| Err(e), |obj_merged| {
|
||||
if obj == obj_merged {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(obj_merged))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,56 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use zerotier_core::{MAC, MulticastGroup};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use num_traits::AsPrimitive;
|
||||
|
||||
/// BSD based OSes support getifmaddrs().
|
||||
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly", target_os = "freebsd", target_os = "darwin"))]
|
||||
pub(crate) fn get_l2_multicast_subscriptions(dev: &str) -> HashSet<MulticastGroup> {
|
||||
let mut groups: HashSet<MulticastGroup> = HashSet::new();
|
||||
let dev = dev.as_bytes();
|
||||
unsafe {
|
||||
let mut maddrs: *mut osdep::ifmaddrs = std::ptr::null_mut();
|
||||
if osdep::getifmaddrs(&mut maddrs as *mut *mut osdep::ifmaddrs) == 0 {
|
||||
let mut i = maddrs;
|
||||
while !i.is_null() {
|
||||
if !(*i).ifma_name.is_null() && !(*i).ifma_addr.is_null() && (*(*i).ifma_addr).sa_family as i32 == osdep::AF_LINK as i32 {
|
||||
let in_: &osdep::sockaddr_dl = &*((*i).ifma_name.cast());
|
||||
let la: &osdep::sockaddr_dl = &*((*i).ifma_addr.cast());
|
||||
if la.sdl_alen == 6 && in_.sdl_nlen <= dev.len().as_() && crate::osdep::memcmp(dev.as_ptr().cast(), in_.sdl_data.as_ptr().cast(), in_.sdl_nlen.as_()) == 0 {
|
||||
let mi = la.sdl_nlen as usize;
|
||||
groups.insert(MulticastGroup{
|
||||
mac: MAC((la.sdl_data[mi] as u64) << 40 | (la.sdl_data[mi+1] as u64) << 32 | (la.sdl_data[mi+2] as u64) << 24 | (la.sdl_data[mi+3] as u64) << 16 | (la.sdl_data[mi+4] as u64) << 8 | la.sdl_data[mi+5] as u64),
|
||||
adi: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
i = (*i).ifma_next;
|
||||
}
|
||||
osdep::freeifmaddrs(maddrs);
|
||||
}
|
||||
}
|
||||
groups
|
||||
}
|
||||
|
||||
/// Linux stores this stuff in /proc and it needs to be fetched from there.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) fn get_l2_multicast_subscriptions(dev: &str) -> HashSet<MulticastGroup> {
|
||||
let mut groups: HashSet<MulticastGroup> = HashSet::new();
|
||||
groups
|
||||
}
|
|
@ -1,471 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
/*
|
||||
* This creates a pair of feth devices with the lower numbered device
|
||||
* being the ZeroTier virtual interface and the higher being the device
|
||||
* used to actually read and write packets. The latter gets no IP config
|
||||
* and is only used for I/O. The behavior of feth is similar to the
|
||||
* veth pairs that exist on Linux.
|
||||
*
|
||||
* The feth device has only existed since MacOS Sierra, but that's fairly
|
||||
* long ago in Mac terms.
|
||||
*
|
||||
* I/O with feth must be done using two different sockets. The BPF socket
|
||||
* is used to receive packets, while an AF_NDRV (low-level network driver
|
||||
* access) socket must be used to inject. AF_NDRV can't read IP frames
|
||||
* since BSD doesn't forward packets out the NDRV tap if they've already
|
||||
* been handled, and while BPF can inject its MTU for injected packets
|
||||
* is limited to 2048. AF_NDRV packet injection is required to inject
|
||||
* ZeroTier's large MTU frames.
|
||||
*
|
||||
* This is all completely undocumented. Finding it and learning how to
|
||||
* use it required sifting through XNU/Darwin kernel source code on
|
||||
* opensource.apple.com. Needless to say we are exploring other options
|
||||
* for future releases, but this works for now.
|
||||
*/
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashSet;
|
||||
use std::error::Error;
|
||||
use std::ffi::CString;
|
||||
use std::ptr::{null_mut, copy_nonoverlapping};
|
||||
use std::mem::{transmute, zeroed};
|
||||
use std::os::raw::{c_int, c_uchar, c_void};
|
||||
use std::process::Command;
|
||||
use std::sync::Mutex;
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use num_traits::cast::AsPrimitive;
|
||||
|
||||
use zerotier_core::{InetAddress, MAC, MulticastGroup, NetworkId};
|
||||
|
||||
use crate::osdep as osdep;
|
||||
use crate::getifaddrs;
|
||||
use crate::vnic::vnic::VNIC;
|
||||
use crate::osdep::getifmaddrs;
|
||||
|
||||
const BPF_BUFFER_SIZE: usize = 131072;
|
||||
const IFCONFIG: &'static str = "/sbin/ifconfig";
|
||||
const SYSCTL: &'static str = "/usr/sbin/sysctl";
|
||||
|
||||
// Holds names of feth devices and destroys them on Drop.
|
||||
struct MacFethDevice {
|
||||
pub name: String,
|
||||
pub peer_name: String
|
||||
}
|
||||
|
||||
impl Drop for MacFethDevice {
|
||||
fn drop(&mut self) {
|
||||
if self.name.len() > 0 && self.peer_name.len() > 0 {
|
||||
let destroy_peer = Command::new(IFCONFIG).arg(self.peer_name.as_str()).arg("destroy").spawn();
|
||||
if destroy_peer.is_ok() {
|
||||
let _ = destroy_peer.unwrap().wait();
|
||||
}
|
||||
let destroy = Command::new(IFCONFIG).arg(self.name.as_str()).arg("destroy").spawn();
|
||||
if destroy.is_ok() {
|
||||
let _ = destroy.unwrap().wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MacFethTap {
|
||||
network_id: u64,
|
||||
device: MacFethDevice,
|
||||
ndrv_fd: c_int,
|
||||
bpf_fd: c_int,
|
||||
bpf_no: u32,
|
||||
bpf_read_thread: Cell<Option<JoinHandle<()>>>,
|
||||
}
|
||||
|
||||
// Rust implementation of the following macro from Darwin sys/bpf.h:
|
||||
// #define BPF_WORDALIGN(x) (((x)+(BPF_ALIGNMENT-1))&~(BPF_ALIGNMENT-1))
|
||||
// ... and also ...
|
||||
// #define BPF_ALIGNMENT sizeof(int32_t)
|
||||
#[allow(non_snake_case)]
|
||||
#[inline(always)]
|
||||
fn BPF_WORDALIGN(x: isize) -> isize {
|
||||
(((x + 3) as usize) & (!(3 as usize))) as isize
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref MAC_FETH_BPF_DEVICES_USED: Mutex<BTreeSet<u32>> = Mutex::new(BTreeSet::new());
|
||||
}
|
||||
|
||||
fn device_ipv6_set_params(device: &String, perform_nud: bool, accept_ra: bool) -> bool {
|
||||
let dev = device.as_bytes();
|
||||
let mut ok = true;
|
||||
unsafe {
|
||||
let s = osdep::socket(osdep::AF_INET6 as c_int, osdep::SOCK_DGRAM as c_int, 0);
|
||||
if s < 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut nd: osdep::in6_ndireq = zeroed();
|
||||
copy_nonoverlapping(dev.as_ptr(), nd.ifname.as_mut_ptr().cast::<u8>(), if dev.len() > (nd.ifname.len() - 1) { nd.ifname.len() - 1 } else { dev.len() });
|
||||
if osdep::ioctl(s, osdep::c_SIOCGIFINFO_IN6, (&mut nd as *mut osdep::in6_ndireq).cast::<c_void>()) == 0 {
|
||||
let oldflags = nd.ndi.flags;
|
||||
if perform_nud {
|
||||
nd.ndi.flags |= osdep::ND6_IFF_PERFORMNUD as osdep::u_int32_t;
|
||||
} else {
|
||||
nd.ndi.flags &= !(osdep::ND6_IFF_PERFORMNUD as osdep::u_int32_t);
|
||||
}
|
||||
if nd.ndi.flags != oldflags {
|
||||
if osdep::ioctl(s, osdep::c_SIOCSIFINFO_FLAGS, (&mut nd as *mut osdep::in6_ndireq).cast::<c_void>()) != 0 {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
let mut ifr: osdep::in6_ifreq = zeroed();
|
||||
copy_nonoverlapping(dev.as_ptr(), ifr.ifr_name.as_mut_ptr().cast::<u8>(), if dev.len() > (ifr.ifr_name.len() - 1) { ifr.ifr_name.len() - 1 } else { dev.len() });
|
||||
if osdep::ioctl(s, if accept_ra { osdep::c_SIOCAUTOCONF_START } else { osdep::c_SIOCAUTOCONF_STOP }, (&mut ifr as *mut osdep::in6_ifreq).cast::<c_void>()) != 0 {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
osdep::close(s);
|
||||
}
|
||||
ok
|
||||
}
|
||||
|
||||
impl MacFethTap {
|
||||
/// Create a new MacFethTap with a function to call for Ethernet frames.
|
||||
/// The function F should return as quickly as possible. It should pass copies
|
||||
/// of frames elsewhere if anything needs to be done with them. The slice it's
|
||||
/// given will not remain valid after it returns. Also note that F will be called
|
||||
/// from another thread that is spawned here, so all its bound references must
|
||||
/// be "Send" and "Sync" e.g. Arc<>.
|
||||
pub(crate) fn new<F: Fn(&[u8]) + Send + Sync + 'static>(nwid: &NetworkId, mac: &MAC, mtu: i32, metric: i32, eth_frame_func: F) -> Result<MacFethTap, String> {
|
||||
// This tracks BPF devices we are using so we don't try to reopen them, and also
|
||||
// doubles as a global lock to ensure that only one feth tap is created at once per
|
||||
// ZeroTier process per system.
|
||||
let mut bpf_devices_used = MAC_FETH_BPF_DEVICES_USED.lock().unwrap();
|
||||
|
||||
if unsafe { osdep::getuid() } != 0 {
|
||||
return Err(String::from("ZeroTier MacFethTap must run as root"));
|
||||
}
|
||||
|
||||
let mut device_name: String;
|
||||
let mut peer_device_name: String;
|
||||
let mut device_feth_ctr = nwid.0 ^ (nwid.0 >> 32) ^ (nwid.0 >> 48);
|
||||
let mut device_alloc_tries = 0;
|
||||
loop {
|
||||
let device_feth_no = 100 + (device_feth_ctr % 4900);
|
||||
device_name = format!("feth{}", device_feth_no);
|
||||
peer_device_name = format!("feth{}", device_feth_no + 5000);
|
||||
let mut already_allocated = false;
|
||||
getifaddrs::for_each_address(|_: &InetAddress, dn: &str| {
|
||||
if dn.eq(&device_name) || dn.eq(&peer_device_name) {
|
||||
already_allocated = true;
|
||||
}
|
||||
});
|
||||
if !already_allocated {
|
||||
break;
|
||||
}
|
||||
|
||||
device_alloc_tries += 1;
|
||||
if device_alloc_tries >= 1000 {
|
||||
return Err(String::from("unable to find unallocated 'feth' device"));
|
||||
}
|
||||
device_feth_ctr += 1;
|
||||
}
|
||||
device_ipv6_set_params(&device_name, true, false);
|
||||
|
||||
// Set sysctl for max if_fake MTU. This is allowed to fail since this sysctl doesn't
|
||||
// exist on older versions of MacOS (and isn't required there). 16000 is larger than
|
||||
// anything ZeroTier supports. OS max is 16384 - some overhead.
|
||||
let _ = Command::new(SYSCTL).arg("net.link.fake.max_mtu").arg("10000").spawn().map(|mut c| { let _ = c.wait(); });
|
||||
|
||||
// Create pair of feth interfaces and create MacFethDevice struct.
|
||||
let cmd = Command::new(IFCONFIG).arg(&device_name).arg("create").spawn();
|
||||
if cmd.is_err() {
|
||||
return Err(format!("unable to create device '{}': {}", device_name.as_str(), cmd.err().unwrap().to_string()));
|
||||
}
|
||||
let _ = cmd.unwrap().wait();
|
||||
let cmd = Command::new(IFCONFIG).arg(&peer_device_name).arg("create").spawn();
|
||||
if cmd.is_err() {
|
||||
return Err(format!("unable to create device '{}': {}", peer_device_name.as_str(), cmd.err().unwrap().to_string()));
|
||||
}
|
||||
let _ = cmd.unwrap().wait();
|
||||
let device = MacFethDevice {
|
||||
name: device_name,
|
||||
peer_name: peer_device_name,
|
||||
};
|
||||
|
||||
// Set link-layer (MAC) address of primary interface.
|
||||
let cmd = Command::new(IFCONFIG).arg(&device.name).arg("lladdr").arg(mac.to_string()).spawn();
|
||||
if cmd.is_err() {
|
||||
return Err(format!("unable to configure device '{}': {}", &device.name, cmd.err().unwrap().to_string()));
|
||||
}
|
||||
let _ = cmd.unwrap().wait();
|
||||
|
||||
// Bind peer interfaces together.
|
||||
let cmd = Command::new(IFCONFIG).arg(&device.peer_name).arg("peer").arg(device.name.as_str()).spawn();
|
||||
if cmd.is_err() {
|
||||
return Err(format!("unable to configure device '{}': {}", &device.peer_name, cmd.err().unwrap().to_string()));
|
||||
}
|
||||
let _ = cmd.unwrap().wait();
|
||||
|
||||
// Set MTU of secondary peer interface, bring up.
|
||||
let cmd = Command::new(IFCONFIG).arg(&device.peer_name).arg("mtu").arg(mtu.to_string()).arg("up").spawn();
|
||||
if cmd.is_err() {
|
||||
return Err(format!("unable to configure device '{}': {}", &device.peer_name, cmd.err().unwrap().to_string()));
|
||||
}
|
||||
let _ = cmd.unwrap().wait();
|
||||
|
||||
// Set MTU and metric of primary interface, bring up.
|
||||
let cmd = Command::new(IFCONFIG).arg(&device.name).arg("mtu").arg(mtu.to_string()).arg("metric").arg(metric.to_string()).arg("up").spawn();
|
||||
if cmd.is_err() {
|
||||
return Err(format!("unable to configure device '{}': {}", &device.name.as_str(), cmd.err().unwrap().to_string()));
|
||||
}
|
||||
let _ = cmd.unwrap().wait();
|
||||
|
||||
// Look for a /dev/bpf node to open. Start at 1 since some software
|
||||
// hard codes /dev/bpf0 and we don't want to break it. If all BPF nodes
|
||||
// are taken MacOS automatically adds more, so we shouldn't run out.
|
||||
let mut bpf_no: u32 = 1;
|
||||
let mut bpf_fd: c_int = -1;
|
||||
loop {
|
||||
if bpf_devices_used.contains(&bpf_no) {
|
||||
bpf_no += 1;
|
||||
} else {
|
||||
let bpf_dev = CString::new(format!("/dev/bpf{}", bpf_no)).unwrap();
|
||||
let bpf_dev = bpf_dev.as_bytes_with_nul();
|
||||
bpf_fd = unsafe { osdep::open(bpf_dev.as_ptr().cast(), osdep::O_RDWR as c_int) };
|
||||
if bpf_fd >= 0 {
|
||||
break;
|
||||
}
|
||||
bpf_no += 1;
|
||||
if bpf_no > 1000 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if bpf_fd < 0 {
|
||||
return Err(String::from("unable to open /dev/bpf## where attempted ## from 1 to 1000"));
|
||||
}
|
||||
|
||||
// Set/get buffer length to use with reads from BPF device, trying to
|
||||
// use up to BPF_BUFFER_SIZE bytes.
|
||||
let mut fl: c_int = BPF_BUFFER_SIZE as c_int;
|
||||
if unsafe { osdep::ioctl(bpf_fd as c_int, osdep::c_BIOCSBLEN, (&mut fl as *mut c_int).cast::<c_void>()) } != 0 {
|
||||
unsafe { osdep::close(bpf_fd); }
|
||||
return Err(String::from("unable to configure BPF device"));
|
||||
}
|
||||
let bpf_read_size = fl as osdep::size_t;
|
||||
|
||||
// Set immediate mode for "live" capture.
|
||||
fl = 1;
|
||||
if unsafe { osdep::ioctl(bpf_fd as c_int, osdep::c_BIOCIMMEDIATE, (&mut fl as *mut c_int).cast::<c_void>()) } != 0 {
|
||||
unsafe { osdep::close(bpf_fd); }
|
||||
return Err(String::from("unable to configure BPF device"));
|
||||
}
|
||||
|
||||
// Do not send us back packets we inject or send.
|
||||
fl = 0;
|
||||
if unsafe { osdep::ioctl(bpf_fd as c_int, osdep::c_BIOCSSEESENT, (&mut fl as *mut c_int).cast::<c_void>()) } != 0 {
|
||||
unsafe { osdep::close(bpf_fd); }
|
||||
return Err(String::from("unable to configure BPF device"));
|
||||
}
|
||||
|
||||
// Bind BPF to secondary feth device.
|
||||
let mut bpf_ifr: osdep::ifreq = unsafe { std::mem::zeroed() };
|
||||
let peer_dev_name_bytes = device.peer_name.as_bytes();
|
||||
unsafe { copy_nonoverlapping(peer_dev_name_bytes.as_ptr(), bpf_ifr.ifr_name.as_mut_ptr().cast::<u8>(), if peer_dev_name_bytes.len() > (bpf_ifr.ifr_name.len() - 1) { bpf_ifr.ifr_name.len() - 1 } else { peer_dev_name_bytes.len() }); }
|
||||
if unsafe { osdep::ioctl(bpf_fd as c_int, osdep::c_BIOCSETIF, (&mut bpf_ifr as *mut osdep::ifreq).cast::<c_void>()) } != 0 {
|
||||
unsafe { osdep::close(bpf_fd); }
|
||||
return Err(String::from("unable to configure BPF device"));
|
||||
}
|
||||
|
||||
// Include Ethernet header in BPF captures.
|
||||
fl = 1;
|
||||
if unsafe { osdep::ioctl(bpf_fd as c_int, osdep::c_BIOCSHDRCMPLT, (&mut fl as *mut c_int).cast::<c_void>()) } != 0 {
|
||||
unsafe { osdep::close(bpf_fd); }
|
||||
return Err(String::from("unable to configure BPF device"));
|
||||
}
|
||||
|
||||
// Set promiscuous mode so bridging can work.
|
||||
fl = 1;
|
||||
if unsafe { osdep::ioctl(bpf_fd as c_int, osdep::c_BIOCPROMISC, (&mut fl as *mut c_int).cast::<c_void>()) } != 0 {
|
||||
unsafe { osdep::close(bpf_fd); }
|
||||
return Err(String::from("unable to configure BPF device"));
|
||||
}
|
||||
|
||||
// Create BPF listener thread, which calls the supplied function on each incoming packet.
|
||||
let t = std::thread::Builder::new().stack_size(zerotier_core::RECOMMENDED_THREAD_STACK_SIZE).spawn(move || {
|
||||
let mut buf: [u8; BPF_BUFFER_SIZE] = [0_u8; BPF_BUFFER_SIZE];
|
||||
let hdr_struct_size = std::mem::size_of::<osdep::bpf_hdr>() as isize;
|
||||
loop {
|
||||
let n = unsafe { osdep::read(bpf_fd, buf.as_mut_ptr().cast(), bpf_read_size) } as isize;
|
||||
if n >= 0 {
|
||||
let mut p: isize = 0;
|
||||
while (p + hdr_struct_size) < n {
|
||||
unsafe {
|
||||
let h = buf.as_ptr().offset(p).cast::<osdep::bpf_hdr>();
|
||||
let hdrlen = (*h).bh_hdrlen as isize;
|
||||
let caplen = (*h).bh_caplen as isize;
|
||||
let pktlen = hdrlen + caplen;
|
||||
if caplen > 0 && (p + pktlen) <= n {
|
||||
eth_frame_func(std::slice::from_raw_parts(buf.as_ptr().offset(p + hdrlen), caplen as usize));
|
||||
}
|
||||
p += BPF_WORDALIGN(pktlen);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
if t.is_err() {
|
||||
unsafe { osdep::close(bpf_fd); }
|
||||
return Err(String::from("unable to start thread"));
|
||||
}
|
||||
|
||||
// Create AF_NDRV socket used to inject packets. We could inject with BPF but that has
|
||||
// a hard MTU limit of 2048 so we have to use AF_NDRV instead. Performance is probably
|
||||
// the same, but it means another socket.
|
||||
let ndrv_fd = unsafe { osdep::socket(osdep::AF_NDRV as c_int, osdep::SOCK_RAW as c_int, 0) };
|
||||
if ndrv_fd < 0 {
|
||||
unsafe { osdep::close(bpf_fd); }
|
||||
return Err(String::from("unable to create AF_NDRV socket"));
|
||||
}
|
||||
let mut ndrv_sa: osdep::sockaddr_ndrv = unsafe { std::mem::zeroed() };
|
||||
ndrv_sa.snd_len = std::mem::size_of::<osdep::sockaddr_ndrv>() as c_uchar;
|
||||
ndrv_sa.snd_family = osdep::AF_NDRV as c_uchar;
|
||||
unsafe { copy_nonoverlapping(peer_dev_name_bytes.as_ptr(), ndrv_sa.snd_name.as_mut_ptr().cast::<u8>(), if peer_dev_name_bytes.len() > (bpf_ifr.ifr_name.len() - 1) { bpf_ifr.ifr_name.len() - 1 } else { peer_dev_name_bytes.len() }); }
|
||||
if unsafe { osdep::bind(ndrv_fd, (&ndrv_sa as *const osdep::sockaddr_ndrv).cast(), std::mem::size_of::<osdep::sockaddr_ndrv>() as osdep::socklen_t) } != 0 {
|
||||
unsafe { osdep::close(bpf_fd); }
|
||||
unsafe { osdep::close(ndrv_fd); }
|
||||
return Err(String::from("unable to bind AF_NDRV socket"));
|
||||
}
|
||||
if unsafe { osdep::connect(ndrv_fd, (&ndrv_sa as *const osdep::sockaddr_ndrv).cast(), std::mem::size_of::<osdep::sockaddr_ndrv>() as osdep::socklen_t) } != 0 {
|
||||
unsafe { osdep::close(bpf_fd); }
|
||||
unsafe { osdep::close(ndrv_fd); }
|
||||
return Err(String::from("unable to connect AF_NDRV socket"));
|
||||
}
|
||||
|
||||
bpf_devices_used.insert(bpf_no);
|
||||
|
||||
Ok(MacFethTap {
|
||||
network_id: nwid.0,
|
||||
device,
|
||||
ndrv_fd,
|
||||
bpf_fd,
|
||||
bpf_no,
|
||||
bpf_read_thread: Cell::new(Some(t.unwrap()))
|
||||
})
|
||||
}
|
||||
|
||||
fn have_ip(&self, ip: &InetAddress) -> bool {
|
||||
let mut have_ip = false;
|
||||
getifaddrs::for_each_address(|addr: &InetAddress, device_name: &str| {
|
||||
if device_name.eq(&self.device.name) && addr.eq(ip) {
|
||||
have_ip = true;
|
||||
}
|
||||
});
|
||||
have_ip
|
||||
}
|
||||
}
|
||||
|
||||
impl VNIC for MacFethTap {
|
||||
fn add_ip(&self, ip: &InetAddress) -> bool {
|
||||
if !self.have_ip(ip) {
|
||||
let cmd = Command::new(IFCONFIG).arg(&self.device.name).arg(if ip.is_v6() { "inet6" } else { "inet" }).arg(ip.to_string()).arg("alias").spawn();
|
||||
if cmd.is_ok() {
|
||||
let _ = cmd.unwrap().wait();
|
||||
}
|
||||
return self.have_ip(ip);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn remove_ip(&self, ip: &InetAddress) -> bool {
|
||||
if self.have_ip(ip) {
|
||||
let cmd = Command::new(IFCONFIG).arg(&self.device.name).arg(if ip.is_v6() { "inet6" } else { "inet" }).arg(ip.to_string()).arg("-alias").spawn();
|
||||
if cmd.is_ok() {
|
||||
let _ = cmd.unwrap().wait();
|
||||
}
|
||||
return !self.have_ip(ip);
|
||||
}
|
||||
true // if we don't have it it's successfully removed
|
||||
}
|
||||
|
||||
fn ips(&self) -> Vec<InetAddress> {
|
||||
let mut ipv: Vec<InetAddress> = Vec::new();
|
||||
ipv.reserve(8);
|
||||
let dev = self.device.name.as_str();
|
||||
getifaddrs::for_each_address(|addr: &InetAddress, device_name: &str| {
|
||||
if device_name.eq(dev) {
|
||||
ipv.push(addr.clone());
|
||||
}
|
||||
});
|
||||
ipv.sort();
|
||||
ipv
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn device_name(&self) -> String {
|
||||
self.device.name.clone()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_multicast_groups(&self) -> HashSet<MulticastGroup> {
|
||||
crate::vnic::common::get_l2_multicast_subscriptions(self.device.name.as_str())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn put(&self, source_mac: &zerotier_core::MAC, dest_mac: &zerotier_core::MAC, ethertype: u16, _vlan_id: u16, data: *const u8, len: usize) -> bool {
|
||||
let dm = dest_mac.0;
|
||||
let sm = source_mac.0;
|
||||
let mut hdr: [u8; 14] = [(dm >> 40) as u8, (dm >> 32) as u8, (dm >> 24) as u8, (dm >> 16) as u8, (dm >> 8) as u8, dm as u8, (sm >> 40) as u8, (sm >> 32) as u8, (sm >> 24) as u8, (sm >> 16) as u8, (sm >> 8) as u8, sm as u8, (ethertype >> 8) as u8, ethertype as u8];
|
||||
unsafe {
|
||||
let iov: [osdep::iovec; 2] = [
|
||||
osdep::iovec {
|
||||
iov_base: hdr.as_mut_ptr().cast(),
|
||||
iov_len: 14,
|
||||
},
|
||||
osdep::iovec {
|
||||
iov_base: transmute(data), // have to "cast away const" even though data is not modified by writev()
|
||||
iov_len: len as osdep::size_t,
|
||||
},
|
||||
];
|
||||
osdep::writev(self.ndrv_fd, iov.as_ptr(), 2) == (len + 14) as osdep::ssize_t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MacFethTap {
|
||||
fn drop(&mut self) {
|
||||
if self.bpf_fd >= 0 {
|
||||
unsafe {
|
||||
osdep::shutdown(self.bpf_fd, osdep::SHUT_RDWR as c_int);
|
||||
osdep::close(self.bpf_fd);
|
||||
MAC_FETH_BPF_DEVICES_USED.lock().unwrap().remove(&self.bpf_no);
|
||||
}
|
||||
}
|
||||
if self.ndrv_fd >= 0 {
|
||||
unsafe {
|
||||
osdep::close(self.ndrv_fd);
|
||||
}
|
||||
}
|
||||
let t = self.bpf_read_thread.replace(None);
|
||||
if t.is_some() {
|
||||
let _ = t.unwrap().join();
|
||||
}
|
||||
// NOTE: the feth devices are destroyed by MacFethDevice's drop().
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
mod vnic;
|
||||
mod common;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod mac_feth_tap;
|
|
@ -1,37 +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.
|
||||
*/
|
||||
/****/
|
||||
|
||||
/// Virtual network interface
|
||||
pub(crate) trait VNIC {
|
||||
/// Add a new IPv4 or IPv6 address to this interface, returning true on success.
|
||||
fn add_ip(&self, ip: &zerotier_core::InetAddress) -> bool;
|
||||
|
||||
/// Remove an IPv4 or IPv6 address, returning true on success.
|
||||
/// Nothing happens if the address is not found.
|
||||
fn remove_ip(&self, ip: &zerotier_core::InetAddress) -> bool;
|
||||
|
||||
/// Enumerate all IPs on this interface including ones assigned outside ZeroTier.
|
||||
fn ips(&self) -> Vec<zerotier_core::InetAddress>;
|
||||
|
||||
/// Get the OS-specific device name for this interface, e.g. zt## or tap##.
|
||||
fn device_name(&self) -> String;
|
||||
|
||||
/// Get L2 multicast groups to which this interface is subscribed.
|
||||
/// This doesn't do any IGMP snooping. It just reports the groups the port
|
||||
/// knows about. On some OSes this may not be supported in which case it
|
||||
/// will return an empty set.
|
||||
fn get_multicast_groups(&self) -> std::collections::BTreeSet<zerotier_core::MulticastGroup>;
|
||||
|
||||
/// Inject an Ethernet frame into this port.
|
||||
fn put(&self, source_mac: &zerotier_core::MAC, dest_mac: &zerotier_core::MAC, ethertype: u16, vlan_id: u16, data: *const u8, len: usize) -> bool;
|
||||
}
|
Loading…
Add table
Reference in a new issue