mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-26 17:03:43 +02:00
Create a root set for our current roots, and it works!!!!
This commit is contained in:
parent
083e2bc666
commit
a4db105f23
7 changed files with 368 additions and 44 deletions
|
@ -8,6 +8,10 @@
|
|||
|
||||
use crate::util::buffer::Buffer;
|
||||
|
||||
/// Must be larger than any object we want to use with to_bytes() or from_bytes().
|
||||
/// This hack can go away once Rust allows us to reference trait consts as generics.
|
||||
const TEMP_BUF_SIZE: usize = 131072;
|
||||
|
||||
/// A super-lightweight zero-allocation serialization interface.
|
||||
pub trait Marshalable: Sized {
|
||||
const MAX_MARSHAL_SIZE: usize;
|
||||
|
@ -26,7 +30,6 @@ pub trait Marshalable: Sized {
|
|||
///
|
||||
/// This will return an Err if the buffer is too small or some other error occurs. It's just
|
||||
/// a shortcut to creating a buffer and marshaling into it.
|
||||
#[inline(always)]
|
||||
fn to_buffer<const BL: usize>(&self) -> std::io::Result<Buffer<BL>> {
|
||||
assert!(BL >= Self::MAX_MARSHAL_SIZE);
|
||||
let mut tmp = Buffer::new();
|
||||
|
@ -37,9 +40,28 @@ pub trait Marshalable: Sized {
|
|||
/// Unmarshal this object from a buffer.
|
||||
///
|
||||
/// This is just a shortcut to calling unmarshal() with a zero cursor and then discarding the cursor.
|
||||
#[inline(always)]
|
||||
fn from_buffer<const BL: usize>(buf: &Buffer<BL>) -> std::io::Result<Self> {
|
||||
let mut tmp = 0;
|
||||
Self::unmarshal(buf, &mut tmp)
|
||||
}
|
||||
|
||||
/// Marshal and convert to a Rust vector.
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE);
|
||||
let mut tmp = Buffer::<TEMP_BUF_SIZE>::new_boxed();
|
||||
assert!(self.marshal(&mut tmp).is_ok());
|
||||
tmp.as_bytes().to_vec()
|
||||
}
|
||||
|
||||
/// Unmarshal from a raw slice.
|
||||
fn from_bytes(b: &[u8]) -> std::io::Result<Self> {
|
||||
if b.len() <= TEMP_BUF_SIZE {
|
||||
let mut tmp = Buffer::<TEMP_BUF_SIZE>::new_boxed();
|
||||
assert!(tmp.append_bytes(b).is_ok());
|
||||
let mut cursor = 0;
|
||||
Self::unmarshal(&tmp, &mut cursor)
|
||||
} else {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "object too large"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
9
zerotier-system-service/src/cli/mod.rs
Normal file
9
zerotier-system-service/src/cli/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c)2021 ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
pub mod rootset;
|
118
zerotier-system-service/src/cli/rootset.rs
Normal file
118
zerotier-system-service/src/cli/rootset.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* (c)2021 ZeroTier, Inc.
|
||||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
||||
use crate::{exitcode, Flags};
|
||||
|
||||
use zerotier_network_hypervisor::util::marshalable::Marshalable;
|
||||
use zerotier_network_hypervisor::vl1::RootSet;
|
||||
|
||||
pub async fn cmd(flags: Flags, cmd_args: &ArgMatches) -> i32 {
|
||||
match cmd_args.subcommand() {
|
||||
Some(("trust", sc_args)) => todo!(),
|
||||
|
||||
Some(("untrust", sc_args)) => todo!(),
|
||||
|
||||
Some(("list", _)) => todo!(),
|
||||
|
||||
Some(("sign", sc_args)) => {
|
||||
let path = sc_args.value_of("path");
|
||||
let secret_arg = sc_args.value_of("secret");
|
||||
if path.is_some() && secret_arg.is_some() {
|
||||
let path = path.unwrap();
|
||||
let secret_arg = secret_arg.unwrap();
|
||||
let secret = crate::utils::parse_cli_identity(secret_arg, true).await;
|
||||
let json_data = crate::utils::read_limit(path, 1048576).await;
|
||||
if secret.is_err() {
|
||||
eprintln!("ERROR: unable to parse '{}' or read as a file.", secret_arg);
|
||||
return exitcode::ERR_IOERR;
|
||||
}
|
||||
let secret = secret.unwrap();
|
||||
if !secret.secret.is_some() {
|
||||
eprintln!("ERROR: identity does not include secret key, which is required for signing.");
|
||||
return exitcode::ERR_IOERR;
|
||||
}
|
||||
if json_data.is_err() {
|
||||
eprintln!("ERROR: unable to read '{}'.", path);
|
||||
return exitcode::ERR_IOERR;
|
||||
}
|
||||
let json_data = json_data.unwrap();
|
||||
let root_set = serde_json::from_slice::<RootSet>(json_data.as_slice());
|
||||
if root_set.is_err() {
|
||||
eprintln!("ERROR: root set JSON parsing failed: {}", root_set.err().unwrap().to_string());
|
||||
return exitcode::ERR_IOERR;
|
||||
}
|
||||
let mut root_set = root_set.unwrap();
|
||||
if !root_set.sign(&secret) {
|
||||
eprintln!("ERROR: root set signing failed, invalid identity?");
|
||||
return exitcode::ERR_INTERNAL;
|
||||
}
|
||||
println!("{}", crate::utils::to_json_pretty(&root_set));
|
||||
} else {
|
||||
eprintln!("ERROR: 'rootset sign' requires a path to a root set in JSON format and a secret identity.");
|
||||
return exitcode::ERR_IOERR;
|
||||
}
|
||||
}
|
||||
|
||||
Some(("verify", sc_args)) => {
|
||||
let path = sc_args.value_of("path");
|
||||
if path.is_some() {
|
||||
let path = path.unwrap();
|
||||
let json_data = crate::utils::read_limit(path, 1048576).await;
|
||||
if json_data.is_err() {
|
||||
eprintln!("ERROR: unable to read '{}'.", path);
|
||||
return exitcode::ERR_IOERR;
|
||||
}
|
||||
let json_data = json_data.unwrap();
|
||||
let root_set = serde_json::from_slice::<RootSet>(json_data.as_slice());
|
||||
if root_set.is_err() {
|
||||
eprintln!("ERROR: root set JSON parsing failed: {}", root_set.err().unwrap().to_string());
|
||||
return exitcode::ERR_IOERR;
|
||||
}
|
||||
let root_set = root_set.unwrap();
|
||||
if root_set.verify() {
|
||||
println!("OK");
|
||||
} else {
|
||||
println!("FAILED");
|
||||
return exitcode::ERR_DATA_FORMAT;
|
||||
}
|
||||
} else {
|
||||
eprintln!("ERROR: 'rootset marshal' requires a path to a root set in JSON format.");
|
||||
return exitcode::ERR_IOERR;
|
||||
}
|
||||
}
|
||||
|
||||
Some(("marshal", sc_args)) => {
|
||||
let path = sc_args.value_of("path");
|
||||
if path.is_some() {
|
||||
let path = path.unwrap();
|
||||
let json_data = crate::utils::read_limit(path, 1048576).await;
|
||||
if json_data.is_err() {
|
||||
eprintln!("ERROR: unable to read '{}'.", path);
|
||||
return exitcode::ERR_IOERR;
|
||||
}
|
||||
let json_data = json_data.unwrap();
|
||||
let root_set = serde_json::from_slice::<RootSet>(json_data.as_slice());
|
||||
if root_set.is_err() {
|
||||
eprintln!("ERROR: root set JSON parsing failed: {}", root_set.err().unwrap().to_string());
|
||||
return exitcode::ERR_IOERR;
|
||||
}
|
||||
let _ = std::io::stdout().write_all(root_set.unwrap().to_bytes().as_slice());
|
||||
} else {
|
||||
eprintln!("ERROR: 'rootset marshal' requires a path to a root set in JSON format.");
|
||||
return exitcode::ERR_IOERR;
|
||||
}
|
||||
}
|
||||
|
||||
_ => panic!(),
|
||||
}
|
||||
return exitcode::OK;
|
||||
}
|
134
zerotier-system-service/src/jsonformatter.rs
Normal file
134
zerotier-system-service/src/jsonformatter.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
/* This is a forked and hacked version of PrettyFormatter from:
|
||||
*
|
||||
* https://github.com/serde-rs/json/blob/master/src/ser.rs
|
||||
*
|
||||
* It is therefore under the same Apache license.
|
||||
*/
|
||||
|
||||
use serde_json::ser::Formatter;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct JsonFormatter<'a> {
|
||||
current_indent: usize,
|
||||
has_value: bool,
|
||||
indent: &'a [u8],
|
||||
}
|
||||
|
||||
fn indent<W>(wr: &mut W, n: usize, s: &[u8]) -> std::io::Result<()>
|
||||
where
|
||||
W: ?Sized + std::io::Write,
|
||||
{
|
||||
for _ in 0..n {
|
||||
wr.write_all(s)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'a> JsonFormatter<'a> {
|
||||
pub fn new() -> Self {
|
||||
JsonFormatter::with_indent(b" ")
|
||||
}
|
||||
|
||||
pub fn with_indent(indent: &'a [u8]) -> Self {
|
||||
JsonFormatter { current_indent: 0, has_value: false, indent }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for JsonFormatter<'a> {
|
||||
fn default() -> Self {
|
||||
JsonFormatter::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formatter for JsonFormatter<'a> {
|
||||
fn begin_array<W>(&mut self, writer: &mut W) -> std::io::Result<()>
|
||||
where
|
||||
W: ?Sized + std::io::Write,
|
||||
{
|
||||
self.current_indent += 1;
|
||||
self.has_value = false;
|
||||
writer.write_all(b"[")
|
||||
}
|
||||
|
||||
fn end_array<W>(&mut self, writer: &mut W) -> std::io::Result<()>
|
||||
where
|
||||
W: ?Sized + std::io::Write,
|
||||
{
|
||||
self.current_indent -= 1;
|
||||
if self.has_value {
|
||||
writer.write_all(b" ]")
|
||||
} else {
|
||||
writer.write_all(b"]")
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_array_value<W>(&mut self, writer: &mut W, first: bool) -> std::io::Result<()>
|
||||
where
|
||||
W: ?Sized + std::io::Write,
|
||||
{
|
||||
if first {
|
||||
writer.write_all(b" ")?;
|
||||
} else {
|
||||
writer.write_all(b", ")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end_array_value<W>(&mut self, _writer: &mut W) -> std::io::Result<()>
|
||||
where
|
||||
W: ?Sized + std::io::Write,
|
||||
{
|
||||
self.has_value = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn begin_object<W>(&mut self, writer: &mut W) -> std::io::Result<()>
|
||||
where
|
||||
W: ?Sized + std::io::Write,
|
||||
{
|
||||
self.current_indent += 1;
|
||||
self.has_value = false;
|
||||
writer.write_all(b"{")
|
||||
}
|
||||
|
||||
fn end_object<W>(&mut self, writer: &mut W) -> std::io::Result<()>
|
||||
where
|
||||
W: ?Sized + std::io::Write,
|
||||
{
|
||||
self.current_indent -= 1;
|
||||
|
||||
if self.has_value {
|
||||
writer.write_all(b"\n")?;
|
||||
indent(writer, self.current_indent, self.indent)?;
|
||||
}
|
||||
|
||||
writer.write_all(b"}")
|
||||
}
|
||||
|
||||
fn begin_object_key<W>(&mut self, writer: &mut W, first: bool) -> std::io::Result<()>
|
||||
where
|
||||
W: ?Sized + std::io::Write,
|
||||
{
|
||||
if first {
|
||||
writer.write_all(b"\n")?;
|
||||
} else {
|
||||
writer.write_all(b",\n")?;
|
||||
}
|
||||
indent(writer, self.current_indent, self.indent)
|
||||
}
|
||||
|
||||
fn begin_object_value<W>(&mut self, writer: &mut W) -> std::io::Result<()>
|
||||
where
|
||||
W: ?Sized + std::io::Write,
|
||||
{
|
||||
writer.write_all(b": ")
|
||||
}
|
||||
|
||||
fn end_object_value<W>(&mut self, _writer: &mut W) -> std::io::Result<()>
|
||||
where
|
||||
W: ?Sized + std::io::Write,
|
||||
{
|
||||
self.has_value = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -13,8 +13,10 @@ use clap::{Arg, ArgMatches, Command};
|
|||
|
||||
use zerotier_network_hypervisor::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION};
|
||||
|
||||
pub mod cli;
|
||||
pub mod exitcode;
|
||||
pub mod getifaddrs;
|
||||
pub mod jsonformatter;
|
||||
pub mod localconfig;
|
||||
pub mod utils;
|
||||
pub mod vnic;
|
||||
|
@ -83,6 +85,8 @@ Advanced Operations:
|
|||
· untrust <root set name> Stop using a root set
|
||||
· list List root sets in use
|
||||
sign <path> <?identity secret> Sign a root set with an identity
|
||||
verify <path> Load and verify a root set
|
||||
marshal <path> Dump root set as binary to stdout
|
||||
|
||||
service Start local service
|
||||
(usually not invoked manually)
|
||||
|
@ -101,13 +105,6 @@ pub fn print_help() {
|
|||
let _ = std::io::stdout().write_all(h.as_bytes());
|
||||
}
|
||||
|
||||
pub struct GlobalCommandLineFlags {
|
||||
pub json_output: bool,
|
||||
pub base_path: String,
|
||||
pub auth_token_path_override: Option<String>,
|
||||
pub auth_token_override: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos"))]
|
||||
pub fn platform_default_home_path() -> String {
|
||||
"/Library/Application Support/ZeroTier".into()
|
||||
|
@ -118,16 +115,16 @@ pub fn platform_default_home_path() -> String {
|
|||
"/var/lib/zerotier".into()
|
||||
}
|
||||
|
||||
async fn async_main(cli_args: Box<ArgMatches>) -> i32 {
|
||||
let global_cli_flags = GlobalCommandLineFlags {
|
||||
json_output: cli_args.is_present("json"),
|
||||
base_path: cli_args.value_of("path").map_or_else(platform_default_home_path, |p| p.to_string()),
|
||||
auth_token_path_override: cli_args.value_of("token_path").map(|p| p.to_string()),
|
||||
auth_token_override: cli_args.value_of("token").map(|t| t.to_string()),
|
||||
};
|
||||
pub struct Flags {
|
||||
pub json_output: bool,
|
||||
pub base_path: String,
|
||||
pub auth_token_path_override: Option<String>,
|
||||
pub auth_token_override: Option<String>,
|
||||
}
|
||||
|
||||
async fn async_main(flags: Flags, global_args: ArgMatches) -> i32 {
|
||||
#[allow(unused)]
|
||||
return match cli_args.subcommand() {
|
||||
return match global_args.subcommand() {
|
||||
Some(("help", _)) => {
|
||||
print_help();
|
||||
exitcode::OK
|
||||
|
@ -137,29 +134,30 @@ async fn async_main(cli_args: Box<ArgMatches>) -> i32 {
|
|||
exitcode::OK
|
||||
}
|
||||
Some(("status", _)) => todo!(),
|
||||
Some(("set", args)) => todo!(),
|
||||
Some(("peer", args)) => todo!(),
|
||||
Some(("network", args)) => todo!(),
|
||||
Some(("join", args)) => todo!(),
|
||||
Some(("leave", args)) => todo!(),
|
||||
Some(("set", cmd_args)) => todo!(),
|
||||
Some(("peer", cmd_args)) => todo!(),
|
||||
Some(("network", cmd_args)) => todo!(),
|
||||
Some(("join", cmd_args)) => todo!(),
|
||||
Some(("leave", cmd_args)) => todo!(),
|
||||
Some(("service", _)) => todo!(),
|
||||
Some(("identity", args)) => todo!(),
|
||||
Some(("rootset", args)) => todo!(),
|
||||
Some(("identity", cmd_args)) => todo!(),
|
||||
Some(("rootset", cmd_args)) => cli::rootset::cmd(flags, cmd_args).await,
|
||||
_ => {
|
||||
print_help();
|
||||
eprintln!("Invalid command line. Use 'help' for help.");
|
||||
exitcode::ERR_USAGE
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli_args = Box::new({
|
||||
let global_args = {
|
||||
let help = make_help();
|
||||
Command::new("zerotier")
|
||||
.arg(Arg::new("json").short('j'))
|
||||
.arg(Arg::new("path").short('p').takes_value(true))
|
||||
.arg(Arg::new("token_path").short('t').takes_value(true))
|
||||
.arg(Arg::new("token").short('T').takes_value(true))
|
||||
.subcommand_required(true)
|
||||
.subcommand(Command::new("help"))
|
||||
.subcommand(Command::new("version"))
|
||||
.subcommand(Command::new("status"))
|
||||
|
@ -198,7 +196,9 @@ fn main() {
|
|||
.subcommand(Command::new("trust").arg(Arg::new("path").index(1).required(true)))
|
||||
.subcommand(Command::new("untrust").arg(Arg::new("name").index(1).required(true)))
|
||||
.subcommand(Command::new("list"))
|
||||
.subcommand(Command::new("sign").arg(Arg::new("path").index(1).required(true)).arg(Arg::new("secret").index(2).required(true))),
|
||||
.subcommand(Command::new("sign").arg(Arg::new("path").index(1).required(true)).arg(Arg::new("secret").index(2).required(true)))
|
||||
.subcommand(Command::new("verify").arg(Arg::new("path").index(1).required(true)))
|
||||
.subcommand(Command::new("marshal").arg(Arg::new("path").index(1).required(true))),
|
||||
)
|
||||
.override_help(help.as_str())
|
||||
.override_usage("")
|
||||
|
@ -207,7 +207,7 @@ fn main() {
|
|||
.disable_help_flag(true)
|
||||
.try_get_matches_from(std::env::args())
|
||||
.unwrap_or_else(|e| {
|
||||
if e.kind() == clap::ErrorKind::DisplayHelp {
|
||||
if e.kind() == clap::ErrorKind::DisplayHelp || e.kind() == clap::ErrorKind::MissingSubcommand {
|
||||
print_help();
|
||||
std::process::exit(exitcode::OK);
|
||||
} else {
|
||||
|
@ -225,18 +225,25 @@ fn main() {
|
|||
}
|
||||
}
|
||||
if invalid.is_empty() {
|
||||
println!("Invalid command line. Use 'help' for help.");
|
||||
eprintln!("Invalid command line. Use 'help' for help.");
|
||||
} else {
|
||||
if suggested.is_empty() {
|
||||
println!("Unrecognized option '{}'. Use 'help' for help.", invalid);
|
||||
eprintln!("Unrecognized option '{}'. Use 'help' for help.", invalid);
|
||||
} else {
|
||||
println!("Unrecognized option '{}', did you mean {}? Use 'help' for help.", invalid, suggested);
|
||||
eprintln!("Unrecognized option '{}', did you mean {}? Use 'help' for help.", invalid, suggested);
|
||||
}
|
||||
}
|
||||
std::process::exit(exitcode::ERR_USAGE);
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
std::process::exit(tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(async_main(cli_args)));
|
||||
let flags = Flags {
|
||||
json_output: global_args.is_present("json"),
|
||||
base_path: global_args.value_of("path").map_or_else(platform_default_home_path, |p| p.to_string()),
|
||||
auth_token_path_override: global_args.value_of("token_path").map(|p| p.to_string()),
|
||||
auth_token_override: global_args.value_of("token").map(|t| t.to_string()),
|
||||
};
|
||||
|
||||
std::process::exit(tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap().block_on(async_main(flags, global_args)));
|
||||
}
|
||||
|
|
|
@ -6,17 +6,20 @@
|
|||
* https://www.zerotier.com/
|
||||
*/
|
||||
|
||||
use std::error::Error;
|
||||
use std::str::FromStr;
|
||||
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||
|
||||
use crate::jsonformatter::JsonFormatter;
|
||||
|
||||
use zerotier_network_hypervisor::vl1::Identity;
|
||||
|
||||
lazy_static! {
|
||||
|
@ -63,16 +66,6 @@ pub fn is_valid_port(v: &str) -> Result<(), String> {
|
|||
Err(format!("invalid TCP/IP port number: {}", v))
|
||||
}
|
||||
|
||||
/// Shortcut to use serde_json to serialize an object, returns "null" on error.
|
||||
pub 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 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
|
||||
|
@ -131,6 +124,22 @@ pub fn json_patch_object<O: Serialize + DeserializeOwned + Eq>(obj: O, patch: &s
|
|||
)
|
||||
}
|
||||
|
||||
/// Shortcut to use serde_json to serialize an object, returns "null" on error.
|
||||
pub 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 fn to_json_pretty<O: serde::Serialize>(o: &O) -> String {
|
||||
let mut buf = Vec::new();
|
||||
let mut ser = serde_json::Serializer::with_formatter(&mut buf, JsonFormatter::new());
|
||||
if o.serialize(&mut ser).is_ok() {
|
||||
String::from_utf8(buf).unwrap_or_else(|_| "null".into())
|
||||
} else {
|
||||
"null".into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience function to read up to limit bytes from a file.
|
||||
///
|
||||
/// If the file is larger than limit, the excess is not read.
|
||||
|
|
25
zerotier-system-service/zerotier-rootset.json
Normal file
25
zerotier-system-service/zerotier-rootset.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "root.zerotier.com",
|
||||
"revision": 1,
|
||||
"members": [ {
|
||||
"identity": "62f865ae71:0:e2076c57de870e6288d7d5e7404408b1545efca37d67f77b87e9e54168c25d3ef1a9abf2905ea5e785c01dff23887ad4232d95c7a8fd2c27111a72bd159322dc",
|
||||
"endpoints": [ "udp:50.7.252.138/9993", "udp:2001:49f0:d0db:2::2/9993" ],
|
||||
"signature": [ 1, 45, 14, 211, 108, 240, 151, 85, 43, 241, 113, 18, 24, 45, 198, 197, 67, 254, 96, 138, 194, 77, 170, 156, 168, 31, 240, 55, 168, 108, 69, 135, 253, 198, 153, 36, 166, 200, 222, 157, 122, 50, 149, 149, 40, 35, 125, 93, 78, 228, 51, 245, 53, 238, 133, 84, 188, 190, 98, 145, 177, 19, 54, 154, 0 ],
|
||||
"flags": 0
|
||||
}, {
|
||||
"identity": "778cde7190:0:3f6681a99e5ad1895e9fba33e6212d4454e168bcec7112101bf000956ed8e92e42892cb6f2ec410881a84ab19da50e1287ba3d926c3a1f755cccf299a1207055",
|
||||
"endpoints": [ "udp:103.195.103.66/9993", "udp:2605:9880:400:c3:254:f2bc:a1f7:19/9993" ],
|
||||
"signature": [ 1, 202, 181, 145, 69, 58, 169, 42, 149, 210, 160, 77, 220, 56, 246, 54, 210, 161, 144, 158, 103, 70, 104, 236, 58, 66, 127, 100, 117, 242, 208, 70, 68, 87, 142, 163, 222, 231, 146, 60, 205, 180, 202, 18, 181, 137, 216, 204, 109, 118, 224, 86, 220, 26, 142, 61, 18, 50, 174, 173, 44, 167, 231, 249, 0 ],
|
||||
"flags": 0
|
||||
}, {
|
||||
"identity": "cafe04eba9:0:6c6a9d1dea55c1616bfe2a2b8f0ff9a8cacaf70374fb1f39e3bef81cbfebef17b7228268a0a2a29d3488c752565c6c965cbd6506ec24397cc8a5d9d15285a87f",
|
||||
"endpoints": [ "udp:84.17.53.155/9993", "udp:2a02:6ea0:d405::9993/9993" ],
|
||||
"signature": [ 1, 129, 31, 37, 249, 242, 179, 153, 184, 117, 15, 192, 41, 69, 112, 196, 189, 18, 57, 96, 33, 82, 31, 142, 57, 251, 151, 118, 86, 71, 11, 170, 197, 11, 20, 55, 74, 66, 10, 248, 133, 216, 88, 212, 34, 139, 128, 179, 246, 241, 8, 126, 105, 195, 126, 235, 140, 219, 66, 92, 166, 203, 111, 132, 0 ],
|
||||
"flags": 0
|
||||
}, {
|
||||
"identity": "cafe9efeb9:0:ccdef76bc7b97ded904eabc5df09886d9c1514a610036cb9139cc214001a2958978efcec15712dd3948c6e6b3a8e893df01ff493d1f8d9806a860c5420571bf0",
|
||||
"endpoints": [ "udp:104.194.8.134/9993", "udp:2605:9880:200:1200:30:571:e34:51/9993" ],
|
||||
"signature": [ 1, 254, 236, 249, 244, 29, 229, 55, 85, 171, 15, 42, 222, 51, 237, 237, 47, 54, 158, 123, 96, 24, 101, 207, 63, 82, 113, 254, 154, 225, 188, 147, 75, 115, 243, 200, 253, 221, 198, 234, 74, 168, 126, 13, 137, 143, 13, 56, 73, 206, 242, 29, 97, 8, 221, 31, 236, 187, 86, 190, 15, 65, 184, 253, 13 ],
|
||||
"flags": 0
|
||||
} ]
|
||||
}
|
Loading…
Add table
Reference in a new issue