Almost done refactoring for long form addresses, etc.

This commit is contained in:
Adam Ierymenko 2023-03-21 15:29:22 -04:00
parent 3f047bc8dc
commit fcadb343a3
34 changed files with 409 additions and 994 deletions

258
debian/changelog vendored
View file

@ -1,258 +0,0 @@
zerotier-one (1.10.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 13 Oct 2022 01:00:00 -0700
zerotier-one (1.10.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 27 Jun 2022 01:00:00 -0700
zerotier-one (1.10.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 03 Jun 2022 01:00:00 -0700
zerotier-one (1.8.10) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 10 May 2022 01:00:00 -0700
zerotier-one (1.8.9) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 25 Apr 2022 01:00:00 -0700
zerotier-one (1.8.8) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 11 Apr 2022 01:00:00 -0700
zerotier-one (1.8.7) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 21 Mar 2022 01:00:00 -0700
zerotier-one (1.8.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 07 Mar 2022 01:00:00 -0700
zerotier-one (1.8.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 17 Dec 2021 01:00:00 -0700
zerotier-one (1.8.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 23 Nov 2021 01:00:00 -0700
zerotier-one (1.8.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 15 Nov 2021 01:00:00 -0700
zerotier-one (1.8.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 08 Nov 2021 01:00:00 -0700
zerotier-one (1.8.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 20 Oct 2021 01:00:00 -0700
zerotier-one (1.8.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 15 Sep 2021 01:00:00 -0700
zerotier-one (1.6.6) unstable; urgency=medium
* Backport endpoint mitigation against address collision attack.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Sep 2021 01:00:00 -0700
zerotier-one (1.6.5) unstable; urgency=medium
* Fix path filtering bug that could cause "software laser" effect.
* Fix printf overflow in CLI (not exploitable or security related)
* Fix Windows device enumeration issue.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 13 Apr 2021 01:00:00 -0700
zerotier-one (1.6.4) unstable; urgency=medium
* REALLY fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 15 Feb 2021 01:00:00 -0700
zerotier-one (1.6.3-1) unstable; urgency=medium
* Fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 02 Feb 2021 01:00:00 -0700
zerotier-one (1.6.2-2) unstable; urgency=medium
* This is a minor update to the 1.6.2 package to address issues with
running on ARMv6 CPUs like the Raspberry Pi Zero and original v1 Pi.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 31 Nov 2020 01:00:00 -0700
zerotier-one (1.6.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 30 Nov 2020 01:00:00 -0700
zerotier-one (1.6.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 24 Nov 2020 01:00:00 -0700
zerotier-one (1.6.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 19 Nov 2020 01:00:00 -0700
zerotier-one (1.5.0) unstable; urgency=medium
* Version 1.5.0 is actually 1.6.0-beta1
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 05 Aug 2020 01:00:00 -0700
zerotier-one (1.4.6) unstable; urgency=medium
* Update default root server list
* Fix build flags on "armhf" (32-bit ARM) platforms for better
compatibility with Pi Zero and other devices.
* Fix license text in one.cpp.
* Add a clarification to LICENSE.txt.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 30 Aug 2019 01:00:00 -0700
zerotier-one (1.4.4) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* License changed to BSL 1.1
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 23 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2-2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* This is a new build that fixes a binary build issue with containers and SELinux
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.0) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 29 Jul 2019 01:00:00 -0700
zerotier-one (1.2.12) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 25 Jul 2018 01:00:00 -0700
zerotier-one (1.2.10) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 08 May 2018 01:00:00 -0700
zerotier-one (1.2.8) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 27 Apr 2018 01:00:00 -0700
zerotier-one (1.2.6) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 17 Apr 2018 01:00:00 -0700
zerotier-one (1.2.4) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 24 Mar 2017 01:00:00 -0700
zerotier-one (1.2.2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 17 Mar 2017 01:00:00 -0700
zerotier-one (1.2.0) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 14 Mar 2017 09:08:00 -0700
zerotier-one (1.1.14) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Jul 2016 07:14:12 -0700
zerotier-one (1.1.12) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 12 Jul 2016 03:02:22 -0700
zerotier-one (1.1.10) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* ZeroTier Debian packages no longer depend on http-parser since its ABI is too unstable.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 12 Jul 2016 12:29:00 -0700
zerotier-one (1.1.8) unstable; urgency=low
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 08 Jul 2016 01:56:00 -0700
zerotier-one (1.1.6) unstable; urgency=medium
* First Debian release on ZeroTier, Inc. private apt repository.
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 24 Jun 2016 10:00:00 -0700
zerotier-one (1.1.5) UNRELEASED; urgency=medium
* Development package -- first clean Debian packaging test.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 08 Jun 2016 10:05:01 -0700

1
debian/compat vendored
View file

@ -1 +0,0 @@
8

19
debian/control vendored
View file

@ -1,19 +0,0 @@
Source: zerotier-one
Maintainer: Adam Ierymenko <adam.ierymenko@zerotier.com>
Section: net
Priority: optional
Standards-Version: 3.9.6
Build-Depends: debhelper (>= 9)
Vcs-Git: git://github.com/zerotier/ZeroTierOne
Vcs-Browser: https://github.com/zerotier/ZeroTierOne
Homepage: https://www.zerotier.com/
Package: zerotier-one
Architecture: any
Depends: iproute2, adduser, libstdc++6 (>= 5), openssl
Homepage: https://www.zerotier.com/
Description: ZeroTier network virtualization service
ZeroTier One lets you join ZeroTier virtual networks and
have them appear as tun/tap ports on your system. See
https://www.zerotier.com/ for instructions and
documentation.

19
debian/control.wheezy vendored
View file

@ -1,19 +0,0 @@
Source: zerotier-one
Maintainer: Adam Ierymenko <adam.ierymenko@zerotier.com>
Section: net
Priority: optional
Standards-Version: 3.9.4
Build-Depends: debhelper
Vcs-Git: git://github.com/zerotier/ZeroTierOne
Vcs-Browser: https://github.com/zerotier/ZeroTierOne
Homepage: https://www.zerotier.com/
Package: zerotier-one
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, iproute, libstdc++6
Homepage: https://www.zerotier.com/
Description: ZeroTier network virtualization service
ZeroTier One lets you join ZeroTier virtual networks and
have them appear as tun/tap ports on your system. See
https://www.zerotier.com/ for instructions and
documentation.

18
debian/copyright vendored
View file

@ -1,18 +0,0 @@
Format: http://dep.debian.net/deps/dep5
Upstream-Name: zerotier-one
Source: https://github.com/zerotier/ZeroTierOne
Files: *
Copyright: 2011-2016 ZeroTier, Inc.
License: ZeroTier BSL 1.1
License: ZeroTier BSL 1.1
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: 2025-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.

11
debian/postinst vendored
View file

@ -1,11 +0,0 @@
#!/bin/sh -e
case "$1" in
configure)
if ! id zerotier-one >>/dev/null 2>&1; then
useradd --system --user-group --home-dir /var/lib/zerotier-one --shell /usr/sbin/nologin --no-create-home zerotier-one
fi
;;
esac
#DEBHELPER#

16
debian/rules vendored
View file

@ -1,16 +0,0 @@
#!/usr/bin/make -f
CFLAGS=-O3 -fstack-protector-strong
CXXFLAGS=-O3 -fstack-protector-strong
%:
dh $@ --with systemd
override_dh_auto_build:
make
override_dh_systemd_start:
dh_systemd_start --restart-after-upgrade
override_dh_installinit:
dh_installinit --name=zerotier-one -- defaults

16
debian/rules.static vendored
View file

@ -1,16 +0,0 @@
#!/usr/bin/make -f
CFLAGS=-O3 -fstack-protector-strong
CXXFLAGS=-O3 -fstack-protector-strong
%:
dh $@ --with systemd
override_dh_auto_build:
# make -j 2
override_dh_systemd_start:
dh_systemd_start --restart-after-upgrade
override_dh_installinit:
dh_installinit --name=zerotier-one -- defaults

11
debian/rules.wheezy vendored
View file

@ -1,11 +0,0 @@
#!/usr/bin/make -f
CFLAGS=-O3 -fstack-protector
CXXFLAGS=-O3 -fstack-protector
%:
dh $@
override_dh_auto_build:
make -j 2

View file

@ -1,11 +0,0 @@
#!/usr/bin/make -f
CFLAGS=-O3 -fstack-protector
CXXFLAGS=-O3 -fstack-protector
%:
dh $@
override_dh_auto_build:
# make -j 2

View file

@ -1 +0,0 @@
3.0 (quilt)

View file

@ -1,4 +0,0 @@
[zerotier-one]
title=ZeroTier One
description=A planetary Ethernet switch
ports=9993/udp

View file

@ -1,49 +0,0 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: zerotier-one
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: ZeroTier One network virtualization service
### END INIT INFO
PATH=/bin:/usr/bin:/sbin:/usr/sbin
DESC="zerotier-one daemon"
NAME=zerotier-one
DAEMON=/usr/sbin/zerotier-one
PIDFILE=/var/lib/zerotier-one/zerotier-one.pid
SCRIPTNAME=/etc/init.d/"$NAME"
EXTRA_OPTS=-d
test -f $DAEMON || exit 0
. /lib/lsb/init-functions
case "$1" in
start) log_daemon_msg "Starting ZeroTier One" "zerotier-one"
start_daemon -p $PIDFILE $DAEMON $EXTRA_OPTS
log_end_msg $?
;;
stop) log_daemon_msg "Stopping ZeroTier One" "zerotier-one"
killproc -p $PIDFILE $DAEMON
RETVAL=$?
[ $RETVAL -eq 0 ] && [ -e "$PIDFILE" ] && rm -f $PIDFILE
log_end_msg $RETVAL
;;
restart) log_daemon_msg "Restarting ZeroTier One" "zerotier-one"
$0 stop
$0 start
;;
reload|force-reload) log_daemon_msg "Reloading ZeroTier One" "zerotier-one"
log_end_msg 0
;;
status)
status_of_proc -p $PIDFILE $DAEMON $NAME && exit 0 || exit $?
;;
*) log_action_msg "Usage: /etc/init.d/cron {start|stop|status|restart|reload|force-reload}"
exit 2
;;
esac
exit 0

View file

@ -1,12 +0,0 @@
[Unit]
Description=ZeroTier One
After=network-online.target network.target
Wants=network-online.target
[Service]
ExecStart=/usr/sbin/zerotier-one
Restart=always
KillMode=process
[Install]
WantedBy=multi-user.target

View file

@ -1,14 +0,0 @@
description "ZeroTier One upstart startup script"
author "Adam Ierymenko <adam.ierymenko@zerotier.com>"
start on (local-filesystems and net-device-up IFACE!=lo)
stop on runlevel [!2345]
respawn
respawn limit 2 300
#pre-start script
#end script
exec /usr/sbin/zerotier-one

View file

@ -306,8 +306,8 @@ pub mod v1 {
#[inline(always)]
pub fn get_packet_aad_bytes(destination: &Address, source: &Address, flags_cipher_hops: u8) -> [u8; 11] {
let mut id = [0u8; 11];
id[0..5].copy_from_slice(destination.as_bytes_v1());
id[5..10].copy_from_slice(source.as_bytes_v1());
id[0..5].copy_from_slice(destination.legacy_address().as_bytes());
id[5..10].copy_from_slice(source.legacy_address().as_bytes());
id[10] = flags_cipher_hops & FLAGS_FIELD_MASK_HIDE_HOPS;
id
}

View file

@ -1,6 +1,8 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
use std::fmt::Debug;
use std::hash::Hash;
use std::num::NonZeroU64;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
@ -12,16 +14,15 @@ use zerotier_utils::memory;
const BASE62_ALPHABET: &'static [u8; 62] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const BASE62_ALPHABET_REVERSE: [u8; 256] = [0; 256];
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct Address([u128; 3]);
impl Address {
pub const V1_ADDRESS_SIZE: usize = 5;
pub const V1_ADDRESS_STRING_SIZE: usize = 10;
pub const V2_ADDRESS_SIZE: usize = 48;
pub const V2_ADDRESS_STRING_SIZE: usize = 65;
/// Size of a full length address in bytes.
pub const SIZE_BYTES: usize = 48;
/// Addresses may not begin with 0xff; reserved for special signaling or future use.
pub const RESERVED_PREFIX: u8 = 0xff;
#[inline(always)]
@ -29,102 +30,50 @@ impl Address {
Self([0, 0, 0])
}
pub fn from_bytes(b: &[u8]) -> Result<Option<Self>, InvalidFormatError> {
if b.len() == Self::V1_ADDRESS_SIZE {
Ok(Self::from_bytes_v1(b))
} else if b.len() == Self::V2_ADDRESS_SIZE {
Ok(Self::from_bytes_v2(b))
#[inline(always)]
pub(crate) fn as_bytes_mut(&mut self) -> &mut [u8; 48] {
memory::as_byte_array_mut(&mut self.0)
}
#[inline(always)]
pub fn from_bytes(b: &[u8]) -> Result<Self, InvalidFormatError> {
if b.len() >= Self::SIZE_BYTES {
let a = Self(memory::load_raw(b));
if b[0] != Self::RESERVED_PREFIX && memory::load_raw::<u64>(b) != 0 {
Ok(a)
} else {
Err(InvalidFormatError)
}
} else {
Err(InvalidFormatError)
}
}
pub(crate) fn from_bytes_v1(b: &[u8]) -> Option<Self> {
let mut a = Self([0; 3]);
memory::as_byte_array_mut::<[u128; 3], 48>(&mut a.0)[..Self::V1_ADDRESS_SIZE].copy_from_slice(b);
if a.0[0] != 0 {
Some(a)
} else {
None
}
}
pub(crate) fn from_bytes_v2(b: &[u8]) -> Option<Self> {
#[inline(always)]
pub fn from_bytes_exact(b: &[u8; Self::SIZE_BYTES]) -> Result<Self, InvalidFormatError> {
let a = Self(memory::load_raw(b));
if a.0.iter().any(|i| *i != 0) {
Some(a)
if b[0] != Self::RESERVED_PREFIX && memory::load_raw::<u64>(b) != 0 {
Ok(a)
} else {
None
Err(InvalidFormatError)
}
}
#[inline(always)]
pub(crate) fn from_bytes_raw(b: &[u8; 48]) -> Option<Self> {
Self::from_bytes_v2(b)
}
#[inline(always)]
pub(crate) fn from_u64_v1(i: u64) -> Option<Self> {
if i != 0 {
Some(Self([i.wrapping_shl(24 + 64).to_be() as u128, 0, 0]))
} else {
None
}
}
/// True if this address lacks extended hash information.
#[inline(always)]
pub fn is_v1_only(&self) -> bool {
self.0[1] == 0 && self.0[2] == 0
}
#[inline(always)]
pub(crate) fn as_bytes_raw(&self) -> &[u8; 48] {
memory::as_byte_array::<[u128; 3], 48>(&self.0)
}
#[inline(always)]
pub(crate) fn as_bytes_raw_mut(&mut self) -> &mut [u8; 48] {
memory::as_byte_array_mut::<[u128; 3], 48>(&mut self.0)
}
#[inline(always)]
pub(crate) fn as_u64_v1(&self) -> u64 {
u128::from_be(self.0[0]).wrapping_shr(24 + 64) as u64
}
#[inline(always)]
pub(crate) fn as_bytes_v1(&self) -> &[u8; Self::V1_ADDRESS_SIZE] {
memory::array_range::<u8, 48, 0, 5>(memory::as_byte_array::<[u128; 3], 48>(&self.0))
pub fn legacy_address(&self) -> LegacyAddress {
LegacyAddress(NonZeroU64::new(memory::load_raw::<u64>(self.as_bytes())).unwrap())
}
/// Get all bits in this address (last 344 will be zero if this is only a V1 address).
#[inline(always)]
pub fn as_bytes_full(&self) -> &[u8; Self::V2_ADDRESS_SIZE] {
memory::as_byte_array::<[u128; 3], 48>(&self.0)
}
/// Get a byte serialized address.
/// This returns either a 40-bit short address or a full 384 bits depending on whether this
/// contains only a short V1 address or a full length V2 address. Use as_bytes_full() to
/// always get all 384 bits with the last 344 being zero for V1-only addresses.
pub fn as_bytes(&self) -> &[u8] {
if self.is_v1_only() {
&memory::as_byte_array::<[u128; 3], 48>(&self.0)[..Self::V1_ADDRESS_SIZE]
} else {
memory::as_byte_array::<[u128; 3], 48>(&self.0)
}
}
/// Get the short 10-digit address string for this address.
pub fn to_short_string(&self) -> String {
hex::to_string(&memory::as_byte_array::<[u128; 3], 48>(&self.0)[..Self::V1_ADDRESS_SIZE])
pub fn as_bytes(&self) -> &[u8; Self::SIZE_BYTES] {
memory::as_byte_array(&self.0)
}
}
impl ToString for Address {
fn to_string(&self) -> String {
let mut s = String::with_capacity(Self::V2_ADDRESS_STRING_SIZE);
let mut s = String::with_capacity(66);
let mut remainders = 0u16;
for qq in self.0.iter() {
let mut q = u128::from_be(*qq);
@ -148,43 +97,38 @@ impl FromStr for Address {
type Err = InvalidFormatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() == Self::V1_ADDRESS_STRING_SIZE {
return Ok(Self::from_bytes_v1(hex::from_string(s).as_slice()).ok_or(InvalidFormatError)?);
} else if s.len() == Self::V2_ADDRESS_STRING_SIZE {
let mut s = s.as_bytes();
let mut a = Self([0, 0, 0]);
for qi in 0..3 {
let mut q = 0u128;
for _ in 0..21 {
let r = BASE62_ALPHABET_REVERSE[s[0] as usize];
if r == 255 {
return Err(InvalidFormatError);
}
q += r as u128;
s = &s[1..];
q *= 62;
}
a.0[qi] = q;
}
let mut remainders = 0u16;
for _ in 0..2 {
let mut s = s.as_bytes();
let mut a = Self([0, 0, 0]);
for qi in 0..3 {
let mut q = 0u128;
for _ in 0..21 {
let r = BASE62_ALPHABET_REVERSE[s[0] as usize];
s = &s[1..];
if r == 255 {
return Err(InvalidFormatError);
}
remainders += r as u16;
s = &s[1..];
remainders *= 62;
q *= 62;
q += r as u128;
}
if remainders > 511 {
a.0[qi] = q;
}
let mut remainders = 0u16;
for _ in 0..2 {
let r = BASE62_ALPHABET_REVERSE[s[0] as usize];
s = &s[1..];
if r == 255 {
return Err(InvalidFormatError);
}
a.0[0] += (remainders.wrapping_shr(6) & 7) as u128;
a.0[1] += (remainders.wrapping_shr(3) & 7) as u128;
a.0[2] += (remainders & 7) as u128;
return Ok(a);
remainders *= 62;
remainders += r as u16;
}
return Err(InvalidFormatError);
if remainders > 511 {
return Err(InvalidFormatError);
}
a.0[0] += (remainders.wrapping_shr(6) & 7) as u128;
a.0[1] += (remainders.wrapping_shr(3) & 7) as u128;
a.0[2] += (remainders & 7) as u128;
return Ok(a);
}
}
@ -196,6 +140,7 @@ impl PartialOrd for Address {
}
impl Ord for Address {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
u128::from_be(self.0[0])
.cmp(&u128::from_be(other.0[0]))
@ -205,12 +150,21 @@ impl Ord for Address {
}
impl Debug for Address {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.to_string().as_str())
}
}
impl Hash for Address {
#[inline(always)]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u128(self.0[0])
}
}
impl Serialize for Address {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
@ -228,15 +182,17 @@ struct AddressVisitor;
impl<'de> serde::de::Visitor<'de> for AddressVisitor {
type Value = Address;
#[inline]
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a ZeroTier address")
formatter.write_str("address")
}
#[inline]
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if let Ok(Some(v)) = Address::from_bytes(v) {
if let Ok(v) = Address::from_bytes(v) {
Ok(v)
} else {
Err(E::custom("invalid address"))
@ -252,6 +208,7 @@ impl<'de> serde::de::Visitor<'de> for AddressVisitor {
}
impl<'de> Deserialize<'de> for Address {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Address, D::Error>
where
D: Deserializer<'de>,
@ -263,3 +220,133 @@ impl<'de> Deserialize<'de> for Address {
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct LegacyAddress(NonZeroU64);
impl LegacyAddress {
pub const SIZE_BYTES: usize = 5;
pub const SIZE_HEX_STRING: usize = 10;
#[inline(always)]
pub fn from_bytes(b: &[u8]) -> Option<Self> {
if b.len() >= Self::SIZE_BYTES && b[0] != Address::RESERVED_PREFIX {
let mut tmp = 0u64.to_ne_bytes();
tmp[..Address::SIZE_BYTES].copy_from_slice(b);
NonZeroU64::new(u64::from_ne_bytes(tmp)).map(|i| Self(i))
} else {
None
}
}
#[inline(always)]
pub fn from_bytes_exact(b: &[u8; Self::SIZE_BYTES]) -> Option<Self> {
if b[0] != Address::RESERVED_PREFIX {
let mut tmp = 0u64.to_ne_bytes();
tmp[..Address::SIZE_BYTES].copy_from_slice(b);
NonZeroU64::new(u64::from_ne_bytes(tmp)).map(|i| Self(i))
} else {
None
}
}
#[inline(always)]
pub(crate) fn from_u64(i: u64) -> Option<Self> {
NonZeroU64::new(i.wrapping_shl(24).to_be()).map(|i| Self(i))
}
#[inline(always)]
pub(crate) fn to_u64(&self) -> u64 {
u64::from_be(self.0.get()).wrapping_shr(24)
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8; Self::SIZE_BYTES] {
debug_assert_eq!(std::mem::size_of::<NonZeroU64>(), 8);
memory::array_range::<u8, 8, 0, 5>(memory::as_byte_array::<NonZeroU64, 8>(&self.0))
}
}
impl ToString for LegacyAddress {
fn to_string(&self) -> String {
hex::to_string(&memory::as_byte_array::<NonZeroU64, 8>(&self.0)[..Self::SIZE_BYTES])
}
}
impl FromStr for LegacyAddress {
type Err = InvalidFormatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() == Self::SIZE_HEX_STRING {
Self::from_bytes(hex::from_string(s).as_slice()).ok_or(InvalidFormatError)
} else {
Err(InvalidFormatError)
}
}
}
impl Debug for LegacyAddress {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.to_string().as_str())
}
}
impl Serialize for LegacyAddress {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(self.to_string().as_str())
} else {
serializer.serialize_bytes(self.as_bytes())
}
}
}
struct LegacyAddressVisitor;
impl<'de> serde::de::Visitor<'de> for LegacyAddressVisitor {
type Value = LegacyAddress;
#[inline]
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("address")
}
#[inline]
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if let Some(v) = LegacyAddress::from_bytes(v) {
Ok(v)
} else {
Err(E::custom("invalid address"))
}
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
LegacyAddress::from_str(v).map_err(|e| E::custom(e.to_string()))
}
}
impl<'de> Deserialize<'de> for LegacyAddress {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
deserializer.deserialize_str(LegacyAddressVisitor)
} else {
deserializer.deserialize_bytes(LegacyAddressVisitor)
}
}
}

View file

@ -140,7 +140,7 @@ impl Marshalable for Endpoint {
}
Endpoint::ZeroTier(a) => {
buf.append_u8(16 + TYPE_ZEROTIER)?;
buf.append_bytes_fixed(a.as_bytes_raw())?;
buf.append_bytes_fixed(a.as_bytes())?;
}
Endpoint::Ethernet(m) => {
buf.append_u8(16 + TYPE_ETHERNET)?;
@ -184,7 +184,7 @@ impl Marshalable for Endpoint {
}
Endpoint::ZeroTierEncap(a) => {
buf.append_u8(16 + TYPE_ZEROTIER_ENCAP)?;
buf.append_bytes_fixed(a.as_bytes_raw())?;
buf.append_bytes_fixed(a.as_bytes())?;
}
}
Ok(())
@ -212,7 +212,7 @@ impl Marshalable for Endpoint {
match type_byte - 16 {
TYPE_NIL => Ok(Endpoint::Nil),
TYPE_ZEROTIER => Ok(Endpoint::ZeroTier(
Address::from_bytes_raw(buf.read_bytes_fixed(cursor)?).ok_or(UnmarshalError::InvalidData)?,
Address::from_bytes(buf.read_bytes_fixed::<{ Address::SIZE_BYTES }>(cursor)?).map_err(|_| UnmarshalError::InvalidData)?,
)),
TYPE_ETHERNET => Ok(Endpoint::Ethernet(MAC::unmarshal(buf, cursor)?)),
TYPE_WIFIDIRECT => Ok(Endpoint::WifiDirect(MAC::unmarshal(buf, cursor)?)),
@ -225,7 +225,7 @@ impl Marshalable for Endpoint {
)),
TYPE_WEBRTC => Ok(Endpoint::WebRTC(buf.read_bytes(buf.read_varint(cursor)? as usize, cursor)?.to_vec())),
TYPE_ZEROTIER_ENCAP => Ok(Endpoint::ZeroTier(
Address::from_bytes_raw(buf.read_bytes_fixed(cursor)?).ok_or(UnmarshalError::InvalidData)?,
Address::from_bytes(buf.read_bytes_fixed::<{ Address::SIZE_BYTES }>(cursor)?).map_err(|_| UnmarshalError::InvalidData)?,
)),
_ => Err(UnmarshalError::InvalidData),
}

View file

@ -5,9 +5,9 @@ use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::Address;
use super::{Address, LegacyAddress};
use zerotier_crypto::hash::{SHA384, SHA384_HASH_SIZE, SHA512};
use zerotier_crypto::hash::{SHA384, SHA512};
use zerotier_crypto::p384::*;
use zerotier_crypto::salsa::Salsa;
use zerotier_crypto::secret::Secret;
@ -40,16 +40,20 @@ pub struct P384 {
pub p384_self_signature: [u8; P384_ECDSA_SIGNATURE_SIZE],
}
#[derive(Clone)]
pub struct IdentitySecret {
pub public: Valid<Identity>,
pub x25519: X25519Secret,
pub p384: Option<P384Secret>,
}
#[derive(Clone)]
pub struct X25519Secret {
pub ecdh: X25519KeyPair,
pub eddsa: Ed25519KeyPair,
}
#[derive(Clone)]
pub struct P384Secret {
pub ecdh: P384KeyPair,
pub ecdsa: P384KeyPair,
@ -65,37 +69,35 @@ impl Identity {
/// Generate a new ZeroTier identity.
/// If x25519_only is true a legacy identity without NIST P-384 key pairs will be generated.
pub fn generate(x25519_only: bool) -> (Identity, IdentitySecret) {
pub fn generate(x25519_only: bool) -> IdentitySecret {
// Generate X25519 portions of the identity plus the first 40 bits of the address, which are
// the legacy "short" address.
let x25519_ecdh = X25519KeyPair::generate();
let ed25519_eddsa = Ed25519KeyPair::generate();
let x25519_ecdh_public = x25519_ecdh.public_bytes();
let ed25519_eddsa_public = ed25519_eddsa.public_bytes();
let mut secret = IdentitySecret {
x25519: X25519Secret {
ecdh: X25519KeyPair::generate(),
eddsa: Ed25519KeyPair::generate(),
},
p384: None,
};
let mut public = Identity {
address: Address::new_uninitialized(),
x25519: X25519 {
ecdh: secret.x25519.ecdh.public_bytes(),
eddsa: secret.x25519.eddsa.public_bytes(),
},
public: Valid::mark_valid(Identity {
address: Address::new_uninitialized(),
x25519: X25519 { ecdh: x25519_ecdh_public, eddsa: ed25519_eddsa_public },
p384: None,
}),
x25519: X25519Secret { ecdh: x25519_ecdh, eddsa: ed25519_eddsa },
p384: None,
};
loop {
let mut legacy_address_derivation_hash = SHA512::new();
legacy_address_derivation_hash.update(&public.x25519.ecdh);
legacy_address_derivation_hash.update(&public.x25519.eddsa);
legacy_address_derivation_hash.update(&secret.public.x25519.ecdh);
legacy_address_derivation_hash.update(&secret.public.x25519.eddsa);
let mut legacy_address_derivation_hash = legacy_address_derivation_hash.finish();
legacy_address_derivation_work_function(&mut legacy_address_derivation_hash);
if legacy_address_derivation_hash[0] < Self::V0_IDENTITY_POW_THRESHOLD && legacy_address_derivation_hash[59] != Address::RESERVED_PREFIX {
public.address.as_bytes_raw_mut()[..5].copy_from_slice(&legacy_address_derivation_hash[59..64]);
secret.public.address.as_bytes_mut()[..5].copy_from_slice(&legacy_address_derivation_hash[59..64]);
break;
} else {
// Regenerate one of the two keys until we meet the legacy address work function criteria.
secret.x25519.ecdh = X25519KeyPair::generate();
public.x25519.ecdh = secret.x25519.ecdh.public_bytes();
secret.public.x25519.ecdh = secret.x25519.ecdh.public_bytes();
}
}
@ -105,7 +107,7 @@ impl Identity {
ecdh: P384KeyPair::generate(),
ecdsa: P384KeyPair::generate(),
});
public.p384 = secret.p384.as_ref().map(|p384s| P384 {
secret.public.p384 = secret.p384.as_ref().map(|p384s| P384 {
ecdh: p384s.ecdh.public_key().clone(),
ecdsa: p384s.ecdsa.public_key().clone(),
ed25519_self_signature: [0u8; ED25519_SIGNATURE_SIZE],
@ -114,20 +116,20 @@ impl Identity {
}
// Bits 40-384 of the address are filled from a SHA384 hash of all keys for a full length V2 address.
public.populate_extended_address_bits();
secret.public.populate_extended_address_bits();
// For V2 identities we include two self signatures to ensure that all these different key pairs
// are properly bound together and can't be changed independently.
if !x25519_only {
let mut for_self_signing =
[0u8; Address::V2_ADDRESS_SIZE + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE];
public.encode_for_self_signing(&mut for_self_signing);
let p384 = public.p384.as_mut().unwrap();
[0u8; Address::SIZE_BYTES + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE];
secret.public.encode_for_self_signing(&mut for_self_signing);
let p384 = secret.public.p384.as_mut().unwrap();
p384.ed25519_self_signature = secret.x25519.eddsa.sign(&for_self_signing);
p384.p384_self_signature = secret.p384.as_ref().unwrap().ecdsa.sign(&for_self_signing);
}
(public, secret)
secret
}
/// Locally validate this identity.
@ -145,10 +147,10 @@ impl Identity {
}
}
/// Hash all keys for generation of bits 40-384 of the address.
/// Populate bits 40-384 of the address with a hash of everything else.
fn populate_extended_address_bits(&mut self) {
let mut sha = SHA384::new();
sha.update(self.address.as_bytes_v1()); // including the short address means we can elide the expensive legacy hash in the future
sha.update(self.address.legacy_address().as_bytes()); // including the short address means we can elide the expensive legacy hash in the future
sha.update(&[Self::ALGORITHM_X25519
| if self.p384.is_some() {
Self::ALGORITHM_P384
@ -162,16 +164,16 @@ impl Identity {
sha.update(p384.ecdsa.as_bytes());
}
let sha = sha.finish();
self.address.as_bytes_raw_mut()[5..].copy_from_slice(&sha[..48 - 5]);
self.address.as_bytes_mut()[LegacyAddress::SIZE_BYTES..].copy_from_slice(&sha[..Address::SIZE_BYTES - LegacyAddress::SIZE_BYTES]);
}
/// Encode for self-signing, used only with p384 keys enabled and panics otherwise.
fn encode_for_self_signing(
&self,
buf: &mut [u8; Address::V2_ADDRESS_SIZE + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE],
buf: &mut [u8; Address::SIZE_BYTES + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE],
) {
let mut buf = &mut buf[Address::V2_ADDRESS_SIZE + 1..];
let _ = buf.write_all(self.address.as_bytes_raw());
let mut buf = &mut buf[Address::SIZE_BYTES + 1..];
let _ = buf.write_all(self.address.as_bytes());
let _ = buf.write_all(&[Self::ALGORITHM_X25519 | Self::ALGORITHM_P384]);
let _ = buf.write_all(&self.x25519.ecdh);
let _ = buf.write_all(&self.x25519.eddsa);
@ -181,10 +183,10 @@ impl Identity {
}
pub fn from_bytes(b: &[u8]) -> Result<Self, InvalidFormatError> {
if b.len() == packed::V2_PUBLIC_SIZE && b[Address::V1_ADDRESS_SIZE] == (Self::ALGORITHM_X25519 | Self::ALGORITHM_P384) {
let p: &packed::V2Public = unsafe { &*b.as_ptr().cast() };
if b.len() == packed::V2_PUBLIC_SIZE && b[LegacyAddress::SIZE_BYTES] == (Self::ALGORITHM_X25519 | Self::ALGORITHM_P384) {
let p: &packed::V2Public = memory::cast_to_struct(b);
let mut id = Self {
address: Address::from_bytes_v1(&p.short_address).ok_or(InvalidFormatError)?,
address: Address::new_uninitialized(),
x25519: X25519 { ecdh: p.c25519, eddsa: p.ed25519 },
p384: Some(P384 {
ecdh: P384PublicKey::from_bytes(&p.p384_ecdh).ok_or(InvalidFormatError)?,
@ -193,19 +195,17 @@ impl Identity {
p384_self_signature: p.p384_self_signature,
}),
};
let hash = SHA384::hash(&b[..packed::V2_PUBLIC_SIZE]);
id.address.as_bytes_raw_mut()[Address::V1_ADDRESS_SIZE..].copy_from_slice(&hash[..SHA384_HASH_SIZE - Address::V1_ADDRESS_SIZE]);
id.address.as_bytes_mut()[..LegacyAddress::SIZE_BYTES].copy_from_slice(&p.short_address);
id.populate_extended_address_bits();
return Ok(id);
} else if b.len() == packed::V1_PUBLIC_SIZE && b[Address::V1_ADDRESS_SIZE] == Self::ALGORITHM_X25519 {
let p: &packed::V1Public = unsafe { &*b.as_ptr().cast() };
} else if b.len() == packed::V1_PUBLIC_SIZE && b[LegacyAddress::SIZE_BYTES] == Self::ALGORITHM_X25519 {
let p: &packed::V1Public = memory::cast_to_struct(b);
let mut id = Self {
address: Address::from_bytes_v1(&p.short_address).ok_or(InvalidFormatError)?,
address: Address::new_uninitialized(),
x25519: X25519 { ecdh: p.c25519, eddsa: p.ed25519 },
p384: None,
};
let hash = SHA384::hash(&b[..packed::V1_PUBLIC_SIZE]);
id.address.as_bytes_raw_mut()[Address::V1_ADDRESS_SIZE..].copy_from_slice(&hash[..SHA384_HASH_SIZE - Address::V1_ADDRESS_SIZE]);
id.address.as_bytes_mut()[..LegacyAddress::SIZE_BYTES].copy_from_slice(&p.short_address);
id.populate_extended_address_bits();
return Ok(id);
}
@ -215,7 +215,7 @@ impl Identity {
pub fn write_bytes<W: Write>(&self, w: &mut W, x25519_only: bool) -> Result<(), std::io::Error> {
if let (false, Some(p384)) = (x25519_only, self.p384.as_ref()) {
w.write_all(memory::as_byte_array::<packed::V2Public, { packed::V2_PUBLIC_SIZE }>(&packed::V2Public {
short_address: *self.address.as_bytes_v1(),
short_address: *self.address.legacy_address().as_bytes(),
algorithms: Self::ALGORITHM_X25519 | Self::ALGORITHM_P384,
c25519: self.x25519.ecdh,
ed25519: self.x25519.eddsa,
@ -226,7 +226,7 @@ impl Identity {
}))
} else {
w.write_all(memory::as_byte_array::<packed::V1Public, { packed::V1_PUBLIC_SIZE }>(&packed::V1Public {
short_address: *self.address.as_bytes_v1(),
short_address: *self.address.legacy_address().as_bytes(),
algorithms: Self::ALGORITHM_X25519,
c25519: self.x25519.ecdh,
ed25519: self.x25519.eddsa,
@ -252,7 +252,7 @@ impl ToString for Identity {
} else {
format!(
"{}:0:{}:{}",
self.address.to_short_string(),
self.address.legacy_address().to_string(),
hex::to_string(&self.x25519.ecdh),
hex::to_string(&self.x25519.eddsa)
)
@ -300,7 +300,7 @@ impl Marshalable for Identity {
fn unmarshal<const BL: usize>(buf: &Buffer<BL>, cursor: &mut usize) -> Result<Self, UnmarshalError> {
const V1_ALG: u8 = Identity::ALGORITHM_X25519;
const V2_ALG: u8 = Identity::ALGORITHM_X25519 | Identity::ALGORITHM_P384;
match buf.u8_at(*cursor + Address::V1_ADDRESS_SIZE)? {
match buf.u8_at(*cursor + LegacyAddress::SIZE_BYTES)? {
V1_ALG => Identity::from_bytes(buf.read_bytes_fixed::<{ packed::V1_PUBLIC_SIZE }>(cursor)?).map_err(|_| UnmarshalError::InvalidData),
V2_ALG => Identity::from_bytes(buf.read_bytes_fixed::<{ packed::V2_PUBLIC_SIZE }>(cursor)?).map_err(|_| UnmarshalError::InvalidData),
_ => Err(UnmarshalError::UnsupportedVersion),
@ -446,6 +446,9 @@ mod packed {
pub ed25519_self_signature: [u8; ED25519_SIGNATURE_SIZE],
pub p384_self_signature: [u8; P384_ECDSA_SIGNATURE_SIZE],
}
unsafe impl memory::FlatBuffer for V1Public {}
unsafe impl memory::FlatBuffer for V2Public {}
}
fn legacy_address_derivation_work_function(digest: &mut [u8; 64]) {

View file

@ -77,6 +77,7 @@ pub const AF_INET: AddressFamilyType = libc::AF_INET as AddressFamilyType;
#[cfg(not(windows))]
pub const AF_INET6: AddressFamilyType = libc::AF_INET6 as AddressFamilyType;
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum IpScope {
None = 0,

View file

@ -7,12 +7,13 @@ mod mac;
mod node;
mod path;
mod peer;
mod peermap;
mod rootset;
pub mod identity;
pub mod inetaddress;
pub use address::Address;
pub use address::{Address, LegacyAddress};
pub use endpoint::Endpoint;
pub use event::Event;
pub use inetaddress::InetAddress;

View file

@ -8,20 +8,22 @@ use std::sync::{Arc, Mutex, RwLock, Weak};
use std::time::Duration;
use crate::protocol::*;
use crate::vl1::address::Address;
use crate::vl1::address::{Address, LegacyAddress};
use crate::vl1::debug_event;
use crate::vl1::endpoint::Endpoint;
use crate::vl1::event::Event;
use crate::vl1::identity::{Identity, IdentitySecret};
use crate::vl1::path::{Path, PathServiceResult};
use crate::vl1::peer::Peer;
use crate::vl1::peermap::PeerMap;
use crate::vl1::rootset::RootSet;
use zerotier_crypto::random;
use zerotier_crypto::typestate::{Valid, Verified};
use zerotier_utils::gate::IntervalGate;
use zerotier_utils::hex;
use zerotier_utils::marshalable::Marshalable;
use zerotier_utils::ringbuffer::RingBuffer;
use zerotier_utils::tokio::io::AsyncWriteExt;
/// Interface trait to be implemented by code that's using the ZeroTier network hypervisor.
///
@ -217,28 +219,23 @@ struct WhoisQueueItem<Application: ApplicationLayer + ?Sized> {
retry_count: u16,
}
/// A ZeroTier VL1 node that can communicate securely with the ZeroTier peer-to-peer network.
pub struct Node<Application: ApplicationLayer + ?Sized> {
pub instance_id: [u8; 16],
pub identity: Valid<Identity>,
identity_secret: IdentitySecret,
intervals: Mutex<BackgroundTaskIntervals>,
paths: RwLock<HashMap<PathKey<'static, 'static, Application::LocalSocket>, Arc<Path<Application>>>>,
peers: RwLock<HashMap<Address, Arc<Peer<Application>>>>,
peers: PeerMap<Application>,
roots: RwLock<RootInfo<Application>>,
best_root: RwLock<Option<Arc<Peer<Application>>>>,
whois_queue: Mutex<HashMap<Address, WhoisQueueItem<Application>>>,
whois_queue: Mutex<HashMap<LegacyAddress, WhoisQueueItem<Application>>>,
}
impl<Application: ApplicationLayer + ?Sized> Node<Application> {
pub fn new(identity: Valid<Identity>, identity_secret: IdentitySecret) -> Self {
pub fn new(identity_secret: IdentitySecret) -> Self {
Self {
instance_id: random::get_bytes_secure(),
identity,
identity_secret,
intervals: Mutex::new(BackgroundTaskIntervals::default()),
paths: RwLock::new(HashMap::new()),
peers: RwLock::new(HashMap::new()),
peers: PeerMap::new(),
roots: RwLock::new(RootInfo {
sets: HashMap::new(),
roots: HashMap::new(),
@ -251,9 +248,19 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
}
}
#[inline]
#[inline(always)]
pub fn identity(&self) -> &Valid<Identity> {
&self.identity_secret.public
}
#[inline(always)]
pub fn peer(&self, a: &Address) -> Option<Arc<Peer<Application>>> {
self.peers.read().unwrap().get(&a).cloned()
self.peers.get(a)
}
#[inline(always)]
pub(crate) fn peer_legacy(&self, a: &LegacyAddress) -> Option<Arc<Peer<Application>>> {
self.peers.get_legacy(a)
}
#[inline]
@ -280,7 +287,6 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
}
/// Add a new root set or update the existing root set if the new root set is newer and otherwise matches.
#[inline]
pub fn add_update_root_set(&self, rs: Verified<RootSet>) -> bool {
let mut roots = self.roots.write().unwrap();
if let Some(entry) = roots.sets.get_mut(&rs.name) {
@ -299,13 +305,11 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
}
/// Returns whether or not this node has any root sets defined.
#[inline]
pub fn has_roots_defined(&self) -> bool {
self.roots.read().unwrap().sets.iter().any(|rs| !rs.1.members.is_empty())
}
/// Initialize with default roots if there are no roots defined, otherwise do nothing.
#[inline]
pub fn init_default_roots(&self) -> bool {
if !self.has_roots_defined() {
self.add_update_root_set(RootSet::zerotier_default())
@ -315,7 +319,6 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
}
/// Get the root sets that this node trusts.
#[inline]
pub fn root_sets(&self) -> Vec<RootSet> {
self.roots.read().unwrap().sets.values().cloned().map(|s| s.remove_typestate()).collect()
}
@ -359,26 +362,23 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
for (_, rs) in roots.sets.iter() {
for m in rs.members.iter() {
if m.endpoints.is_some() && !m.identity.eq(&self.identity) {
if m.identity.eq(&self.identity_secret.public) {
let _ = my_root_sets
.get_or_insert_with(|| Vec::new())
.write_all(rs.to_buffer::<{ RootSet::MAX_MARSHAL_SIZE }>().unwrap().as_bytes());
} else if m.endpoints.is_some() {
debug_event!(
app,
"[vl1] examining root {} with {} endpoints",
m.identity.address.to_string(),
m.endpoints.as_ref().map_or(0, |e| e.len())
);
let peers = self.peers.read().unwrap();
if let Some(peer) = peers.get(&m.identity.address) {
if let Some(peer) = self.peers.get(&m.identity.address) {
new_roots.insert(peer.clone(), m.endpoints.as_ref().unwrap().iter().cloned().collect());
} else {
if let Some(peer) = Peer::new(&self.identity_secret, Valid::mark_valid(m.identity.clone()), time_ticks) {
drop(peers);
new_roots.insert(
self.peers
.write()
.unwrap()
.entry(m.identity.address.clone())
.or_insert_with(|| Arc::new(peer))
.clone(),
self.peers.insert_if_unique(Arc::new(peer)).0,
m.endpoints.as_ref().unwrap().iter().cloned().collect(),
);
} else {
@ -503,14 +503,14 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
let mut dead_peers = Vec::new();
{
let roots = self.roots.read().unwrap();
for (a, peer) in self.peers.read().unwrap().iter() {
self.peers.each(|peer| {
if !peer.service(app, self, time_ticks) && !roots.roots.contains_key(peer) {
dead_peers.push(a.clone());
dead_peers.push(peer.identity.address.clone());
}
}
});
}
for dp in dead_peers.iter() {
self.peers.write().unwrap().remove(dp);
self.peers.remove(dp);
}
}
@ -591,10 +591,10 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
// Legacy ZeroTier V1 packet handling
if let Ok(fragment_header) = packet.struct_mut_at::<v1::FragmentHeader>(0) {
if let Some(dest) = Address::from_bytes_v1(&fragment_header.dest) {
if let Some(dest) = LegacyAddress::from_bytes_exact(&fragment_header.dest) {
// Packet is addressed to this node.
if dest == self.identity.address {
if dest == self.identity_secret.public.address.legacy_address() {
let fragment_header = &*fragment_header; // discard mut
let path = self.canonical_path(source_endpoint, source_local_socket, source_local_interface, time_ticks);
path.log_receive_anything(time_ticks);
@ -622,8 +622,8 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
debug_event!(app, "[vl1] [v1] #{:0>16x} packet fully assembled!", fragment_header_id);
if let Ok(packet_header) = frag0.struct_at::<v1::PacketHeader>(0) {
if let Some(source) = Address::from_bytes_v1(&packet_header.src) {
if let Some(peer) = self.peer(&source) {
if let Some(source) = LegacyAddress::from_bytes_exact(&packet_header.src) {
if let Some(peer) = self.peers.get_legacy(&source) {
peer.v1_proto_receive(
self,
app,
@ -657,8 +657,8 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
} else if let Ok(packet_header) = packet.struct_at::<v1::PacketHeader>(0) {
debug_event!(app, "[vl1] [v1] #{:0>16x} is unfragmented", u64::from_be_bytes(packet_header.id));
if let Some(source) = Address::from_bytes_v1(&packet_header.src) {
if let Some(peer) = self.peer(&source) {
if let Some(source) = LegacyAddress::from_bytes_exact(&packet_header.src) {
if let Some(peer) = self.peers.get_legacy(&source) {
peer.v1_proto_receive(self, app, inner, time_ticks, &path, packet_header, packet.as_ref(), &[]);
} else {
self.whois(app, source, Some((Arc::downgrade(&path), packet)), time_ticks);
@ -707,7 +707,7 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
return;
}
if let Some(peer) = self.peer(&dest) {
if let Some(peer) = self.peers.get_legacy(&dest) {
if let Some(forward_path) = peer.direct_path() {
app.wire_send(
&forward_path.endpoint,
@ -729,10 +729,16 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
}
/// Enqueue and send a WHOIS query for a given address, adding the supplied packet (if any) to the list to be processed on reply.
fn whois(&self, app: &Application, address: Address, waiting_packet: Option<(Weak<Path<Application>>, PooledPacketBuffer)>, time_ticks: i64) {
fn whois(
&self,
app: &Application,
address: LegacyAddress,
waiting_packet: Option<(Weak<Path<Application>>, PooledPacketBuffer)>,
time_ticks: i64,
) {
{
let mut whois_queue = self.whois_queue.lock().unwrap();
let qi = whois_queue.entry(address.clone()).or_insert_with(|| WhoisQueueItem {
let qi = whois_queue.entry(address).or_insert_with(|| WhoisQueueItem {
v1_proto_waiting_packets: RingBuffer::new(),
last_retry_time: 0,
retry_count: 0,
@ -751,7 +757,7 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
}
/// Send a WHOIS query to the current best root.
fn send_whois(&self, app: &Application, mut addresses: &[Address], time_ticks: i64) {
fn send_whois(&self, app: &Application, mut addresses: &[LegacyAddress], time_ticks: i64) {
debug_assert!(!addresses.is_empty());
debug_event!(app, "[vl1] [v1] sending WHOIS for {}", {
let mut tmp = String::new();
@ -769,7 +775,7 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
.send(app, self, None, time_ticks, |packet| -> Result<(), Infallible> {
assert!(packet.append_u8(message_type::VL1_WHOIS).is_ok());
while !addresses.is_empty() && (packet.len() + ADDRESS_SIZE) <= UDP_DEFAULT_MTU {
assert!(packet.append_bytes_fixed(addresses[0].as_bytes_v1()).is_ok());
assert!(packet.append_bytes_fixed(addresses[0].as_bytes()).is_ok());
addresses = &addresses[1..];
}
Ok(())
@ -794,10 +800,10 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
if authoritative {
if let Some(received_identity) = received_identity.validate() {
let mut whois_queue = self.whois_queue.lock().unwrap();
if let Some(qi) = whois_queue.get_mut(&received_identity.address) {
let address = received_identity.address.clone();
if let Some(qi) = whois_queue.get_mut(&received_identity.address.legacy_address()) {
let address = received_identity.address.legacy_address();
/*
if app.should_respond_to(&received_identity) {
let mut peers = self.peers.write().unwrap();
if let Some(peer) = peers.get(&address).cloned().or_else(|| {
Peer::new(&self.identity_secret, received_identity, time_ticks)
.map(|p| Arc::new(p))
@ -813,6 +819,7 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
}
}
}
*/
whois_queue.remove(&address);
}
}

View file

@ -15,7 +15,6 @@ use zerotier_utils::memory::array_range;
use zerotier_utils::NEVER_HAPPENED_TICKS;
use crate::protocol::*;
use crate::vl1::address::Address;
use crate::vl1::debug_event;
use crate::vl1::identity::{Identity, IdentitySecret};
use crate::vl1::node::*;
@ -23,6 +22,8 @@ use crate::vl1::Valid;
use crate::vl1::{Endpoint, Path};
use crate::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
use super::LegacyAddress;
pub(crate) const SERVICE_INTERVAL_MS: i64 = 10000;
pub struct Peer<Application: ApplicationLayer + ?Sized> {
@ -327,7 +328,7 @@ impl<Application: ApplicationLayer + ?Sized> Peer<Application> {
aes_gmac_siv.encrypt_init(&self.v1_proto_next_message_id().to_be_bytes());
aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes(
&self.identity.address,
&node.identity.address,
&node.identity().address,
flags_cipher_hops,
));
let payload = packet.as_bytes_starting_at_mut(v1::HEADER_SIZE).unwrap();
@ -338,8 +339,8 @@ impl<Application: ApplicationLayer + ?Sized> Peer<Application> {
let header = packet.struct_mut_at::<v1::PacketHeader>(0).unwrap();
header.id.copy_from_slice(&tag[0..8]);
header.dest = *self.identity.address.as_bytes_v1();
header.src = *node.identity.address.as_bytes_v1();
header.dest = *self.identity.address.legacy_address().as_bytes();
header.src = *node.identity().address.legacy_address().as_bytes();
header.flags_cipher_hops = flags_cipher_hops;
header.mac.copy_from_slice(&tag[8..16]);
} else {
@ -355,8 +356,8 @@ impl<Application: ApplicationLayer + ?Sized> Peer<Application> {
{
let header = packet.struct_mut_at::<v1::PacketHeader>(0).unwrap();
header.id = self.v1_proto_next_message_id().to_be_bytes();
header.dest = *self.identity.address.as_bytes_v1();
header.src = *node.identity.address.as_bytes_v1();
header.dest = *self.identity.address.legacy_address().as_bytes();
header.src = *node.identity().address.legacy_address().as_bytes();
header.flags_cipher_hops = flags_cipher_hops;
header
},
@ -413,8 +414,8 @@ impl<Application: ApplicationLayer + ?Sized> Peer<Application> {
{
let f: &mut (v1::PacketHeader, v1::message_component_structs::HelloFixedHeaderFields) = packet.append_struct_get_mut().unwrap();
f.0.id = message_id.to_ne_bytes();
f.0.dest = *self.identity.address.as_bytes_v1();
f.0.src = *node.identity.address.as_bytes_v1();
f.0.dest = *self.identity.address.legacy_address().as_bytes();
f.0.src = *node.identity().address.legacy_address().as_bytes();
f.0.flags_cipher_hops = v1::CIPHER_NOCRYPT_POLY1305;
f.1.verb = message_type::VL1_HELLO;
f.1.version_proto = PROTOCOL_VERSION;
@ -425,7 +426,7 @@ impl<Application: ApplicationLayer + ?Sized> Peer<Application> {
}
debug_assert_eq!(packet.len(), 41);
assert!(node.identity.write_bytes(packet.as_mut(), !self.is_v2()).is_ok());
assert!(node.identity().write_bytes(packet.as_mut(), !self.is_v2()).is_ok());
let (_, poly1305_key) = v1_proto_salsa_poly_create(
&self.v1_proto_static_secret,
@ -770,8 +771,8 @@ impl<Application: ApplicationLayer + ?Sized> Peer<Application> {
if !self
.send(app, node, None, time_ticks, |packet| {
while addresses.len() >= ADDRESS_SIZE && (packet.len() + Identity::MAX_MARSHAL_SIZE) <= UDP_DEFAULT_MTU {
if let Some(zt_address) = Address::from_bytes_v1(&addresses[..ADDRESS_SIZE]) {
if let Some(peer) = node.peer(&zt_address) {
if let Some(zt_address) = LegacyAddress::from_bytes(&addresses[..ADDRESS_SIZE]) {
if let Some(peer) = node.peer_legacy(&zt_address) {
peer.identity.write_bytes(packet, !self.is_v2())?;
}
}

View file

@ -127,7 +127,7 @@ impl MulticastAuthority {
packet.append_u16(in_this_packet as u16)?;
for _ in 0..in_this_packet {
packet.append_bytes_fixed(gathered.pop().unwrap().as_bytes_v1())?;
packet.append_bytes_fixed(gathered.pop().unwrap().legacy_address().as_bytes())?;
}
Ok(())

View file

@ -22,7 +22,7 @@ impl NetworkId {
pub fn from_u64(i: u64) -> Option<NetworkId> {
// Note that we check both that 'i' is non-zero and that the address of the controller is valid.
if let Some(ii) = NonZeroU64::new(i) {
if Address::from_u64_v1(i).is_some() {
if Address::from_legacy_u64(i).is_some() {
return Some(Self(ii));
}
}
@ -31,7 +31,7 @@ impl NetworkId {
#[inline]
pub fn from_controller_and_network_no(controller: Address, network_no: u64) -> Option<NetworkId> {
Self::from_u64(controller.as_u64_v1().wrapping_shl(24) | (network_no & 0xffffff))
Self::from_u64(controller.to_legacy_u64().wrapping_shl(24) | (network_no & 0xffffff))
}
#[inline]
@ -56,12 +56,12 @@ impl NetworkId {
/// Get the network controller ID for this network, which is the most significant 40 bits.
#[inline]
pub fn network_controller(&self) -> Address {
Address::from_u64_v1(self.0.get()).unwrap()
Address::from_legacy_u64(self.0.get()).unwrap()
}
/// Consume this network ID and return one with the same network number but a different controller ID.
pub fn change_network_controller(self, new_controller: Address) -> NetworkId {
Self(NonZeroU64::new((self.network_no() as u64) | new_controller.as_u64_v1().wrapping_shl(24)).unwrap())
Self(NonZeroU64::new((self.network_no() as u64) | new_controller.to_legacy_u64().wrapping_shl(24)).unwrap())
}
/// Get the 24-bit local network identifier minus the 40-bit controller address portion.

View file

@ -10,7 +10,7 @@ use zerotier_utils::buffer::{Buffer, OutOfBoundsError};
use zerotier_utils::marshalable::{Marshalable, UnmarshalError};
use crate::protocol;
use crate::vl1::{Address, InetAddress, MAC};
use crate::vl1::{Address, InetAddress, LegacyAddress, MAC};
#[allow(unused)]
pub const RULES_ENGINE_REVISION: u8 = 1;
@ -174,16 +174,16 @@ impl Default for RuleValue {
pub trait RuleVisitor {
fn action_drop(&mut self) -> bool;
fn action_accept(&mut self) -> bool;
fn action_tee(&mut self, address: Address, flags: u32, length: u16) -> bool;
fn action_watch(&mut self, address: Address, flags: u32, length: u16) -> bool;
fn action_redirect(&mut self, address: Address, flags: u32, length: u16) -> bool;
fn action_tee(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool;
fn action_watch(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool;
fn action_redirect(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool;
fn action_break(&mut self) -> bool;
fn action_priority(&mut self, qos_bucket: u8) -> bool;
fn invalid_rule(&mut self) -> bool;
fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: Address);
fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: Address);
fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: LegacyAddress);
fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: LegacyAddress);
fn match_vlan_id(&mut self, not: bool, or: bool, id: u16);
fn match_vlan_pcp(&mut self, not: bool, or: bool, pcp: u8);
fn match_vlan_dei(&mut self, not: bool, or: bool, dei: u8);
@ -239,7 +239,7 @@ impl Rule {
Self {
t: action::TEE,
v: RuleValue {
forward: rule_value::Forward { address: address.as_u64_v1(), flags, length },
forward: rule_value::Forward { address: address.legacy_address().to_u64(), flags, length },
},
}
}
@ -248,7 +248,7 @@ impl Rule {
Self {
t: action::TEE,
v: RuleValue {
forward: rule_value::Forward { address: address.as_u64_v1(), flags, length },
forward: rule_value::Forward { address: address.legacy_address().to_u64(), flags, length },
},
}
}
@ -257,7 +257,7 @@ impl Rule {
Self {
t: action::TEE,
v: RuleValue {
forward: rule_value::Forward { address: address.as_u64_v1(), flags, length },
forward: rule_value::Forward { address: address.legacy_address().to_u64(), flags, length },
},
}
}
@ -273,14 +273,14 @@ impl Rule {
pub fn match_source_zerotier_address(not: bool, or: bool, address: Address) -> Self {
Self {
t: t(not, or, match_cond::SOURCE_ZEROTIER_ADDRESS),
v: RuleValue { zt: address.as_u64_v1() },
v: RuleValue { zt: address.legacy_address().to_u64() },
}
}
pub fn match_dest_zerotier_address(not: bool, or: bool, address: Address) -> Self {
Self {
t: t(not, or, match_cond::DEST_ZEROTIER_ADDRESS),
v: RuleValue { zt: address.as_u64_v1() },
v: RuleValue { zt: address.legacy_address().to_u64() },
}
}
@ -306,21 +306,21 @@ impl Rule {
return v.action_accept();
}
action::TEE => {
if let Some(a) = Address::from_u64_v1(self.v.forward.address) {
if let Some(a) = LegacyAddress::from_u64(self.v.forward.address) {
return v.action_tee(a, self.v.forward.flags, self.v.forward.length);
} else {
return v.invalid_rule();
}
}
action::WATCH => {
if let Some(a) = Address::from_u64_v1(self.v.forward.address) {
if let Some(a) = LegacyAddress::from_u64(self.v.forward.address) {
return v.action_watch(a, self.v.forward.flags, self.v.forward.length);
} else {
return v.invalid_rule();
}
}
action::REDIRECT => {
if let Some(a) = Address::from_u64_v1(self.v.forward.address) {
if let Some(a) = LegacyAddress::from_u64(self.v.forward.address) {
return v.action_redirect(a, self.v.forward.flags, self.v.forward.length);
} else {
return v.invalid_rule();
@ -333,14 +333,14 @@ impl Rule {
return v.action_priority(self.v.qos_bucket);
}
match_cond::SOURCE_ZEROTIER_ADDRESS => {
if let Some(a) = Address::from_u64_v1(self.v.zt) {
if let Some(a) = LegacyAddress::from_u64(self.v.zt) {
v.match_source_zerotier_address(not, or, a);
} else {
return v.invalid_rule();
}
}
match_cond::DEST_ZEROTIER_ADDRESS => {
if let Some(a) = Address::from_u64_v1(self.v.zt) {
if let Some(a) = LegacyAddress::from_u64(self.v.zt) {
v.match_dest_zerotier_address(not, or, a);
} else {
return v.invalid_rule();
@ -775,13 +775,13 @@ static HR_NAME_TO_RULE_TYPE: phf::Map<&'static str, u8> = phf_map! {
#[derive(Default, Serialize, Deserialize)]
struct HumanReadableRule<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<Address>,
pub address: Option<LegacyAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
pub flags: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub length: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub zt: Option<Address>,
pub zt: Option<LegacyAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vlanId: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
@ -837,7 +837,7 @@ impl<'a> HumanReadableRule<'a> {
unsafe {
match *t {
action::TEE | action::WATCH | action::REDIRECT => {
r.v.forward.address = self.address.as_ref()?.as_u64_v1();
r.v.forward.address = self.address.as_ref()?.to_u64();
r.v.forward.flags = self.flags?;
r.v.forward.length = self.length?;
}
@ -845,7 +845,7 @@ impl<'a> HumanReadableRule<'a> {
r.v.qos_bucket = self.qosBucket?;
}
match_cond::SOURCE_ZEROTIER_ADDRESS | match_cond::DEST_ZEROTIER_ADDRESS => {
r.v.zt = self.address.as_ref()?.as_u64_v1();
r.v.zt = self.address.as_ref()?.to_u64();
}
match_cond::VLAN_ID => {
r.v.vlan_id = self.vlanId?;
@ -982,7 +982,7 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> {
}
#[inline(always)]
fn action_tee(&mut self, address: Address, flags: u32, length: u16) -> bool {
fn action_tee(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool {
self.0._type = "ACTION_TEE";
let _ = self.0.address.insert(address);
let _ = self.0.flags.insert(flags);
@ -991,7 +991,7 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> {
}
#[inline(always)]
fn action_watch(&mut self, address: Address, flags: u32, length: u16) -> bool {
fn action_watch(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool {
self.0._type = "ACTION_WATCH";
let _ = self.0.address.insert(address);
let _ = self.0.flags.insert(flags);
@ -1000,7 +1000,7 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> {
}
#[inline(always)]
fn action_redirect(&mut self, address: Address, flags: u32, length: u16) -> bool {
fn action_redirect(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool {
self.0._type = "ACTION_REDIRECT";
let _ = self.0.address.insert(address);
let _ = self.0.flags.insert(flags);
@ -1027,13 +1027,13 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> {
}
#[inline(always)]
fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: Address) {
fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: LegacyAddress) {
let _ = self.0.zt.insert(address);
self.do_cond("MATCH_SOURCE_ZEROTIER_ADDRESS", not, or);
}
#[inline(always)]
fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: Address) {
fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: LegacyAddress) {
let _ = self.0.zt.insert(address);
self.do_cond("MATCH_DEST_ZEROTIER_ADDRESS", not, or);
}
@ -1217,19 +1217,19 @@ impl RuleVisitor for RuleStringer {
}
#[inline(always)]
fn action_tee(&mut self, address: Address, flags: u32, length: u16) -> bool {
fn action_tee(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool {
self.0 = format!("ACTION_TEE({}, {}, {})", address.to_string(), flags, length);
true
}
#[inline(always)]
fn action_watch(&mut self, address: Address, flags: u32, length: u16) -> bool {
fn action_watch(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool {
self.0 = format!("ACTION_WATCH({}, {}, {})", address.to_string(), flags, length);
true
}
#[inline(always)]
fn action_redirect(&mut self, address: Address, flags: u32, length: u16) -> bool {
fn action_redirect(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool {
self.0 = format!("ACTION_REDIRECT({}, {}, {})", address.to_string(), flags, length);
true
}
@ -1253,7 +1253,7 @@ impl RuleVisitor for RuleStringer {
}
#[inline(always)]
fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: Address) {
fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: LegacyAddress) {
self.0 = format!(
"MATCH_SOURCE_ZEROTIER_ADDRESS({}{}{})",
if or {
@ -1271,7 +1271,7 @@ impl RuleVisitor for RuleStringer {
}
#[inline(always)]
fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: Address) {
fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: LegacyAddress) {
self.0 = format!(
"MATCH_DEST_ZEROTIER_ADDRESS({}{}{})",
if or {

View file

@ -1,7 +1,7 @@
use std::io::Write;
use crate::vl1::identity::{Identity, IdentitySecret};
use crate::vl1::Address;
use crate::vl1::LegacyAddress;
use crate::vl2::NetworkId;
use serde::{Deserialize, Serialize};
@ -27,7 +27,7 @@ pub struct CertificateOfMembership {
pub network_id: NetworkId,
pub timestamp: i64,
pub max_delta: u64,
pub issued_to: Address,
pub issued_to: LegacyAddress,
pub issued_to_fingerprint: Blob<32>,
pub signature: ArrayVec<u8, { Identity::MAX_SIGNATURE_SIZE }>,
}
@ -40,7 +40,7 @@ impl CertificateOfMembership {
network_id,
timestamp,
max_delta,
issued_to: issued_to.address.clone(),
issued_to: issued_to.address.legacy_address(),
issued_to_fingerprint: Blob::default(),
signature: ArrayVec::new(),
};
@ -59,7 +59,7 @@ impl CertificateOfMembership {
q[4] = u64::from(self.network_id).to_be();
q[5] = 0; // no disagreement permitted
q[6] = 2u64.to_be();
q[7] = self.issued_to.as_u64_v1().to_be();
q[7] = self.issued_to.to_u64().to_be();
q[8] = u64::MAX; // no to_be needed for all-1s
// This is a fix for a security issue in V1 in which an attacker could (with much CPU use)
@ -86,20 +86,20 @@ impl CertificateOfMembership {
/// Get the identity fingerprint used in V1, which only covers the curve25519 keys.
fn v1_proto_issued_to_fingerprint(issued_to: &Identity) -> [u8; 32] {
let mut v1_signee_hasher = SHA384::new();
v1_signee_hasher.update(issued_to.address.as_bytes_v1());
v1_signee_hasher.update(issued_to.address.legacy_address().as_bytes());
v1_signee_hasher.update(&issued_to.x25519.ecdh);
v1_signee_hasher.update(&issued_to.x25519.eddsa);
(&v1_signee_hasher.finish()[..32]).try_into().unwrap()
}
/// Get this certificate of membership in byte encoded format.
pub fn to_bytes(&self, controller_address: Address) -> ArrayVec<u8, 384> {
pub fn to_bytes(&self, controller_address: LegacyAddress) -> ArrayVec<u8, 384> {
let mut v = ArrayVec::new();
v.push(1); // version byte from v1 protocol
v.push(0);
v.push(7); // 7 qualifiers, big-endian 16-bit
let _ = v.write_all(&self.v1_proto_get_qualifier_bytes());
let _ = v.write_all(controller_address.as_bytes_v1());
let _ = v.write_all(controller_address.as_bytes());
let _ = v.write_all(self.signature.as_bytes());
v
}
@ -155,7 +155,7 @@ impl CertificateOfMembership {
network_id: NetworkId::from_u64(network_id).ok_or(InvalidParameterError("invalid network ID"))?,
timestamp,
max_delta,
issued_to: Address::from_u64_v1(issued_to).ok_or(InvalidParameterError("invalid issued to address"))?,
issued_to: LegacyAddress::from_u64(issued_to).ok_or(InvalidParameterError("invalid issued to address"))?,
issued_to_fingerprint: Blob::from(v1_fingerprint),
signature: {
let mut s = ArrayVec::new();

View file

@ -2,7 +2,7 @@ use std::collections::HashSet;
use std::io::Write;
use crate::vl1::identity::{Identity, IdentitySecret};
use crate::vl1::{Address, InetAddress, MAC};
use crate::vl1::{InetAddress, LegacyAddress, MAC};
use crate::vl2::NetworkId;
use serde::{Deserialize, Serialize};
@ -33,13 +33,13 @@ pub struct CertificateOfOwnership {
pub network_id: NetworkId,
pub timestamp: i64,
pub things: HashSet<Thing>,
pub issued_to: Address,
pub issued_to: LegacyAddress,
pub signature: ArrayVec<u8, { Identity::MAX_SIGNATURE_SIZE }>,
}
impl CertificateOfOwnership {
/// Create a new empty and unsigned certificate.
pub fn new(network_id: NetworkId, timestamp: i64, issued_to: Address) -> Self {
pub fn new(network_id: NetworkId, timestamp: i64, issued_to: LegacyAddress) -> Self {
Self {
network_id,
timestamp,
@ -63,7 +63,7 @@ impl CertificateOfOwnership {
let _ = self.things.insert(Thing::Mac(mac));
}
fn internal_to_bytes(&self, for_sign: bool, signed_by: &Address) -> Option<Vec<u8>> {
fn internal_to_bytes(&self, for_sign: bool, signed_by: LegacyAddress) -> Option<Vec<u8>> {
if self.things.len() > 0xffff {
return None;
}
@ -94,8 +94,8 @@ impl CertificateOfOwnership {
}
}
}
let _ = v.write_all(self.issued_to.as_bytes_v1());
let _ = v.write_all(signed_by.as_bytes_v1());
let _ = v.write_all(self.issued_to.as_bytes());
let _ = v.write_all(signed_by.as_bytes());
if for_sign {
v.push(0);
v.push(0);
@ -112,7 +112,7 @@ impl CertificateOfOwnership {
}
#[inline(always)]
pub fn to_bytes(&self, signed_by: &Address) -> Option<Vec<u8>> {
pub fn to_bytes(&self, signed_by: LegacyAddress) -> Option<Vec<u8>> {
self.internal_to_bytes(false, signed_by)
}
@ -156,7 +156,7 @@ impl CertificateOfOwnership {
network_id: NetworkId::from_u64(network_id).ok_or(InvalidParameterError("invalid network ID"))?,
timestamp,
things,
issued_to: Address::from_bytes_v1(&b[..5]).ok_or(InvalidParameterError("invalid address"))?,
issued_to: LegacyAddress::from_bytes(&b[..5]).ok_or(InvalidParameterError("invalid address"))?,
signature: {
let mut s = ArrayVec::new();
s.push_slice(&b[13..109]);
@ -168,8 +168,8 @@ impl CertificateOfOwnership {
}
/// Sign certificate of ownership for use by V1 nodes.
pub fn sign(&mut self, issuer_address: &Address, issuer: &IdentitySecret, issued_to: &Identity) -> bool {
self.issued_to = issued_to.address.clone();
pub fn sign(&mut self, issuer_address: LegacyAddress, issuer: &IdentitySecret, issued_to: &Identity) -> bool {
self.issued_to = issued_to.address.legacy_address();
if let Some(to_sign) = self.internal_to_bytes(true, issuer_address) {
self.signature = issuer.sign(&to_sign.as_slice());
return true;

View file

@ -175,7 +175,7 @@ impl NetworkConfig {
proto_v1_field_name::network_config::CERTIFICATE_OF_MEMBERSHIP,
v1cred
.certificate_of_membership
.to_bytes(self.network_id.network_controller())
.to_bytes(self.network_id.network_controller().legacy_address())
.as_bytes()
.to_vec(),
);
@ -183,7 +183,7 @@ impl NetworkConfig {
if !v1cred.certificates_of_ownership.is_empty() {
let mut certs = Vec::with_capacity(v1cred.certificates_of_ownership.len() * 256);
for c in v1cred.certificates_of_ownership.iter() {
let _ = certs.write_all(c.to_bytes(&controller_identity.address)?.as_slice());
let _ = certs.write_all(c.to_bytes(controller_identity.address.legacy_address())?.as_slice());
}
d.set_bytes(proto_v1_field_name::network_config::CERTIFICATES_OF_OWNERSHIP, certs);
}
@ -191,7 +191,7 @@ impl NetworkConfig {
if !v1cred.tags.is_empty() {
let mut tags = Vec::with_capacity(v1cred.tags.len() * 256);
for (_, t) in v1cred.tags.iter() {
let _ = tags.write_all(t.to_bytes(&controller_identity.address).as_ref());
let _ = tags.write_all(t.to_bytes(controller_identity.address.legacy_address()).as_ref());
}
d.set_bytes(proto_v1_field_name::network_config::TAGS, tags);
}

View file

@ -5,7 +5,7 @@ use zerotier_utils::arrayvec::ArrayVec;
use serde::{Deserialize, Serialize};
use crate::vl1::identity::IdentitySecret;
use crate::vl1::Address;
use crate::vl1::LegacyAddress;
use crate::vl2::v1::CredentialType;
use crate::vl2::NetworkId;
@ -14,8 +14,8 @@ use crate::vl2::NetworkId;
pub struct Revocation {
pub network_id: NetworkId,
pub threshold: i64,
pub target: Address,
pub issued_to: Address,
pub target: LegacyAddress,
pub issued_to: LegacyAddress,
pub signature: ArrayVec<u8, 96>,
pub fast_propagate: bool,
}
@ -24,9 +24,9 @@ impl Revocation {
pub fn new(
network_id: NetworkId,
threshold: i64,
target: Address,
issued_to: Address,
signer_address: &Address,
target: LegacyAddress,
issued_to: LegacyAddress,
signer_address: LegacyAddress,
signer: &IdentitySecret,
fast_propagate: bool,
) -> Self {
@ -42,20 +42,20 @@ impl Revocation {
r
}
fn internal_to_bytes(&self, for_sign: bool, signed_by: &Address) -> ArrayVec<u8, 256> {
fn internal_to_bytes(&self, for_sign: bool, signed_by: LegacyAddress) -> ArrayVec<u8, 256> {
let mut v = ArrayVec::new();
if for_sign {
let _ = v.write_all(&[0x7f; 8]);
}
let _ = v.write_all(&[0; 4]);
let _ = v.write_all(&((self.threshold as u32) ^ (self.target.as_u64_v1() as u32)).to_be_bytes()); // ID only used in V1, arbitrary
let _ = v.write_all(&((self.threshold as u32) ^ (self.target.to_u64() as u32)).to_be_bytes()); // ID only used in V1, arbitrary
let _ = v.write_all(&self.network_id.to_bytes());
let _ = v.write_all(&[0; 8]);
let _ = v.write_all(&self.threshold.to_be_bytes());
let _ = v.write_all(&(self.fast_propagate as u64).to_be_bytes()); // 0x1 is the flag for this
let _ = v.write_all(self.target.as_bytes_v1());
let _ = v.write_all(signed_by.as_bytes_v1());
let _ = v.write_all(self.target.as_bytes());
let _ = v.write_all(signed_by.as_bytes());
v.push(CredentialType::CertificateOfMembership as u8);
if for_sign {
@ -71,7 +71,7 @@ impl Revocation {
}
#[inline(always)]
pub fn v1_proto_to_bytes(&self, controller_address: &Address) -> ArrayVec<u8, 256> {
pub fn v1_proto_to_bytes(&self, controller_address: LegacyAddress) -> ArrayVec<u8, 256> {
self.internal_to_bytes(false, controller_address)
}
}

View file

@ -1,7 +1,7 @@
use std::io::Write;
use crate::vl1::identity::{Identity, IdentitySecret};
use crate::vl1::Address;
use crate::vl1::LegacyAddress;
use crate::vl2::NetworkId;
use serde::{Deserialize, Serialize};
@ -14,7 +14,7 @@ use zerotier_utils::error::InvalidParameterError;
pub struct Tag {
pub network_id: NetworkId,
pub timestamp: i64,
pub issued_to: Address,
pub issued_to: LegacyAddress,
pub id: u32,
pub value: u32,
pub signature: Blob<96>,
@ -24,7 +24,7 @@ impl Tag {
pub fn new(
id: u32,
value: u32,
issuer_address: &Address,
issuer_address: LegacyAddress,
issuer: &IdentitySecret,
network_id: NetworkId,
issued_to: &Identity,
@ -33,7 +33,7 @@ impl Tag {
let mut tag = Self {
network_id,
timestamp,
issued_to: issued_to.address.clone(),
issued_to: issued_to.address.legacy_address(),
id,
value,
signature: Blob::default(),
@ -43,7 +43,7 @@ impl Tag {
tag
}
fn internal_to_bytes(&self, for_sign: bool, signed_by: &Address) -> ArrayVec<u8, 256> {
fn internal_to_bytes(&self, for_sign: bool, signed_by: LegacyAddress) -> ArrayVec<u8, 256> {
let mut v = ArrayVec::new();
if for_sign {
let _ = v.write_all(&[0x7f; 8]);
@ -52,8 +52,8 @@ impl Tag {
let _ = v.write_all(&self.timestamp.to_be_bytes());
let _ = v.write_all(&self.id.to_be_bytes());
let _ = v.write_all(&self.value.to_be_bytes());
let _ = v.write_all(self.issued_to.as_bytes_v1());
let _ = v.write_all(signed_by.as_bytes_v1());
let _ = v.write_all(self.issued_to.as_bytes());
let _ = v.write_all(signed_by.as_bytes());
if !for_sign {
v.push(1);
v.push(0);
@ -69,7 +69,7 @@ impl Tag {
}
#[inline(always)]
pub fn to_bytes(&self, signed_by: &Address) -> ArrayVec<u8, 256> {
pub fn to_bytes(&self, signed_by: LegacyAddress) -> ArrayVec<u8, 256> {
self.internal_to_bytes(false, signed_by)
}
@ -82,7 +82,7 @@ impl Tag {
Self {
network_id: NetworkId::from_bytes(&b[0..8]).ok_or(InvalidParameterError("invalid network ID"))?,
timestamp: i64::from_be_bytes(b[8..16].try_into().unwrap()),
issued_to: Address::from_bytes_v1(&b[24..29]).ok_or(InvalidParameterError("invalid address"))?,
issued_to: LegacyAddress::from_bytes(&b[24..29]).ok_or(InvalidParameterError("invalid address"))?,
id: u32::from_be_bytes(b[16..20].try_into().unwrap()),
value: u32::from_be_bytes(b[20..24].try_into().unwrap()),
signature: {

View file

@ -22,6 +22,7 @@ pub mod io;
pub mod json;
pub mod marshalable;
pub mod memory;
pub mod oneormore;
pub mod pool;
pub mod proquint;
#[cfg(feature = "tokio")]

View file

@ -15,6 +15,9 @@ use std::mem::{needs_drop, size_of, MaybeUninit};
#[allow(unused_imports)]
use std::ptr::copy_nonoverlapping;
/// Implement this trait to mark a struct as safe to cast from a byte array.
pub unsafe trait FlatBuffer: Sized {}
/// Store a raw object to a byte array (for architectures known not to care about unaligned access).
/// This will panic if the slice is too small or the object requires drop.
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64"))]
@ -79,7 +82,7 @@ pub fn array_chunks_exact<T, const S: usize>(a: &[T]) -> impl Iterator<Item = &[
/// Obtain a view into an array cast as another array.
/// This will panic if the template parameters would result in out of bounds access.
#[inline(always)]
pub fn array_range<T: Copy, const S: usize, const START: usize, const LEN: usize>(a: &[T; S]) -> &[T; LEN] {
pub fn array_range<T, const S: usize, const START: usize, const LEN: usize>(a: &[T; S]) -> &[T; LEN] {
assert!((START + LEN) <= S);
unsafe { &*a.as_ptr().add(START).cast::<[T; LEN]>() }
}
@ -108,3 +111,11 @@ pub fn to_byte_array<T: Copy, const S: usize>(o: T) -> [u8; S] {
assert!(!std::mem::needs_drop::<T>());
unsafe { *(&o as *const T).cast() }
}
/// Cast a byte slice into a flat struct.
/// This will panic if the slice is too small or the struct requires drop.
pub fn cast_to_struct<T: FlatBuffer>(b: &[u8]) -> &T {
assert!(b.len() >= size_of::<T>());
assert!(!std::mem::needs_drop::<T>());
unsafe { &*b.as_ptr().cast() }
}

View file

@ -1,237 +0,0 @@
Name: zerotier-one
Version: 1.10.2
Release: 1%{?dist}
Summary: ZeroTier network virtualization service
License: ZeroTier BSL 1.1
URL: https://www.zerotier.com
# Fedora
%if "%{?dist}" == ".fc35"
BuildRequires: systemd clang openssl openssl-devel
Requires: systemd openssl
Requires(pre): /usr/sbin/useradd, /usr/bin/getent
%endif
%if "%{?dist}" == ".fc36"
BuildRequires: systemd clang openssl1.1 openssl1.1-devel
Requires: systemd openssl1.1
Requires(pre): /usr/sbin/useradd, /usr/bin/getent
%endif
%if "%{?dist}" == ".fc37"
BuildRequires: systemd clang openssl1.1 openssl1.1-devel
Requires: systemd openssl1.1
Requires(pre): /usr/sbin/useradd, /usr/bin/getent
%endif
# RHEL
%if "%{?dist}" == ".el6"
Requires: chkconfig
Requires(pre): /usr/sbin/useradd, /usr/bin/getent
%endif
%if "%{?dist}" == ".el7"
BuildRequires: systemd openssl-devel
Requires: systemd openssl
Requires(pre): /usr/sbin/useradd, /usr/bin/getent
%endif
%if "%{?dist}" == ".el8"
BuildRequires: systemd openssl-devel
Requires: systemd openssl
Requires(pre): /usr/sbin/useradd, /usr/bin/getent
%endif
%if "%{?dist}" == ".el9"
BuildRequires: systemd openssl-devel
Requires: systemd openssl
Requires(pre): /usr/sbin/useradd, /usr/bin/getent
%endif
# Amazon
%if "%{?dist}" == ".amzn2"
BuildRequires: systemd openssl-devel
Requires: systemd openssl
Requires(pre): /usr/sbin/useradd, /usr/bin/getent
%endif
%if "%{?dist}" == ".amzn2022"
BuildRequires: systemd openssl-devel
Requires: systemd openssl
Requires(pre): /usr/sbin/useradd, /usr/bin/getent
%endif
%description
ZeroTier is a software defined networking layer for Earth.
It can be used for on-premise network virtualization, as a peer to peer VPN
for mobile teams, for hybrid or multi-data-center cloud deployments, or just
about anywhere else secure software defined virtual networking is useful.
This is our OS-level client service. It allows Mac, Linux, Windows,
FreeBSD, and soon other types of clients to join ZeroTier virtual networks
like conventional VPNs or VLANs. It can run on native systems, VMs, or
containers (Docker, OpenVZ, etc.).
%prep
%if "%{?dist}" != ".el6"
rm -rf BUILD BUILDROOT RPMS SRPMS SOURCES
ln -s %{getenv:PWD} %{name}-%{version}
mkdir -p SOURCES
tar --exclude=%{name}-%{version}/.git --exclude=%{name}-%{version}/%{name}-%{version} -czf SOURCES/%{name}-%{version}.tar.gz %{name}-%{version}/*
rm -f %{name}-%{version}
cp -a %{getenv:PWD}/* .
%endif
%build
%if "%{?dist}" != ".el6"
make ZT_USE_MINIUPNPC=1 %{?_smp_mflags} one
%endif
%pre
/usr/bin/getent passwd zerotier-one || /usr/sbin/useradd -r -d /var/lib/zerotier-one -s /sbin/nologin zerotier-one
%install
%if "%{?dist}" != ".el6"
make install DESTDIR=$RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT%{_unitdir}
cp %{getenv:PWD}/debian/zerotier-one.service $RPM_BUILD_ROOT%{_unitdir}/%{name}.service
%else
rm -rf $RPM_BUILD_ROOT
pushd %{getenv:PWD}
make install DESTDIR=$RPM_BUILD_ROOT
popd
mkdir -p $RPM_BUILD_ROOT/etc/init.d
cp %{getenv:PWD}/ext/installfiles/linux/zerotier-one.init.rhel6 $RPM_BUILD_ROOT/etc/init.d/zerotier-one
chmod 0755 $RPM_BUILD_ROOT/etc/init.d/zerotier-one
%endif
%files
%{_sbindir}/*
%{_mandir}/*
%{_localstatedir}/*
%if 0%{?rhel} && 0%{?rhel} <= 6
/etc/init.d/zerotier-one
%else
%{_unitdir}/%{name}.service
%endif
%post
%if ! 0%{?rhel} && 0%{?rhel} <= 6
%systemd_post zerotier-one.service
%endif
%preun
%if ! 0%{?rhel} && 0%{?rhel} <= 6
%systemd_preun zerotier-one.service
%endif
%postun
%if ! 0%{?rhel} && 0%{?rhel} <= 6
%systemd_postun_with_restart zerotier-one.service
%endif
%changelog
* Mon Oct 13 2022 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.10.2
- see https://github.com/zerotier/ZeroTierOne for release notes
* Mon Jun 27 2022 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.10.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Fri Jun 03 2022 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.10.0
- see https://github.com/zerotier/ZeroTierOne for release notes
* Tue May 10 2022 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.8.10
- see https://github.com/zerotier/ZeroTierOne for release notes
* Mon Apr 25 2022 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.8.9
- see https://github.com/zerotier/ZeroTierOne for release notes
* Mon Apr 11 2022 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.8.8
- see https://github.com/zerotier/ZeroTierOne for release notes
* Mon Mar 21 2022 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.8.7
- see https://github.com/zerotier/ZeroTierOne for release notes
* Mon Mar 07 2022 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.8.6
- see https://github.com/zerotier/ZeroTierOne for release notes
* Fri Dec 17 2021 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.8.5
- see https://github.com/zerotier/ZeroTierOne for release notes
* Tue Nov 23 2021 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.8.4
- see https://github.com/zerotier/ZeroTierOne for release notes
* Mon Nov 15 2021 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.8.3
- see https://github.com/zerotier/ZeroTierOne for release notes
* Mon Nov 08 2021 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.8.2
- see https://github.com/zerotier/ZeroTierOne for release notes
* Wed Oct 20 2021 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.8.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Wed Sep 15 2021 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.8.0
- see https://github.com/zerotier/ZeroTierOne for release notes
* Tue Apr 13 2021 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.6.5
- see https://github.com/zerotier/ZeroTierOne for release notes
* Mon Feb 15 2021 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.6.4
- see https://github.com/zerotier/ZeroTierOne for release notes
* Mon Nov 30 2020 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.6.2-0.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Tue Nov 24 2020 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.6.1-0.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Thu Nov 19 2020 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.6.0-0.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Mon Oct 05 2020 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.6.0-beta1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Fri Aug 23 2019 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.4.4-0.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Mon Jul 29 2019 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.4.0-0.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Tue May 08 2018 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.2.10-0.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Thu May 03 2018 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.2.8-0.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Mon Apr 24 2017 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.2.2-0.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Fri Mar 17 2017 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.2.2-0.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Tue Mar 14 2017 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.2.0-0.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Tue Jul 12 2016 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.1.10-0.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Fri Jul 08 2016 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.1.8-0.1
- see https://github.com/zerotier/ZeroTierOne for release notes
* Sat Jun 25 2016 Adam Ierymenko <adam.ierymenko@zerotier.com> - 1.1.6-0.1
- now builds on CentOS 6 as well as newer distros, and some cleanup
* Wed Jun 08 2016 François Kooman <fkooman@tuxed.net> - 1.1.5-0.3
- include systemd unit file
* Wed Jun 08 2016 François Kooman <fkooman@tuxed.net> - 1.1.5-0.2
- add libnatpmp as (build)dependency
* Wed Jun 08 2016 François Kooman <fkooman@tuxed.net> - 1.1.5-0.1
- initial package