mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-04 10:05:42 +00:00
refactor(core): separate portable core from native runtime (#2451)
Create easytier-core as the portable owner of configuration, connectivity, tunnels, peer and routing state, gateways, management, the data plane, and instance lifecycle. Keep operating-system integration, native protocol engines, process startup, and presentation in easytier behind explicit Host capability adapters. Create easytier-proto to own schemas, generated RPC types, descriptors, and feature-scoped protocol slices. Remove runtime protobuf reflection from core while preserving unknown route-peer fields across forwarding. Normalize instance construction through CoreInstance, CoreHostAdapters, CoreProcessRuntime, and InstanceManager. Make the runtime config store the only authoritative mutable configuration after startup. Move the portable TCP/UDP data plane into core and extract a generic OperationBroker for completion, cancellation, disposal, and capacity accounting. Expose the session-based FFI v2 completion API and keep the WASI guest ABI, wire schemas, and adapters with core. Migrate CLI, GUI, web, FFI, Android JNI, OHOS, uptime, and mobile consumers to the shared manager and core state. Add explicit user/web config ownership and revision-aware web reconciliation. Preserve configuration, wire, and management behavior while fixing regressions discovered by the full platform and integration matrix: - inherit advertised relay capabilities in foreign networks; - refresh OSPF peer state immediately after runtime config changes; - restore CLI GlobalCtx event output without forcing GUI logging; - retain legacy encryption names and standalone RPC tunnel metadata; - restore ICMP host composition and fragmented UDP handling; - use portable 64-bit atomics on 32-bit MIPS targets; and - retain discarded operations until late cancellation completes. Validate the refactor across 45 GitHub checks, including Linux, macOS, Windows, FreeBSD, web, GUI, Android, OHOS, feature profiles, and three-node and subnet-proxy integration tests. BREAKING CHANGE: internal Rust module paths are not preserved. Legacy native data-plane APIs are replaced by the session-based FFI v2 API. The dedicated Android data-plane wrapper is removed.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
[package]
|
||||
name = "easytier-proto"
|
||||
description = "EasyTier protobuf and generated RPC types."
|
||||
homepage = "https://github.com/EasyTier/EasyTier"
|
||||
repository = "https://github.com/EasyTier/EasyTier"
|
||||
version = "2.6.4"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors = ["kkrainbow"]
|
||||
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||
categories = ["network-programming"]
|
||||
license-file = "../LICENSE"
|
||||
build = "build/main.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0", optional = true }
|
||||
async-trait = { version = "0.1.74", optional = true }
|
||||
auto_impl = { version = "1.1.0", optional = true }
|
||||
base64 = { version = "0.22", optional = true }
|
||||
bytes = { version = "1.5.0", optional = true }
|
||||
chrono = { version = "0.4.37", features = ["serde"], optional = true }
|
||||
cidr = { version = "0.3.1", features = ["serde"], optional = true }
|
||||
hmac = { version = "0.12.1", optional = true }
|
||||
prost = { version = "0.14.3", optional = true }
|
||||
prost-types = { version = "0.14.3", optional = true }
|
||||
prost-wkt-types = { version = "0.7.1", optional = true }
|
||||
pbjson = { version = "0.9.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1", optional = true }
|
||||
sha2 = { version = "0.10.8", optional = true }
|
||||
thiserror = { version = "1.0", optional = true }
|
||||
tokio = { version = "1", default-features = false, features = ["time"], optional = true }
|
||||
url = { version = "2.5", features = ["serde"], optional = true }
|
||||
uuid = { version = "1.5.0", features = ["serde"], optional = true }
|
||||
x25519-dalek = { version = "2.0", features = ["static_secrets"], optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
indoc = "2.0"
|
||||
pbjson-build = "0.9.0"
|
||||
proc-macro2 = "1"
|
||||
prost-build = "0.14.3"
|
||||
quote = "1"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
reqwest = { version = "0.12.12", features = ["blocking"] }
|
||||
zip = "4.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", default-features = false, features = [
|
||||
"macros",
|
||||
"rt",
|
||||
] }
|
||||
uuid = { version = "1.5.0", features = [
|
||||
"v4",
|
||||
"fast-rng",
|
||||
] }
|
||||
|
||||
[features]
|
||||
default = ["full"]
|
||||
full = [
|
||||
"api",
|
||||
"core",
|
||||
"faketcp",
|
||||
"magic-dns",
|
||||
"quic",
|
||||
"utils",
|
||||
"websocket",
|
||||
"wireguard",
|
||||
"zstd",
|
||||
"json-rpc",
|
||||
]
|
||||
api = ["core"]
|
||||
core = [
|
||||
"dep:anyhow",
|
||||
"dep:async-trait",
|
||||
"dep:auto_impl",
|
||||
"dep:base64",
|
||||
"dep:bytes",
|
||||
"dep:chrono",
|
||||
"dep:cidr",
|
||||
"dep:hmac",
|
||||
"dep:pbjson",
|
||||
"dep:prost",
|
||||
"dep:prost-types",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"dep:sha2",
|
||||
"dep:thiserror",
|
||||
"dep:tokio",
|
||||
"dep:url",
|
||||
"dep:uuid",
|
||||
"dep:x25519-dalek",
|
||||
]
|
||||
faketcp = []
|
||||
magic-dns = []
|
||||
quic = []
|
||||
utils = []
|
||||
websocket = []
|
||||
wireguard = []
|
||||
zstd = []
|
||||
json-rpc = ["dep:prost-wkt-types"]
|
||||
@@ -0,0 +1,142 @@
|
||||
mod rpc;
|
||||
|
||||
use crate::rpc::ServiceGenerator;
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::io::Cursor;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn check_protoc_exist() -> Option<PathBuf> {
|
||||
let path = env::var_os("PROTOC").map(PathBuf::from);
|
||||
if path.is_some() && path.as_ref().unwrap().exists() {
|
||||
return path;
|
||||
}
|
||||
|
||||
let path = env::var_os("PATH").unwrap_or_default();
|
||||
for p in env::split_paths(&path) {
|
||||
let p = p.join("protoc.exe");
|
||||
if p.exists() && p.is_file() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn get_cargo_target_dir() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
|
||||
let profile = env::var("PROFILE")?;
|
||||
let mut target_dir = None;
|
||||
let mut sub_path = out_dir.as_path();
|
||||
while let Some(parent) = sub_path.parent() {
|
||||
if parent.ends_with(&profile) {
|
||||
target_dir = Some(parent);
|
||||
break;
|
||||
}
|
||||
sub_path = parent;
|
||||
}
|
||||
let target_dir = target_dir.ok_or("not found")?;
|
||||
Ok(target_dir.to_path_buf())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn download_protoc() -> PathBuf {
|
||||
let out_dir = get_cargo_target_dir().unwrap().join("protobuf");
|
||||
let fname = out_dir.join("bin/protoc.exe");
|
||||
if fname.exists() {
|
||||
println!("cargo:info=use existing protoc: {:?}", fname);
|
||||
return fname;
|
||||
}
|
||||
|
||||
println!("cargo:info=need download protoc, please wait...");
|
||||
|
||||
let url = "https://github.com/protocolbuffers/protobuf/releases/download/v26.0-rc1/protoc-26.0-rc-1-win64.zip";
|
||||
let response = reqwest::blocking::get(url).unwrap();
|
||||
println!("{:?}", response);
|
||||
let mut content = response
|
||||
.bytes()
|
||||
.map(|v| v.to_vec())
|
||||
.map(Cursor::new)
|
||||
.map(zip::ZipArchive::new)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
content.extract(out_dir).unwrap();
|
||||
|
||||
fname
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn ensure_protoc_for_windows() {
|
||||
let protoc_path = if let Some(path) = check_protoc_exist() {
|
||||
println!("cargo:info=use os existing protoc: {:?}", path);
|
||||
path
|
||||
} else {
|
||||
download_protoc()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
env::set_var("PROTOC", protoc_path);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[cfg(target_os = "windows")]
|
||||
ensure_protoc_for_windows();
|
||||
|
||||
let proto_files_reflect = ["proto/peer_rpc.proto", "proto/common.proto"];
|
||||
|
||||
let proto_files = [
|
||||
"proto/core_peer.proto",
|
||||
"proto/core_config.proto",
|
||||
"proto/error.proto",
|
||||
"proto/tests.proto",
|
||||
"proto/api_instance.proto",
|
||||
"proto/api_logger.proto",
|
||||
"proto/api_config.proto",
|
||||
"proto/api_manage.proto",
|
||||
"proto/web.proto",
|
||||
"proto/magic_dns.proto",
|
||||
"proto/acl.proto",
|
||||
];
|
||||
|
||||
for proto_file in proto_files.iter().chain(proto_files_reflect.iter()) {
|
||||
println!("cargo:rerun-if-changed={proto_file}");
|
||||
}
|
||||
|
||||
let out = PathBuf::from(env::var("OUT_DIR")?);
|
||||
let descriptor = out.join("descriptors.bin");
|
||||
|
||||
let mut config = prost_build::Config::new();
|
||||
if env::var_os("CARGO_FEATURE_JSON_RPC").is_some() {
|
||||
config
|
||||
.extern_path(".google.protobuf.Any", "::prost_wkt_types::Any")
|
||||
.extern_path(".google.protobuf.Timestamp", "::prost_wkt_types::Timestamp")
|
||||
.extern_path(".google.protobuf.Value", "::prost_wkt_types::Value");
|
||||
} else {
|
||||
config
|
||||
.extern_path(".google.protobuf.Any", "::prost_types::Any")
|
||||
.extern_path(".google.protobuf.Timestamp", "::prost_types::Timestamp")
|
||||
.extern_path(".google.protobuf.Value", "::prost_types::Value");
|
||||
}
|
||||
config
|
||||
.file_descriptor_set_path(&descriptor)
|
||||
.service_generator(Box::new(ServiceGenerator::default()))
|
||||
.btree_map(["."])
|
||||
.skip_debug([".common.Ipv4Addr", ".common.Ipv6Addr", ".common.UUID"]);
|
||||
|
||||
config.compile_protos(&proto_files, &["proto/"])?;
|
||||
|
||||
config.file_descriptor_set_path(out.join("file_descriptor_set.bin"));
|
||||
config.compile_protos(&proto_files_reflect, &["proto/"])?;
|
||||
|
||||
let descriptor = std::fs::read(descriptor)?;
|
||||
pbjson_build::Builder::new()
|
||||
.register_descriptors(&descriptor)?
|
||||
.preserve_proto_field_names()
|
||||
.btree_map(["."])
|
||||
.build(&["."])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,721 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use indoc::formatdoc;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use std::str::FromStr;
|
||||
|
||||
fn parse(value: &str) -> TokenStream {
|
||||
TokenStream::from_str(value)
|
||||
.unwrap_or_else(|err| panic!("Failed to parse tokens: {} ({})", value, err))
|
||||
}
|
||||
|
||||
fn doc(comments: &prost_build::Comments) -> TokenStream {
|
||||
let doc = comments
|
||||
.leading
|
||||
.iter()
|
||||
.flat_map(|c| c.lines().filter(|s| !s.is_empty()));
|
||||
quote! { #( #[doc = #doc] )* }
|
||||
}
|
||||
|
||||
const NAMESPACE: &str = "crate::proto::rpc_types";
|
||||
|
||||
struct Method {
|
||||
index: u8,
|
||||
doc: TokenStream,
|
||||
method: Ident,
|
||||
method_inner: Ident,
|
||||
method_str: String,
|
||||
method_proto: Ident,
|
||||
method_proto_str: String,
|
||||
Input: TokenStream,
|
||||
Input_proto_str: String,
|
||||
Output: TokenStream,
|
||||
Output_proto_str: String,
|
||||
}
|
||||
|
||||
impl Method {
|
||||
fn new(index: u8, method: prost_build::Method) -> Self {
|
||||
assert!(
|
||||
!method.client_streaming,
|
||||
"Client streaming not yet supported for method {}",
|
||||
method.proto_name
|
||||
);
|
||||
assert!(
|
||||
!method.server_streaming,
|
||||
"Server streaming not yet supported for method {}",
|
||||
method.proto_name
|
||||
);
|
||||
Self {
|
||||
index,
|
||||
doc: doc(&method.comments),
|
||||
method: format_ident!("{}", method.name),
|
||||
method_inner: format_ident!("{}_inner", method.name),
|
||||
method_str: method.name,
|
||||
method_proto: format_ident!("{}", method.proto_name),
|
||||
method_proto_str: method.proto_name,
|
||||
Input: parse(&method.input_type),
|
||||
Input_proto_str: method.input_proto_type,
|
||||
Output: parse(&method.output_type),
|
||||
Output_proto_str: method.output_proto_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Service {
|
||||
namespace: TokenStream,
|
||||
doc: TokenStream,
|
||||
Service: Ident,
|
||||
ServiceDescriptor: Ident,
|
||||
ServiceServer: Ident,
|
||||
ServiceClient: Ident,
|
||||
ServiceClientFactory: Ident,
|
||||
ServiceMethodDescriptor: Ident,
|
||||
Service_str: String,
|
||||
Service_proto_str: String,
|
||||
Service_package_str: String,
|
||||
methods: Vec<Method>,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
fn new(service: prost_build::Service) -> Self {
|
||||
let methods = service
|
||||
.methods
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, method)| Method::new((i + 1) as u8, method))
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
namespace: parse(NAMESPACE),
|
||||
doc: doc(&service.comments),
|
||||
Service: format_ident!("{}", service.name),
|
||||
ServiceDescriptor: format_ident!("{}Descriptor", service.name),
|
||||
ServiceServer: format_ident!("{}Server", service.name),
|
||||
ServiceClient: format_ident!("{}Client", service.name),
|
||||
ServiceClientFactory: format_ident!("{}ClientFactory", service.name),
|
||||
ServiceMethodDescriptor: format_ident!("{}MethodDescriptor", service.name),
|
||||
Service_str: service.name,
|
||||
Service_proto_str: service.proto_name,
|
||||
Service_package_str: service.package,
|
||||
methods,
|
||||
}
|
||||
}
|
||||
|
||||
fn trait_Service(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
doc,
|
||||
Service,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let match_json_call_method = methods.iter().map(
|
||||
|Method {
|
||||
method,
|
||||
method_str,
|
||||
method_proto_str,
|
||||
Input,
|
||||
..
|
||||
}| {
|
||||
quote! {
|
||||
#method_str | #method_proto_str => {
|
||||
let req: #Input = ::serde_json::from_value(json)
|
||||
.map_err(|e| #namespace::error::Error::MalformatRpcPacket(format!("json error: {}", e)))?;
|
||||
let resp = self.#method(ctrl, req).await?;
|
||||
Ok(::serde_json::to_value(resp)
|
||||
.map_err(|e| #namespace::error::Error::MalformatRpcPacket(format!("json error: {}", e)))?)
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let methods = methods.iter().map(
|
||||
|Method {
|
||||
doc,
|
||||
method,
|
||||
Input,
|
||||
Output,
|
||||
..
|
||||
}| {
|
||||
quote! {
|
||||
#doc
|
||||
async fn #method(&self, ctrl: Self::Controller, input: #Input) -> #namespace::error::Result<#Output>;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
#doc
|
||||
#[async_trait::async_trait]
|
||||
#[auto_impl::auto_impl(&, Arc, Box)]
|
||||
pub trait #Service {
|
||||
type Controller: #namespace::controller::Controller;
|
||||
|
||||
#(#methods)*
|
||||
|
||||
#[cfg(feature = "json-rpc")]
|
||||
async fn json_call_method(
|
||||
&self,
|
||||
ctrl: Self::Controller,
|
||||
method: &str,
|
||||
json: ::serde_json::Value,
|
||||
) -> #namespace::error::Result<::serde_json::Value> {
|
||||
match method {
|
||||
#(#match_json_call_method)*
|
||||
_ => Err(#namespace::error::Error::InvalidMethodIndex(0, method.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_Service_for_Weak(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
Service,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
let methods = methods.iter().map(
|
||||
|Method {
|
||||
method,
|
||||
Input,
|
||||
Output,
|
||||
..
|
||||
}| {
|
||||
quote! {
|
||||
async fn #method(&self, ctrl: Self::Controller, input: #Input) -> #namespace::error::Result<#Output> {
|
||||
let Some(service) = self.upgrade() else {
|
||||
return Err(#namespace::error::Error::Shutdown);
|
||||
};
|
||||
service.#method(ctrl, input).await
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
#[async_trait::async_trait]
|
||||
impl<T> #Service for ::std::sync::Weak<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
::std::sync::Arc<T>: #Service,
|
||||
{
|
||||
type Controller = <::std::sync::Arc<T> as #Service>::Controller;
|
||||
|
||||
#(#methods)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_ServiceDescriptor(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
ServiceDescriptor,
|
||||
ServiceMethodDescriptor,
|
||||
Service_str,
|
||||
Service_proto_str,
|
||||
Service_package_str,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let doc = format!("A service descriptor for a `{}`.", Service_str);
|
||||
|
||||
let methods = methods.iter().map(|Method { method_proto, .. }| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto, }
|
||||
});
|
||||
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Default)]
|
||||
pub struct #ServiceDescriptor;
|
||||
|
||||
impl #namespace::descriptor::ServiceDescriptor for #ServiceDescriptor {
|
||||
type Method = #ServiceMethodDescriptor;
|
||||
fn name(&self) -> &'static str { #Service_str }
|
||||
fn proto_name(&self) -> &'static str { #Service_proto_str }
|
||||
fn package(&self) -> &'static str { #Service_package_str }
|
||||
fn methods(&self) -> &'static [Self::Method] {
|
||||
&[ #(#methods)* ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enum_ServiceMethodDescriptor(&self) -> TokenStream {
|
||||
let Self {
|
||||
ServiceMethodDescriptor,
|
||||
Service_str,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let doc = formatdoc! {"
|
||||
Methods available on a `{Service_str}`.
|
||||
|
||||
This can be used as a key when routing requests for servers/clients of a `{Service_str}`.
|
||||
"};
|
||||
|
||||
let variants = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
index,
|
||||
..
|
||||
}| {
|
||||
quote! { #method_proto = #index, }
|
||||
},
|
||||
);
|
||||
|
||||
let impl_MethodDescriptor = self.impl_MethodDescriptor_for_ServiceMethodDescriptor();
|
||||
let impl_TryFrom = self.impl_TryFrom_for_ServiceMethodDescriptor();
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
#[repr(u8)]
|
||||
pub enum #ServiceMethodDescriptor {
|
||||
#(#variants)*
|
||||
}
|
||||
|
||||
#impl_MethodDescriptor
|
||||
|
||||
#impl_TryFrom
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_MethodDescriptor_for_ServiceMethodDescriptor(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
ServiceMethodDescriptor,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let name = {
|
||||
let arms = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
method_str,
|
||||
..
|
||||
}| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto => #method_str, }
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
fn name(&self) -> &'static str {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let proto_name = {
|
||||
let arms = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
method_proto_str,
|
||||
..
|
||||
}| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto => #method_proto_str, }
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
fn proto_name(&self) -> &'static str {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let input_type = {
|
||||
let arms = methods.iter().map(|Method { method_proto, Input, .. }| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto => ::std::any::TypeId::of::<#Input>(), }
|
||||
});
|
||||
|
||||
quote! {
|
||||
fn input_type(&self) -> ::std::any::TypeId {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let input_proto_type = {
|
||||
let arms = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
Input_proto_str,
|
||||
..
|
||||
}| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto => #Input_proto_str, }
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
fn input_proto_type(&self) -> &'static str {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let output_type = {
|
||||
let arms = methods.iter().map(|Method { method_proto, Output, .. }| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto => ::std::any::TypeId::of::<#Output>(), }
|
||||
});
|
||||
|
||||
quote! {
|
||||
fn output_type(&self) -> ::std::any::TypeId {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let output_proto_type = {
|
||||
let arms = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
Output_proto_str,
|
||||
..
|
||||
}| {
|
||||
quote! { #ServiceMethodDescriptor::#method_proto => #Output_proto_str, }
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
fn output_proto_type(&self) -> &'static str {
|
||||
match *self {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl #namespace::descriptor::MethodDescriptor for #ServiceMethodDescriptor {
|
||||
#name
|
||||
|
||||
#proto_name
|
||||
|
||||
#input_type
|
||||
|
||||
#input_proto_type
|
||||
|
||||
#output_type
|
||||
|
||||
#output_proto_type
|
||||
|
||||
fn index(&self) -> u8 {
|
||||
*self as u8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_TryFrom_for_ServiceMethodDescriptor(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
ServiceMethodDescriptor,
|
||||
Service_str,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let arms = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
index,
|
||||
..
|
||||
}| {
|
||||
quote! { #index => Ok(#ServiceMethodDescriptor::#method_proto), }
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
impl std::convert::TryFrom<u8> for #ServiceMethodDescriptor {
|
||||
type Error = #namespace::error::Error;
|
||||
fn try_from(value: u8) -> #namespace::error::Result<Self> {
|
||||
match value {
|
||||
#(#arms)*
|
||||
_ => Err(#namespace::error::Error::InvalidMethodIndex(value, #Service_str.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_ServiceClient(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
ServiceDescriptor,
|
||||
ServiceClient,
|
||||
Service_str,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let doc = formatdoc! {"
|
||||
A client for a `{Service_str}`.
|
||||
|
||||
This implements the `{Service_str}` trait by dispatching all method calls to the supplied `Handler`.
|
||||
"};
|
||||
|
||||
let impl_service_client = self.impl_ServiceClient();
|
||||
let impl_service_for_client = self.impl_Service_for_ServiceClient();
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct #ServiceClient<H>(H) where H: #namespace::handler::Handler;
|
||||
|
||||
impl<H> #ServiceClient<H> where H: #namespace::handler::Handler<Descriptor = #ServiceDescriptor> {
|
||||
/// Creates a new client instance that delegates all method calls to the supplied handler.
|
||||
pub fn new(handler: H) -> Self {
|
||||
Self(handler)
|
||||
}
|
||||
}
|
||||
|
||||
#impl_service_client
|
||||
|
||||
#impl_service_for_client
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_ServiceClient(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
ServiceClient,
|
||||
ServiceDescriptor,
|
||||
ServiceMethodDescriptor,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let methods = methods.iter().map(
|
||||
|Method {
|
||||
method_inner,
|
||||
method_proto,
|
||||
Input,
|
||||
Output,
|
||||
..
|
||||
}| {
|
||||
quote! {
|
||||
async fn #method_inner(handler: H, ctrl: H::Controller, input: #Input) -> #namespace::error::Result<#Output> {
|
||||
#namespace::__rt::call_method(handler, ctrl, #ServiceMethodDescriptor::#method_proto, input).await
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
impl<H> #ServiceClient<H> where H: #namespace::handler::Handler<Descriptor = #ServiceDescriptor> {
|
||||
#(#methods)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_Service_for_ServiceClient(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
Service,
|
||||
ServiceClient,
|
||||
ServiceDescriptor,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let methods = methods.iter().map(
|
||||
|Method {
|
||||
method,
|
||||
method_inner,
|
||||
Input,
|
||||
Output,
|
||||
..
|
||||
}| {
|
||||
quote! {
|
||||
async fn #method(&self, ctrl: H::Controller, input: #Input) -> #namespace::error::Result<#Output> {
|
||||
#ServiceClient::#method_inner(self.0.clone(), ctrl, input).await
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
#[async_trait::async_trait]
|
||||
impl<H> #Service for #ServiceClient<H> where H: #namespace::handler::Handler<Descriptor = #ServiceDescriptor> {
|
||||
type Controller = H::Controller;
|
||||
|
||||
#(#methods)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_ServiceClientFactory(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
Service,
|
||||
ServiceClient,
|
||||
ServiceClientFactory,
|
||||
ServiceDescriptor,
|
||||
..
|
||||
} = self;
|
||||
|
||||
quote! {
|
||||
pub struct #ServiceClientFactory<C: #namespace::controller::Controller>(std::marker::PhantomData<C>);
|
||||
|
||||
impl<C: #namespace::controller::Controller> Clone for #ServiceClientFactory<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(std::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> #namespace::__rt::RpcClientFactory for #ServiceClientFactory<C> where C: #namespace::controller::Controller {
|
||||
type Descriptor = #ServiceDescriptor;
|
||||
type ClientImpl = Box<dyn #Service<Controller = C> + Send + Sync + 'static>;
|
||||
type Controller = C;
|
||||
|
||||
fn new(handler: impl #namespace::handler::Handler<Descriptor = Self::Descriptor, Controller = Self::Controller>) -> Self::ClientImpl {
|
||||
Box::new(#ServiceClient::new(handler))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_ServiceServer(&self) -> TokenStream {
|
||||
let Self {
|
||||
namespace,
|
||||
Service,
|
||||
ServiceDescriptor,
|
||||
ServiceServer,
|
||||
ServiceMethodDescriptor,
|
||||
Service_str,
|
||||
methods,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let doc = formatdoc! {"
|
||||
A server for a `{Service_str}`.
|
||||
|
||||
This implements the `Server` trait by handling requests and dispatch them to methods on the
|
||||
supplied `{Service_str}`.
|
||||
"};
|
||||
|
||||
let arms = methods.iter().map(
|
||||
|Method {
|
||||
method_proto,
|
||||
method,
|
||||
Input,
|
||||
..
|
||||
}| {
|
||||
quote! {
|
||||
#ServiceMethodDescriptor::#method_proto => {
|
||||
let decoded: #Input = #namespace::__rt::decode(input)?;
|
||||
let ret = service.#method(ctrl, decoded).await?;
|
||||
#namespace::__rt::encode(ret)
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct #ServiceServer<A>(A) where A: #Service + Clone + Send + 'static;
|
||||
|
||||
impl<T> #ServiceServer<::std::sync::Weak<T>>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
::std::sync::Arc<T>: #Service,
|
||||
{
|
||||
pub fn new_arc(service: ::std::sync::Arc<T>) -> #ServiceServer<::std::sync::Weak<T>> {
|
||||
#ServiceServer(::std::sync::Arc::downgrade(&service))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> #ServiceServer<A> where A: #Service + Clone + Send + 'static {
|
||||
/// Creates a new server instance that dispatches all calls to the supplied service.
|
||||
pub fn new(service: A) -> #ServiceServer<A> {
|
||||
#ServiceServer(service)
|
||||
}
|
||||
|
||||
async fn call_inner(
|
||||
service: A,
|
||||
method: #ServiceMethodDescriptor,
|
||||
ctrl: A::Controller,
|
||||
input: ::bytes::Bytes)
|
||||
-> #namespace::error::Result<::bytes::Bytes> {
|
||||
match method {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<A> #namespace::handler::Handler for #ServiceServer<A>
|
||||
where
|
||||
A: #Service + Clone + Send + Sync + 'static {
|
||||
type Descriptor = #ServiceDescriptor;
|
||||
type Controller = A::Controller;
|
||||
|
||||
async fn call(
|
||||
&self,
|
||||
ctrl: A::Controller,
|
||||
method: #ServiceMethodDescriptor,
|
||||
input: ::bytes::Bytes)
|
||||
-> #namespace::error::Result<::bytes::Bytes> {
|
||||
#ServiceServer::call_inner(self.0.clone(), method, ctrl, input).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The service generator to be used with `prost-build` to generate RPC implementations for
|
||||
/// `prost-simple-rpc`.
|
||||
///
|
||||
/// See the crate-level documentation for more info.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ServiceGenerator;
|
||||
|
||||
impl prost_build::ServiceGenerator for ServiceGenerator {
|
||||
fn generate(&mut self, service: prost_build::Service, buf: &mut String) {
|
||||
let info = Service::new(service);
|
||||
|
||||
let trait_Service = info.trait_Service();
|
||||
let impl_Service_for_Weak = info.impl_Service_for_Weak();
|
||||
let struct_ServiceDescriptor = info.struct_ServiceDescriptor();
|
||||
let enum_ServiceMethodDescriptor = info.enum_ServiceMethodDescriptor();
|
||||
let struct_ServiceClient = info.struct_ServiceClient();
|
||||
let struct_ServiceClientFactory = info.struct_ServiceClientFactory();
|
||||
let struct_ServiceServer = info.struct_ServiceServer();
|
||||
|
||||
let tokens = quote! {
|
||||
#trait_Service
|
||||
|
||||
#impl_Service_for_Weak
|
||||
|
||||
#struct_ServiceDescriptor
|
||||
|
||||
#enum_ServiceMethodDescriptor
|
||||
|
||||
#struct_ServiceClient
|
||||
|
||||
#struct_ServiceClientFactory
|
||||
|
||||
#struct_ServiceServer
|
||||
};
|
||||
|
||||
buf.push('\n');
|
||||
buf.push_str(&tokens.to_string());
|
||||
buf.push('\n');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "common.proto";
|
||||
|
||||
package acl;
|
||||
|
||||
// Enhanced protocol enum with more granular options
|
||||
enum Protocol {
|
||||
Unspecified = 0;
|
||||
TCP = 1;
|
||||
UDP = 2;
|
||||
ICMP = 3;
|
||||
ICMPv6 = 4;
|
||||
Any = 5;
|
||||
}
|
||||
|
||||
enum Action {
|
||||
Noop = 0;
|
||||
Allow = 1;
|
||||
Drop = 2; // Silent drop (no response)
|
||||
}
|
||||
|
||||
enum ChainType {
|
||||
UnspecifiedChain = 0;
|
||||
// send to this node
|
||||
Inbound = 1;
|
||||
// send from this node
|
||||
Outbound = 2;
|
||||
// subnet proxy
|
||||
Forward = 3;
|
||||
}
|
||||
|
||||
// Time-based access control
|
||||
message TimeWindow {
|
||||
// Days of week: 0=Sunday, 1=Monday, ..., 6=Saturday
|
||||
repeated uint32 days_of_week = 1;
|
||||
// Time in minutes from midnight (0-1439)
|
||||
uint32 start_time = 2;
|
||||
uint32 end_time = 3;
|
||||
// Timezone offset in minutes from UTC
|
||||
int32 timezone_offset = 4;
|
||||
}
|
||||
|
||||
// Enhanced rule with priority and metadata
|
||||
message Rule {
|
||||
// Rule identification and metadata
|
||||
string name = 1; // Human-readable rule name
|
||||
string description = 2; // Rule description
|
||||
uint32 priority = 3; // Higher number = higher priority (0-65535)
|
||||
bool enabled = 4; // Rule enabled/disabled state
|
||||
|
||||
// Core matching criteria
|
||||
Protocol protocol = 5;
|
||||
repeated string ports = 6;
|
||||
repeated string source_ips = 7; // Source IP ranges
|
||||
repeated string destination_ips = 8; // Destination IP ranges
|
||||
|
||||
// Enhanced matching criteria
|
||||
repeated string source_ports = 9; // Source port range
|
||||
|
||||
// Action and logging
|
||||
Action action = 10;
|
||||
|
||||
// Rate limiting (packets per second)
|
||||
uint32 rate_limit = 11; // 0 = no limit
|
||||
uint32 burst_limit = 12; // Burst allowance
|
||||
|
||||
// Connection tracking
|
||||
bool stateful = 13; // Enable connection tracking
|
||||
|
||||
// Group matching criteria
|
||||
repeated string source_groups = 14;
|
||||
repeated string destination_groups = 15;
|
||||
}
|
||||
|
||||
// Rule chain with metadata and optimization hints
|
||||
message Chain {
|
||||
// Chain identification
|
||||
string name = 1; // Human-readable chain name
|
||||
ChainType chain_type = 2;
|
||||
string description = 3; // Chain description
|
||||
bool enabled = 4; // Chain enabled/disabled state
|
||||
|
||||
// Rules in priority order (highest priority first)
|
||||
repeated Rule rules = 5;
|
||||
|
||||
// Default action when no rules match
|
||||
Action default_action = 6;
|
||||
}
|
||||
|
||||
message GroupInfo {
|
||||
repeated GroupIdentity declares = 1;
|
||||
repeated string members = 2;
|
||||
}
|
||||
|
||||
message GroupIdentity {
|
||||
string group_name = 1;
|
||||
string group_secret = 2;
|
||||
}
|
||||
|
||||
message AclV1 {
|
||||
repeated Chain chains = 1;
|
||||
GroupInfo group = 2;
|
||||
}
|
||||
|
||||
enum ConnState {
|
||||
New = 0;
|
||||
Established = 1;
|
||||
Related = 2;
|
||||
Invalid = 3;
|
||||
}
|
||||
|
||||
// Connection tracking entry for stateful ACLs
|
||||
message ConnTrackEntry {
|
||||
common.SocketAddr src_addr = 1;
|
||||
common.SocketAddr dst_addr = 2;
|
||||
Protocol protocol = 3; // IP protocol number (e.g., 6 = TCP, 17 = UDP)
|
||||
ConnState state = 4;
|
||||
uint64 created_at = 5; // Unix timestamp (seconds)
|
||||
uint64 last_seen = 6; // Unix timestamp (seconds)
|
||||
uint64 packet_count = 7;
|
||||
uint64 byte_count = 8;
|
||||
}
|
||||
|
||||
// Top-level ACL configuration
|
||||
message Acl {
|
||||
AclV1 acl_v1 = 2;
|
||||
}
|
||||
|
||||
message StatItem {
|
||||
uint64 packet_count = 1;
|
||||
uint64 byte_count = 2;
|
||||
}
|
||||
|
||||
message RuleStats {
|
||||
Rule rule = 1;
|
||||
StatItem stat = 2;
|
||||
}
|
||||
|
||||
message AclStats {
|
||||
repeated RuleStats rules = 1;
|
||||
repeated ConnTrackEntry conn_track = 2;
|
||||
map<string, uint64> global = 3;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "common.proto";
|
||||
import "acl.proto";
|
||||
import "api_instance.proto";
|
||||
import "api_manage.proto";
|
||||
|
||||
package api.config;
|
||||
|
||||
enum ConfigPatchAction {
|
||||
ADD = 0;
|
||||
REMOVE = 1;
|
||||
CLEAR = 2;
|
||||
}
|
||||
|
||||
message InstanceConfigPatch {
|
||||
optional string hostname = 1;
|
||||
optional common.Ipv4Inet ipv4 = 2;
|
||||
optional common.Ipv6Inet ipv6 = 3;
|
||||
repeated PortForwardPatch port_forwards = 4;
|
||||
optional AclPatch acl = 5;
|
||||
repeated ProxyNetworkPatch proxy_networks = 6;
|
||||
repeated RoutePatch routes = 7;
|
||||
repeated ExitNodePatch exit_nodes = 8;
|
||||
repeated UrlPatch mapped_listeners = 9;
|
||||
repeated UrlPatch connectors = 10;
|
||||
optional bool ipv6_public_addr_provider = 11;
|
||||
optional bool ipv6_public_addr_auto = 12;
|
||||
optional string ipv6_public_addr_prefix = 13;
|
||||
optional bool disable_relay_data = 14;
|
||||
}
|
||||
|
||||
message PortForwardPatch {
|
||||
ConfigPatchAction action = 1;
|
||||
common.PortForwardConfigPb cfg = 2;
|
||||
}
|
||||
|
||||
message StringPatch {
|
||||
ConfigPatchAction action = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message UrlPatch {
|
||||
ConfigPatchAction action = 1;
|
||||
common.Url url = 2;
|
||||
}
|
||||
|
||||
message AclPatch {
|
||||
optional acl.Acl acl = 1;
|
||||
repeated StringPatch tcp_whitelist = 2;
|
||||
repeated StringPatch udp_whitelist = 3;
|
||||
}
|
||||
|
||||
message ProxyNetworkPatch {
|
||||
ConfigPatchAction action = 1;
|
||||
common.Ipv4Inet cidr = 2;
|
||||
optional common.Ipv4Inet mapped_cidr = 3;
|
||||
}
|
||||
|
||||
message RoutePatch {
|
||||
ConfigPatchAction action = 1;
|
||||
common.Ipv4Inet cidr = 2;
|
||||
}
|
||||
|
||||
message ExitNodePatch {
|
||||
ConfigPatchAction action = 1;
|
||||
common.IpAddr node = 2;
|
||||
}
|
||||
|
||||
message PatchConfigRequest {
|
||||
InstanceConfigPatch patch = 1;
|
||||
api.instance.InstanceIdentifier instance = 2;
|
||||
}
|
||||
|
||||
message PatchConfigResponse {}
|
||||
|
||||
message GetConfigRequest {
|
||||
api.instance.InstanceIdentifier instance = 1;
|
||||
}
|
||||
|
||||
message GetConfigResponse {
|
||||
api.manage.NetworkConfig config = 1;
|
||||
}
|
||||
|
||||
service ConfigRpc {
|
||||
rpc PatchConfig(PatchConfigRequest) returns (PatchConfigResponse);
|
||||
rpc GetConfig(GetConfigRequest) returns (GetConfigResponse);
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "common.proto";
|
||||
import "peer_rpc.proto";
|
||||
import "acl.proto";
|
||||
|
||||
package api.instance;
|
||||
|
||||
message InstanceIdentifier {
|
||||
message InstanceSelector { optional string name = 1; }
|
||||
|
||||
oneof selector {
|
||||
common.UUID id = 1;
|
||||
InstanceSelector instance_selector = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message Status {
|
||||
int32 code = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message PeerConnStats {
|
||||
uint64 rx_bytes = 1;
|
||||
uint64 tx_bytes = 2;
|
||||
|
||||
uint64 rx_packets = 3;
|
||||
uint64 tx_packets = 4;
|
||||
|
||||
uint64 latency_us = 5;
|
||||
}
|
||||
|
||||
message PeerConnInfo {
|
||||
string conn_id = 1;
|
||||
uint32 my_peer_id = 2;
|
||||
uint32 peer_id = 3;
|
||||
repeated string features = 4;
|
||||
common.TunnelInfo tunnel = 5;
|
||||
PeerConnStats stats = 6;
|
||||
float loss_rate = 7;
|
||||
bool is_client = 8;
|
||||
string network_name = 9;
|
||||
bool is_closed = 10;
|
||||
bytes noise_local_static_pubkey = 11;
|
||||
bytes noise_remote_static_pubkey = 12;
|
||||
peer_rpc.SecureAuthLevel secure_auth_level = 13;
|
||||
peer_rpc.PeerIdentityType peer_identity_type = 14;
|
||||
}
|
||||
|
||||
message PeerInfo {
|
||||
uint32 peer_id = 1;
|
||||
repeated PeerConnInfo conns = 2;
|
||||
common.UUID default_conn_id = 3;
|
||||
repeated common.UUID directly_connected_conns = 4;
|
||||
}
|
||||
|
||||
message ListPeerRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message ListPeerResponse {
|
||||
repeated PeerInfo peer_infos = 1;
|
||||
NodeInfo my_info = 2;
|
||||
}
|
||||
|
||||
message Route {
|
||||
uint32 peer_id = 1;
|
||||
common.Ipv4Inet ipv4_addr = 2;
|
||||
|
||||
uint32 next_hop_peer_id = 3;
|
||||
int32 cost = 4;
|
||||
int32 path_latency = 11;
|
||||
|
||||
repeated string proxy_cidrs = 5;
|
||||
string hostname = 6;
|
||||
common.StunInfo stun_info = 7;
|
||||
string inst_id = 8;
|
||||
string version = 9;
|
||||
common.PeerFeatureFlag feature_flag = 10;
|
||||
|
||||
optional uint32 next_hop_peer_id_latency_first = 12;
|
||||
optional int32 cost_latency_first = 13;
|
||||
optional int32 path_latency_latency_first = 14;
|
||||
|
||||
common.Ipv6Inet ipv6_addr = 15;
|
||||
common.Ipv6Inet public_ipv6_addr = 16;
|
||||
common.Ipv6Inet ipv6_public_addr_prefix = 17;
|
||||
}
|
||||
|
||||
message PeerRoutePair {
|
||||
Route route = 1;
|
||||
PeerInfo peer = 2;
|
||||
}
|
||||
|
||||
message NodeInfo {
|
||||
uint32 peer_id = 1;
|
||||
string ipv4_addr = 2;
|
||||
repeated string proxy_cidrs = 3;
|
||||
string hostname = 4;
|
||||
common.StunInfo stun_info = 5;
|
||||
string inst_id = 6;
|
||||
repeated string listeners = 7;
|
||||
string config = 8;
|
||||
string version = 9;
|
||||
common.PeerFeatureFlag feature_flag = 10;
|
||||
peer_rpc.GetIpListResponse ip_list = 11;
|
||||
common.Ipv6Inet public_ipv6_addr = 12;
|
||||
common.Ipv6Inet ipv6_public_addr_prefix = 13;
|
||||
}
|
||||
|
||||
message ShowNodeInfoRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message ShowNodeInfoResponse { NodeInfo node_info = 1; }
|
||||
|
||||
message PublicIpv6LeaseInfo {
|
||||
uint32 peer_id = 1;
|
||||
string inst_id = 2;
|
||||
common.Ipv6Inet leased_addr = 3;
|
||||
int64 valid_until_unix_seconds = 4;
|
||||
bool reused = 5;
|
||||
}
|
||||
|
||||
message ListPublicIpv6InfoRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message ListPublicIpv6InfoResponse {
|
||||
common.Ipv6Inet provider_prefix = 1;
|
||||
repeated PublicIpv6LeaseInfo provider_leases = 2;
|
||||
}
|
||||
|
||||
message ListRouteRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message ListRouteResponse { repeated Route routes = 1; }
|
||||
|
||||
message DumpRouteRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message DumpRouteResponse { string result = 1; }
|
||||
|
||||
message ListForeignNetworkRequest {
|
||||
InstanceIdentifier instance = 1;
|
||||
bool include_trusted_keys = 2;
|
||||
}
|
||||
|
||||
enum TrustedKeySourcePb {
|
||||
TRUSTED_KEY_SOURCE_PB_UNSPECIFIED = 0;
|
||||
TRUSTED_KEY_SOURCE_PB_OSPF_NODE = 1;
|
||||
TRUSTED_KEY_SOURCE_PB_OSPF_CREDENTIAL = 2;
|
||||
}
|
||||
|
||||
message TrustedKeyInfoPb {
|
||||
bytes pubkey = 1;
|
||||
TrustedKeySourcePb source = 2;
|
||||
optional int64 expiry_unix = 3;
|
||||
}
|
||||
|
||||
message ForeignNetworkEntryPb {
|
||||
repeated PeerInfo peers = 1;
|
||||
bytes network_secret_digest = 2;
|
||||
uint32 my_peer_id_for_this_network = 3;
|
||||
repeated TrustedKeyInfoPb trusted_keys = 4;
|
||||
}
|
||||
|
||||
message ListForeignNetworkResponse {
|
||||
// foreign network in local
|
||||
map<string, ForeignNetworkEntryPb> foreign_networks = 1;
|
||||
}
|
||||
|
||||
message ListGlobalForeignNetworkRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message ListGlobalForeignNetworkResponse {
|
||||
// foreign network in the entire network
|
||||
message OneForeignNetwork {
|
||||
string network_name = 1;
|
||||
repeated uint32 peer_ids = 2;
|
||||
string last_updated = 3;
|
||||
uint32 version = 4;
|
||||
}
|
||||
|
||||
message ForeignNetworks { repeated OneForeignNetwork foreign_networks = 1; }
|
||||
|
||||
map<uint32, ForeignNetworks> foreign_networks = 1;
|
||||
}
|
||||
|
||||
message GetForeignNetworkSummaryRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message GetForeignNetworkSummaryResponse {
|
||||
peer_rpc.RouteForeignNetworkSummary summary = 1;
|
||||
}
|
||||
|
||||
service PeerManageRpc {
|
||||
rpc ListPeer(ListPeerRequest) returns (ListPeerResponse);
|
||||
rpc ListPublicIpv6Info(ListPublicIpv6InfoRequest)
|
||||
returns (ListPublicIpv6InfoResponse);
|
||||
rpc ListRoute(ListRouteRequest) returns (ListRouteResponse);
|
||||
rpc DumpRoute(DumpRouteRequest) returns (DumpRouteResponse);
|
||||
rpc ListForeignNetwork(ListForeignNetworkRequest)
|
||||
returns (ListForeignNetworkResponse);
|
||||
rpc ListGlobalForeignNetwork(ListGlobalForeignNetworkRequest)
|
||||
returns (ListGlobalForeignNetworkResponse);
|
||||
rpc ShowNodeInfo(ShowNodeInfoRequest) returns (ShowNodeInfoResponse);
|
||||
rpc GetForeignNetworkSummary(GetForeignNetworkSummaryRequest)
|
||||
returns (GetForeignNetworkSummaryResponse);
|
||||
}
|
||||
|
||||
enum ConnectorStatus {
|
||||
CONNECTED = 0;
|
||||
DISCONNECTED = 1;
|
||||
CONNECTING = 2;
|
||||
}
|
||||
|
||||
message Connector {
|
||||
common.Url url = 1;
|
||||
ConnectorStatus status = 2;
|
||||
}
|
||||
|
||||
message ListConnectorRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message ListConnectorResponse { repeated Connector connectors = 1; }
|
||||
|
||||
service ConnectorManageRpc {
|
||||
rpc ListConnector(ListConnectorRequest) returns (ListConnectorResponse);
|
||||
}
|
||||
|
||||
message MappedListener { common.Url url = 1; }
|
||||
|
||||
message ListMappedListenerRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message ListMappedListenerResponse {
|
||||
repeated MappedListener mappedlisteners = 1;
|
||||
}
|
||||
|
||||
service MappedListenerManageRpc {
|
||||
rpc ListMappedListener(ListMappedListenerRequest)
|
||||
returns (ListMappedListenerResponse);
|
||||
}
|
||||
|
||||
message VpnPortalInfo {
|
||||
string vpn_type = 1;
|
||||
string client_config = 2;
|
||||
repeated string connected_clients = 3;
|
||||
}
|
||||
|
||||
message GetVpnPortalInfoRequest { InstanceIdentifier instance = 1; }
|
||||
message GetVpnPortalInfoResponse { VpnPortalInfo vpn_portal_info = 1; }
|
||||
|
||||
service VpnPortalRpc {
|
||||
rpc GetVpnPortalInfo(GetVpnPortalInfoRequest)
|
||||
returns (GetVpnPortalInfoResponse);
|
||||
}
|
||||
|
||||
enum TcpProxyEntryTransportType {
|
||||
TCP = 0;
|
||||
KCP = 1;
|
||||
QUIC = 2;
|
||||
}
|
||||
|
||||
enum TcpProxyEntryState {
|
||||
Unknown = 0;
|
||||
// receive syn packet but not start connecting to dst
|
||||
SynReceived = 1;
|
||||
// connecting to dst
|
||||
ConnectingDst = 2;
|
||||
// connected to dst
|
||||
Connected = 3;
|
||||
// connection closed
|
||||
Closed = 4;
|
||||
// closing src
|
||||
ClosingSrc = 5;
|
||||
// closing dst
|
||||
ClosingDst = 6;
|
||||
}
|
||||
|
||||
message TcpProxyEntry {
|
||||
common.SocketAddr src = 1;
|
||||
common.SocketAddr dst = 2;
|
||||
uint64 start_time = 3;
|
||||
TcpProxyEntryState state = 4;
|
||||
TcpProxyEntryTransportType transport_type = 5;
|
||||
}
|
||||
|
||||
message ListTcpProxyEntryRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message ListTcpProxyEntryResponse { repeated TcpProxyEntry entries = 1; }
|
||||
|
||||
service TcpProxyRpc {
|
||||
rpc ListTcpProxyEntry(ListTcpProxyEntryRequest)
|
||||
returns (ListTcpProxyEntryResponse);
|
||||
}
|
||||
|
||||
message GetAclStatsRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message GetAclStatsResponse { acl.AclStats acl_stats = 1; }
|
||||
|
||||
service AclManageRpc {
|
||||
rpc GetAclStats(GetAclStatsRequest) returns (GetAclStatsResponse);
|
||||
rpc GetWhitelist(GetWhitelistRequest) returns (GetWhitelistResponse);
|
||||
}
|
||||
|
||||
message GetWhitelistRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message GetWhitelistResponse {
|
||||
repeated string tcp_ports = 1;
|
||||
repeated string udp_ports = 2;
|
||||
}
|
||||
|
||||
message ListPortForwardRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message ListPortForwardResponse {
|
||||
repeated common.PortForwardConfigPb cfgs = 1;
|
||||
}
|
||||
|
||||
service PortForwardManageRpc {
|
||||
rpc ListPortForward(ListPortForwardRequest) returns (ListPortForwardResponse);
|
||||
}
|
||||
|
||||
message MetricSnapshot {
|
||||
string name = 1;
|
||||
uint64 value = 2;
|
||||
map<string, string> labels = 3;
|
||||
}
|
||||
|
||||
message GetStatsRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message GetStatsResponse { repeated MetricSnapshot metrics = 1; }
|
||||
|
||||
message GetPrometheusStatsRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message GetPrometheusStatsResponse { string prometheus_text = 1; }
|
||||
|
||||
service StatsRpc {
|
||||
rpc GetStats(GetStatsRequest) returns (GetStatsResponse);
|
||||
rpc GetPrometheusStats(GetPrometheusStatsRequest)
|
||||
returns (GetPrometheusStatsResponse);
|
||||
}
|
||||
|
||||
// Credential management messages
|
||||
|
||||
message GenerateCredentialRequest {
|
||||
repeated string groups = 1; // optional: ACL groups for this credential
|
||||
bool allow_relay = 2; // optional: allow relay through credential node
|
||||
repeated string allowed_proxy_cidrs = 3; // optional: restrict proxy_cidrs
|
||||
int64 ttl_seconds = 4; // must be > 0: credential TTL in seconds (0 / omitted is invalid)
|
||||
optional string credential_id = 5; // optional: user-specified credential id, reused if already exists
|
||||
InstanceIdentifier instance = 6; // target network instance
|
||||
optional bool reusable = 7; // default true: allow multiple peers to reuse this credential
|
||||
}
|
||||
|
||||
message GenerateCredentialResponse {
|
||||
string credential_id = 1; // UUID
|
||||
string credential_secret = 2; // private key base64
|
||||
}
|
||||
|
||||
message RevokeCredentialRequest {
|
||||
string credential_id = 1;
|
||||
InstanceIdentifier instance = 2; // target network instance
|
||||
}
|
||||
|
||||
message RevokeCredentialResponse {
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
message ListCredentialsRequest {
|
||||
InstanceIdentifier instance = 1; // target network instance
|
||||
}
|
||||
|
||||
message CredentialInfo {
|
||||
string credential_id = 1; // UUID
|
||||
repeated string groups = 2;
|
||||
bool allow_relay = 3;
|
||||
int64 expiry_unix = 4;
|
||||
repeated string allowed_proxy_cidrs = 5;
|
||||
optional bool reusable = 6;
|
||||
}
|
||||
|
||||
message ListCredentialsResponse {
|
||||
repeated CredentialInfo credentials = 1;
|
||||
}
|
||||
|
||||
service CredentialManageRpc {
|
||||
rpc GenerateCredential(GenerateCredentialRequest) returns (GenerateCredentialResponse);
|
||||
rpc RevokeCredential(RevokeCredentialRequest) returns (RevokeCredentialResponse);
|
||||
rpc ListCredentials(ListCredentialsRequest) returns (ListCredentialsResponse);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package api.logger;
|
||||
|
||||
enum LogLevel {
|
||||
DISABLED = 0;
|
||||
ERROR = 1;
|
||||
WARNING = 2;
|
||||
INFO = 3;
|
||||
DEBUG = 4;
|
||||
TRACE = 5;
|
||||
}
|
||||
|
||||
message SetLoggerConfigRequest { LogLevel level = 1; }
|
||||
|
||||
message SetLoggerConfigResponse {}
|
||||
|
||||
message GetLoggerConfigRequest {}
|
||||
|
||||
message GetLoggerConfigResponse { LogLevel level = 1; }
|
||||
service LoggerRpc {
|
||||
rpc SetLoggerConfig(SetLoggerConfigRequest) returns (SetLoggerConfigResponse);
|
||||
rpc GetLoggerConfig(GetLoggerConfigRequest) returns (GetLoggerConfigResponse);
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "common.proto";
|
||||
import "peer_rpc.proto";
|
||||
import "api_instance.proto";
|
||||
import "acl.proto";
|
||||
|
||||
package api.manage;
|
||||
|
||||
enum NetworkingMethod {
|
||||
PublicServer = 0;
|
||||
Manual = 1;
|
||||
Standalone = 2;
|
||||
}
|
||||
|
||||
enum ConfigSource {
|
||||
ConfigSourceUnspecified = 0;
|
||||
ConfigSourceUser = 1;
|
||||
ConfigSourceWeb = 2;
|
||||
}
|
||||
|
||||
message NetworkConfig {
|
||||
optional string instance_id = 1;
|
||||
|
||||
optional bool dhcp = 2;
|
||||
optional string virtual_ipv4 = 3;
|
||||
optional int32 network_length = 4;
|
||||
optional string hostname = 5;
|
||||
optional string network_name = 6;
|
||||
optional string network_secret = 7;
|
||||
optional NetworkingMethod networking_method = 8;
|
||||
|
||||
optional string public_server_url = 9;
|
||||
repeated string peer_urls = 10;
|
||||
|
||||
repeated string proxy_cidrs = 11;
|
||||
|
||||
optional bool enable_vpn_portal = 12;
|
||||
optional int32 vpn_portal_listen_port = 13;
|
||||
optional string vpn_portal_client_network_addr = 14;
|
||||
optional int32 vpn_portal_client_network_len = 15;
|
||||
|
||||
optional bool advanced_settings = 16;
|
||||
|
||||
repeated string listener_urls = 17;
|
||||
// optional int32 rpc_port = 18;
|
||||
optional bool latency_first = 19;
|
||||
|
||||
optional string dev_name = 20;
|
||||
|
||||
optional bool use_smoltcp = 21;
|
||||
optional bool disable_ipv6 = 47;
|
||||
optional bool enable_kcp_proxy = 22;
|
||||
optional bool disable_kcp_input = 23;
|
||||
optional bool disable_p2p = 24;
|
||||
optional bool bind_device = 25;
|
||||
optional bool no_tun = 26;
|
||||
|
||||
optional bool enable_exit_node = 27;
|
||||
optional bool relay_all_peer_rpc = 28;
|
||||
optional bool multi_thread = 29;
|
||||
optional bool enable_relay_network_whitelist = 30;
|
||||
repeated string relay_network_whitelist = 31;
|
||||
optional bool enable_manual_routes = 32;
|
||||
repeated string routes = 33;
|
||||
repeated string exit_nodes = 34;
|
||||
optional bool proxy_forward_by_system = 35;
|
||||
optional bool disable_encryption = 36;
|
||||
optional bool enable_socks5 = 37;
|
||||
optional int32 socks5_port = 38;
|
||||
optional bool disable_udp_hole_punching = 39;
|
||||
optional int32 mtu = 40;
|
||||
repeated string mapped_listeners = 41;
|
||||
|
||||
optional bool enable_magic_dns = 42;
|
||||
optional bool enable_private_mode = 43;
|
||||
|
||||
// repeated string rpc_portal_whitelists = 44;
|
||||
|
||||
optional bool enable_quic_proxy = 45;
|
||||
optional bool disable_quic_input = 46;
|
||||
optional int32 quic_listen_port = 50 [deprecated = true];
|
||||
repeated PortForwardConfig port_forwards = 48;
|
||||
|
||||
optional bool disable_sym_hole_punching = 49;
|
||||
|
||||
optional bool p2p_only = 51;
|
||||
optional common.CompressionAlgoPb data_compress_algo = 52;
|
||||
optional string encryption_algorithm = 53;
|
||||
optional bool disable_tcp_hole_punching = 54;
|
||||
|
||||
common.SecureModeConfig secure_mode = 55;
|
||||
optional acl.Acl acl = 56;
|
||||
optional string credential_file = 57;
|
||||
optional bool lazy_p2p = 58;
|
||||
optional bool need_p2p = 59;
|
||||
optional uint64 instance_recv_bps_limit = 60;
|
||||
optional bool disable_upnp = 61;
|
||||
optional bool ipv6_public_addr_provider = 62;
|
||||
optional bool ipv6_public_addr_auto = 63;
|
||||
optional string ipv6_public_addr_prefix = 64;
|
||||
optional bool disable_relay_data = 65;
|
||||
optional bool enable_udp_broadcast_relay = 66;
|
||||
optional uint32 socket_mark = 67;
|
||||
repeated NetworkPeerConfig peers = 68;
|
||||
}
|
||||
|
||||
message NetworkPeerConfig {
|
||||
string uri = 1;
|
||||
optional string peer_public_key = 2;
|
||||
}
|
||||
|
||||
message PortForwardConfig {
|
||||
string bind_ip = 1;
|
||||
uint32 bind_port = 2;
|
||||
string dst_ip = 3;
|
||||
uint32 dst_port = 4;
|
||||
string proto = 5;
|
||||
}
|
||||
|
||||
message MyNodeInfo {
|
||||
common.Ipv4Inet virtual_ipv4 = 1;
|
||||
string hostname = 2;
|
||||
string version = 3;
|
||||
peer_rpc.GetIpListResponse ips = 4;
|
||||
common.StunInfo stun_info = 5;
|
||||
repeated common.Url listeners = 6;
|
||||
optional string vpn_portal_cfg = 7;
|
||||
uint32 peer_id = 8;
|
||||
}
|
||||
|
||||
message NetworkInstanceRunningInfo {
|
||||
string dev_name = 1;
|
||||
MyNodeInfo my_node_info = 2;
|
||||
repeated string events = 3;
|
||||
repeated api.instance.Route routes = 4;
|
||||
repeated api.instance.PeerInfo peers = 5;
|
||||
repeated api.instance.PeerRoutePair peer_route_pairs = 6;
|
||||
bool running = 7;
|
||||
optional string error_msg = 8;
|
||||
peer_rpc.RouteForeignNetworkSummary foreign_network_summary = 9;
|
||||
}
|
||||
|
||||
message NetworkInstanceRunningInfoMap {
|
||||
map<string, NetworkInstanceRunningInfo> map = 1;
|
||||
}
|
||||
|
||||
message NetworkMeta {
|
||||
common.UUID inst_id = 1;
|
||||
string network_name = 2;
|
||||
uint32 config_permission = 3;
|
||||
string instance_name = 4;
|
||||
ConfigSource source = 5;
|
||||
}
|
||||
|
||||
message ValidateConfigRequest { NetworkConfig config = 1; }
|
||||
|
||||
message ValidateConfigResponse { string toml_config = 1; }
|
||||
|
||||
message RunNetworkInstanceRequest {
|
||||
common.UUID inst_id = 1;
|
||||
NetworkConfig config = 2;
|
||||
bool overwrite = 3;
|
||||
ConfigSource source = 4;
|
||||
}
|
||||
|
||||
message RunNetworkInstanceResponse { common.UUID inst_id = 1; }
|
||||
|
||||
message RetainNetworkInstanceRequest { repeated common.UUID inst_ids = 1; }
|
||||
|
||||
message RetainNetworkInstanceResponse {
|
||||
repeated common.UUID remain_inst_ids = 1;
|
||||
}
|
||||
|
||||
message CollectNetworkInfoRequest { repeated common.UUID inst_ids = 1; }
|
||||
|
||||
message CollectNetworkInfoResponse { NetworkInstanceRunningInfoMap info = 1; }
|
||||
|
||||
message ListNetworkInstanceRequest {}
|
||||
|
||||
message ListNetworkInstanceResponse { repeated common.UUID inst_ids = 1; }
|
||||
|
||||
message DeleteNetworkInstanceRequest { repeated common.UUID inst_ids = 1; }
|
||||
|
||||
message DeleteNetworkInstanceResponse {
|
||||
repeated common.UUID remain_inst_ids = 1;
|
||||
}
|
||||
|
||||
message GetNetworkInstanceConfigRequest { common.UUID inst_id = 1; }
|
||||
|
||||
message GetNetworkInstanceConfigResponse {
|
||||
NetworkConfig config = 1;
|
||||
ConfigSource source = 2;
|
||||
}
|
||||
|
||||
message ListNetworkInstanceMetaRequest { repeated common.UUID inst_ids = 1; }
|
||||
|
||||
message ListNetworkInstanceMetaResponse { repeated NetworkMeta metas = 1; }
|
||||
|
||||
service WebClientService {
|
||||
rpc ValidateConfig(ValidateConfigRequest) returns (ValidateConfigResponse) {}
|
||||
rpc RunNetworkInstance(RunNetworkInstanceRequest)
|
||||
returns (RunNetworkInstanceResponse) {}
|
||||
rpc RetainNetworkInstance(RetainNetworkInstanceRequest)
|
||||
returns (RetainNetworkInstanceResponse) {}
|
||||
rpc CollectNetworkInfo(CollectNetworkInfoRequest)
|
||||
returns (CollectNetworkInfoResponse) {}
|
||||
rpc ListNetworkInstance(ListNetworkInstanceRequest)
|
||||
returns (ListNetworkInstanceResponse) {}
|
||||
rpc DeleteNetworkInstance(DeleteNetworkInstanceRequest)
|
||||
returns (DeleteNetworkInstanceResponse) {}
|
||||
rpc GetNetworkInstanceConfig(GetNetworkInstanceConfigRequest)
|
||||
returns (GetNetworkInstanceConfigResponse) {}
|
||||
rpc ListNetworkInstanceMeta(ListNetworkInstanceMetaRequest)
|
||||
returns (ListNetworkInstanceMetaResponse) {}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "error.proto";
|
||||
|
||||
package common;
|
||||
|
||||
message FlagsInConfig {
|
||||
string default_protocol = 1;
|
||||
string dev_name = 2;
|
||||
bool enable_encryption = 3;
|
||||
bool enable_ipv6 = 4;
|
||||
uint32 mtu = 5;
|
||||
bool latency_first = 6;
|
||||
bool enable_exit_node = 7;
|
||||
bool no_tun = 8;
|
||||
bool use_smoltcp = 9;
|
||||
string relay_network_whitelist = 10;
|
||||
bool disable_p2p = 11;
|
||||
bool relay_all_peer_rpc = 12;
|
||||
bool disable_udp_hole_punching = 13;
|
||||
// string ipv6_listener = 14; [deprecated = true]; use -l udp://[::]:12345
|
||||
// instead
|
||||
bool multi_thread = 15;
|
||||
CompressionAlgoPb data_compress_algo = 16;
|
||||
bool bind_device = 17;
|
||||
|
||||
// should we convert all tcp streams into kcp streams
|
||||
bool enable_kcp_proxy = 18;
|
||||
// does this peer allow kcp input
|
||||
bool disable_kcp_input = 19;
|
||||
// disable relay local network kcp packets
|
||||
bool disable_relay_kcp = 20;
|
||||
bool proxy_forward_by_system = 21;
|
||||
|
||||
// enable magic dns or not
|
||||
bool accept_dns = 22;
|
||||
// enable private mode
|
||||
bool private_mode = 23;
|
||||
|
||||
// should we convert all tcp streams into quic streams
|
||||
bool enable_quic_proxy = 24;
|
||||
// does this peer allow quic input
|
||||
bool disable_quic_input = 25;
|
||||
// disable relay local network quic packets
|
||||
bool disable_relay_quic = 35;
|
||||
|
||||
// quic listen port
|
||||
uint32 quic_listen_port = 33 [deprecated = true];
|
||||
|
||||
// a global relay limit, only work for foreign network
|
||||
uint64 foreign_relay_bps_limit = 26;
|
||||
|
||||
uint32 multi_thread_count = 27;
|
||||
|
||||
// enable relay foreign network kcp packets
|
||||
bool enable_relay_foreign_network_kcp = 28;
|
||||
|
||||
// enable relay foreign network quic packets
|
||||
bool enable_relay_foreign_network_quic = 36;
|
||||
|
||||
// encryption algorithm to use, empty string means default (aes-gcm)
|
||||
string encryption_algorithm = 29;
|
||||
|
||||
// disable symmetric nat hole punching, treat symmetric as cone when enabled
|
||||
bool disable_sym_hole_punching = 30;
|
||||
|
||||
// tld dns zone for magic dns
|
||||
string tld_dns_zone = 31;
|
||||
|
||||
bool p2p_only = 32;
|
||||
|
||||
bool disable_tcp_hole_punching = 34;
|
||||
|
||||
bool lazy_p2p = 37;
|
||||
bool need_p2p = 38;
|
||||
uint64 instance_recv_bps_limit = 39;
|
||||
bool disable_upnp = 40;
|
||||
bool disable_relay_data = 41;
|
||||
bool enable_udp_broadcast_relay = 42;
|
||||
|
||||
// Linux-only: SO_MARK (fwmark) value applied to every outbound underlay
|
||||
// socket (TCP/UDP/QUIC/WS/WG connectors and listeners). Unset = leave
|
||||
// SO_MARK untouched (kernel default 0). Any set value (including 0) is
|
||||
// applied via setsockopt. Requires CAP_NET_ADMIN; silently ignored on
|
||||
// non-Linux platforms.
|
||||
optional uint32 socket_mark = 43;
|
||||
}
|
||||
|
||||
message RpcDescriptor {
|
||||
// allow same service registered multiple times in different domain
|
||||
string domain_name = 1;
|
||||
|
||||
string proto_name = 2;
|
||||
string service_name = 3;
|
||||
uint32 method_index = 4;
|
||||
}
|
||||
|
||||
message RpcRequest {
|
||||
RpcDescriptor descriptor = 1 [ deprecated = true ];
|
||||
|
||||
bytes request = 2;
|
||||
int32 timeout_ms = 3;
|
||||
}
|
||||
|
||||
message RpcResponse {
|
||||
bytes response = 1;
|
||||
error.Error error = 2;
|
||||
|
||||
uint64 runtime_us = 3;
|
||||
}
|
||||
|
||||
enum CompressionAlgoPb {
|
||||
Invalid = 0;
|
||||
None = 1;
|
||||
Zstd = 2;
|
||||
}
|
||||
|
||||
message RpcCompressionInfo {
|
||||
// use this to compress the content
|
||||
CompressionAlgoPb algo = 1;
|
||||
|
||||
// tell the peer which compression algo is used to compress the next
|
||||
// response/request
|
||||
CompressionAlgoPb accepted_algo = 2;
|
||||
}
|
||||
|
||||
message RpcPacket {
|
||||
uint32 from_peer = 1;
|
||||
uint32 to_peer = 2;
|
||||
int64 transaction_id = 3;
|
||||
|
||||
RpcDescriptor descriptor = 4;
|
||||
bytes body = 5;
|
||||
bool is_request = 6;
|
||||
|
||||
uint32 total_pieces = 7;
|
||||
uint32 piece_idx = 8;
|
||||
|
||||
int32 trace_id = 9;
|
||||
|
||||
RpcCompressionInfo compression_info = 10;
|
||||
}
|
||||
|
||||
message Void {}
|
||||
|
||||
message UUID {
|
||||
uint32 part1 = 1;
|
||||
uint32 part2 = 2;
|
||||
uint32 part3 = 3;
|
||||
uint32 part4 = 4;
|
||||
}
|
||||
|
||||
enum NatType {
|
||||
// has NAT; but own a single public IP, port is not changed
|
||||
Unknown = 0;
|
||||
OpenInternet = 1;
|
||||
NoPAT = 2;
|
||||
FullCone = 3;
|
||||
Restricted = 4;
|
||||
PortRestricted = 5;
|
||||
Symmetric = 6;
|
||||
SymUdpFirewall = 7;
|
||||
SymmetricEasyInc = 8;
|
||||
SymmetricEasyDec = 9;
|
||||
}
|
||||
|
||||
message Ipv4Addr { uint32 addr = 1; }
|
||||
|
||||
message Ipv6Addr {
|
||||
uint32 part1 = 1;
|
||||
uint32 part2 = 2;
|
||||
uint32 part3 = 3;
|
||||
uint32 part4 = 4;
|
||||
}
|
||||
|
||||
message IpAddr {
|
||||
oneof ip {
|
||||
Ipv4Addr ipv4 = 1;
|
||||
Ipv6Addr ipv6 = 2;
|
||||
};
|
||||
}
|
||||
|
||||
message Ipv4Inet {
|
||||
Ipv4Addr address = 1;
|
||||
uint32 network_length = 2;
|
||||
}
|
||||
|
||||
message Ipv6Inet {
|
||||
Ipv6Addr address = 1;
|
||||
uint32 network_length = 2;
|
||||
}
|
||||
|
||||
message IpInet {
|
||||
oneof ip {
|
||||
Ipv4Inet ipv4 = 1;
|
||||
Ipv6Inet ipv6 = 2;
|
||||
};
|
||||
}
|
||||
|
||||
message Url { string url = 1; }
|
||||
|
||||
message SocketAddr {
|
||||
oneof ip {
|
||||
Ipv4Addr ipv4 = 1;
|
||||
Ipv6Addr ipv6 = 2;
|
||||
};
|
||||
uint32 port = 3;
|
||||
}
|
||||
|
||||
message TunnelInfo {
|
||||
string tunnel_type = 1;
|
||||
common.Url local_addr = 2;
|
||||
common.Url remote_addr = 3;
|
||||
common.Url resolved_remote_addr = 4;
|
||||
}
|
||||
|
||||
message StunInfo {
|
||||
NatType udp_nat_type = 1;
|
||||
NatType tcp_nat_type = 2;
|
||||
int64 last_update_time = 3;
|
||||
repeated string public_ip = 4;
|
||||
uint32 min_port = 5;
|
||||
uint32 max_port = 6;
|
||||
}
|
||||
|
||||
message PeerFeatureFlag {
|
||||
bool is_public_server = 1;
|
||||
bool avoid_relay_data = 2;
|
||||
bool kcp_input = 3;
|
||||
bool no_relay_kcp = 4;
|
||||
bool support_conn_list_sync = 5;
|
||||
bool quic_input = 6;
|
||||
bool no_relay_quic = 7;
|
||||
bool is_credential_peer = 8;
|
||||
bool need_p2p = 9;
|
||||
bool disable_p2p = 10;
|
||||
bool ipv6_public_addr_provider = 11;
|
||||
}
|
||||
|
||||
enum SocketType {
|
||||
TCP = 0;
|
||||
UDP = 1;
|
||||
}
|
||||
|
||||
message PortForwardConfigPb {
|
||||
SocketAddr bind_addr = 1;
|
||||
SocketAddr dst_addr = 2;
|
||||
SocketType socket_type = 3;
|
||||
}
|
||||
|
||||
message ProxyDstInfo { SocketAddr dst_addr = 1; }
|
||||
|
||||
message LimiterConfig {
|
||||
optional uint64 burst_rate =
|
||||
1; // default 1 means no burst (capacity is same with bps)
|
||||
optional uint64 bps = 2; // default 0 means no limit (unit is B/s)
|
||||
optional uint64 fill_duration_ms =
|
||||
3; // default 10ms, the period to fill the bucket
|
||||
}
|
||||
|
||||
message SecureModeConfig {
|
||||
bool enabled = 1;
|
||||
|
||||
// base64(X25519 private key), used by shared node to present a stable identity
|
||||
optional string local_private_key = 2;
|
||||
|
||||
// base64(X25519 public key), required if local_private_key is set
|
||||
optional string local_public_key = 3;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "common.proto";
|
||||
|
||||
package core_config;
|
||||
|
||||
message CoreConfig {
|
||||
NodeConfig node = 1;
|
||||
RouteConfig routes = 2;
|
||||
PeerPolicyConfig peer_policy = 3;
|
||||
TrafficConfig traffic = 4;
|
||||
}
|
||||
|
||||
message NodeConfig {
|
||||
optional uint32 peer_id = 1;
|
||||
optional common.UUID instance_id = 2;
|
||||
optional string hostname = 3;
|
||||
string network_name = 4;
|
||||
}
|
||||
|
||||
message RouteConfig {
|
||||
optional IpPrefix ipv4 = 1;
|
||||
optional IpPrefix ipv6 = 2;
|
||||
repeated IpPrefix advertised_routes = 3;
|
||||
repeated ProxyNetworkConfig proxy_networks = 4;
|
||||
repeated ForeignNetworkConfig foreign_networks = 5;
|
||||
}
|
||||
|
||||
message IpPrefix {
|
||||
common.IpAddr address = 1;
|
||||
uint32 prefix_len = 2;
|
||||
}
|
||||
|
||||
message ProxyNetworkConfig {
|
||||
IpPrefix real = 1;
|
||||
optional IpPrefix mapped = 2;
|
||||
}
|
||||
|
||||
message ForeignNetworkConfig {
|
||||
string name = 1;
|
||||
repeated IpPrefix cidrs = 2;
|
||||
}
|
||||
|
||||
message PeerPolicyConfig {
|
||||
optional bool p2p_enabled = 1;
|
||||
optional bool relay_peer_rpc = 2;
|
||||
optional bool relay_data = 3;
|
||||
optional bool latency_first = 4;
|
||||
optional bool encryption_required = 5;
|
||||
}
|
||||
|
||||
message TrafficConfig {
|
||||
optional uint32 mtu = 1;
|
||||
optional uint64 instance_recv_bps_limit = 2;
|
||||
optional uint64 foreign_relay_bps_limit = 3;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "common.proto";
|
||||
import "peer_rpc.proto";
|
||||
|
||||
package core.peer;
|
||||
|
||||
message PeerConnStats {
|
||||
uint64 rx_bytes = 1;
|
||||
uint64 tx_bytes = 2;
|
||||
|
||||
uint64 rx_packets = 3;
|
||||
uint64 tx_packets = 4;
|
||||
|
||||
uint64 latency_us = 5;
|
||||
}
|
||||
|
||||
message PeerConnInfo {
|
||||
string conn_id = 1;
|
||||
uint32 my_peer_id = 2;
|
||||
uint32 peer_id = 3;
|
||||
repeated string features = 4;
|
||||
common.TunnelInfo tunnel = 5;
|
||||
PeerConnStats stats = 6;
|
||||
float loss_rate = 7;
|
||||
bool is_client = 8;
|
||||
string network_name = 9;
|
||||
bool is_closed = 10;
|
||||
bytes noise_local_static_pubkey = 11;
|
||||
bytes noise_remote_static_pubkey = 12;
|
||||
peer_rpc.SecureAuthLevel secure_auth_level = 13;
|
||||
peer_rpc.PeerIdentityType peer_identity_type = 14;
|
||||
}
|
||||
|
||||
message PeerInfo {
|
||||
uint32 peer_id = 1;
|
||||
repeated PeerConnInfo conns = 2;
|
||||
common.UUID default_conn_id = 3;
|
||||
repeated common.UUID directly_connected_conns = 4;
|
||||
}
|
||||
|
||||
message Route {
|
||||
uint32 peer_id = 1;
|
||||
common.Ipv4Inet ipv4_addr = 2;
|
||||
|
||||
uint32 next_hop_peer_id = 3;
|
||||
int32 cost = 4;
|
||||
int32 path_latency = 11;
|
||||
|
||||
repeated string proxy_cidrs = 5;
|
||||
string hostname = 6;
|
||||
common.StunInfo stun_info = 7;
|
||||
string inst_id = 8;
|
||||
string version = 9;
|
||||
common.PeerFeatureFlag feature_flag = 10;
|
||||
|
||||
optional uint32 next_hop_peer_id_latency_first = 12;
|
||||
optional int32 cost_latency_first = 13;
|
||||
optional int32 path_latency_latency_first = 14;
|
||||
|
||||
common.Ipv6Inet ipv6_addr = 15;
|
||||
common.Ipv6Inet public_ipv6_addr = 16;
|
||||
common.Ipv6Inet ipv6_public_addr_prefix = 17;
|
||||
}
|
||||
|
||||
message PublicIpv6LeaseInfo {
|
||||
uint32 peer_id = 1;
|
||||
string inst_id = 2;
|
||||
common.Ipv6Inet leased_addr = 3;
|
||||
int64 valid_until_unix_seconds = 4;
|
||||
bool reused = 5;
|
||||
}
|
||||
|
||||
message ListPublicIpv6InfoResponse {
|
||||
common.Ipv6Inet provider_prefix = 1;
|
||||
repeated PublicIpv6LeaseInfo provider_leases = 2;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
syntax = "proto3";
|
||||
package error;
|
||||
|
||||
message OtherError { string error_message = 1; }
|
||||
|
||||
message InvalidMethodIndex {
|
||||
string service_name = 1;
|
||||
uint32 method_index = 2;
|
||||
}
|
||||
|
||||
message InvalidService { string service_name = 1; }
|
||||
|
||||
message ProstDecodeError {}
|
||||
|
||||
message ProstEncodeError {}
|
||||
|
||||
message ExecuteError { string error_message = 1; }
|
||||
|
||||
message MalformatRpcPacket { string error_message = 1; }
|
||||
|
||||
message Timeout { string error_message = 1; }
|
||||
|
||||
message Error {
|
||||
oneof error_kind {
|
||||
OtherError other_error = 1;
|
||||
InvalidMethodIndex invalid_method_index = 2;
|
||||
InvalidService invalid_service = 3;
|
||||
ProstDecodeError prost_decode_error = 4;
|
||||
ProstEncodeError prost_encode_error = 5;
|
||||
ExecuteError execute_error = 6;
|
||||
MalformatRpcPacket malformat_rpc_packet = 7;
|
||||
Timeout timeout = 8;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "common.proto";
|
||||
import "api_instance.proto";
|
||||
|
||||
package magic_dns;
|
||||
|
||||
message DnsRecordA {
|
||||
string name = 1;
|
||||
common.Ipv4Addr value = 2;
|
||||
int32 ttl = 3;
|
||||
}
|
||||
|
||||
message DnsRecordSOA {
|
||||
string name = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message DnsRecord {
|
||||
oneof record {
|
||||
DnsRecordA a = 1;
|
||||
DnsRecordSOA soa = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message DnsRecordList {
|
||||
repeated DnsRecord records = 1;
|
||||
}
|
||||
|
||||
message UpdateDnsRecordRequest {
|
||||
string zone = 1;
|
||||
repeated api.instance.Route routes = 2;
|
||||
}
|
||||
|
||||
message GetDnsRecordResponse {
|
||||
map<string, DnsRecordList> records = 1;
|
||||
}
|
||||
|
||||
message HandshakeRequest {}
|
||||
|
||||
message HandshakeResponse {}
|
||||
|
||||
service MagicDnsServerRpc {
|
||||
rpc Handshake(HandshakeRequest) returns (HandshakeResponse) {}
|
||||
rpc Heartbeat(common.Void) returns (common.Void) {}
|
||||
rpc UpdateDnsRecord(UpdateDnsRecordRequest) returns (common.Void) {}
|
||||
rpc GetDnsRecord(common.Void) returns (GetDnsRecordResponse) {}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "common.proto";
|
||||
|
||||
package peer_rpc;
|
||||
|
||||
message TrustedCredentialPubkey {
|
||||
bytes pubkey = 1; // X25519 public key (32 bytes)
|
||||
repeated string groups = 2; // ACL groups this credential belongs to
|
||||
bool allow_relay = 3; // whether this credential node can relay data
|
||||
int64 expiry_unix = 4; // expiry time (Unix timestamp)
|
||||
repeated string allowed_proxy_cidrs = 5; // allowed proxy_cidrs ranges
|
||||
optional bool reusable = 6; // whether multiple peers may use the same credential concurrently
|
||||
}
|
||||
|
||||
message TrustedCredentialPubkeyProof {
|
||||
TrustedCredentialPubkey credential = 1;
|
||||
bytes credential_hmac = 2;
|
||||
}
|
||||
|
||||
message RoutePeerInfo {
|
||||
// means next hop in route table.
|
||||
uint32 peer_id = 1;
|
||||
common.UUID inst_id = 2;
|
||||
uint32 cost = 3;
|
||||
optional common.Ipv4Addr ipv4_addr = 4;
|
||||
repeated string proxy_cidrs = 5;
|
||||
optional string hostname = 6;
|
||||
common.NatType udp_nat_type = 7;
|
||||
google.protobuf.Timestamp last_update = 8;
|
||||
uint32 version = 9;
|
||||
|
||||
string easytier_version = 10;
|
||||
common.PeerFeatureFlag feature_flag = 11;
|
||||
uint64 peer_route_id = 12;
|
||||
|
||||
uint32 network_length = 13;
|
||||
|
||||
optional uint32 quic_port = 14 [deprecated = true];
|
||||
optional common.Ipv6Inet ipv6_addr = 15;
|
||||
|
||||
repeated PeerGroupInfo groups = 16;
|
||||
|
||||
common.NatType tcp_nat_type = 17;
|
||||
bytes noise_static_pubkey = 18;
|
||||
|
||||
// Trusted credential public keys published by admin nodes (holding network_secret)
|
||||
repeated TrustedCredentialPubkeyProof trusted_credential_pubkeys = 19;
|
||||
|
||||
optional common.Ipv6Inet ipv6_public_addr_prefix = 22;
|
||||
optional common.Ipv6Inet ipv6_public_addr_lease = 24;
|
||||
}
|
||||
|
||||
message PeerIdVersion {
|
||||
uint32 peer_id = 1;
|
||||
uint32 version = 2;
|
||||
}
|
||||
|
||||
message RouteConnBitmap {
|
||||
repeated PeerIdVersion peer_ids = 1;
|
||||
bytes bitmap = 2;
|
||||
}
|
||||
|
||||
message RouteConnPeerList {
|
||||
message PeerConnInfo {
|
||||
PeerIdVersion peer_id = 1;
|
||||
repeated uint32 connected_peer_ids = 2;
|
||||
}
|
||||
repeated PeerConnInfo peer_conn_infos = 1;
|
||||
}
|
||||
|
||||
message RoutePeerInfos { repeated RoutePeerInfo items = 1; }
|
||||
|
||||
message ForeignNetworkRouteInfoKey {
|
||||
uint32 peer_id = 1;
|
||||
string network_name = 2;
|
||||
}
|
||||
|
||||
message ForeignNetworkRouteInfoEntry {
|
||||
repeated uint32 foreign_peer_ids = 1;
|
||||
google.protobuf.Timestamp last_update = 2;
|
||||
uint32 version = 3;
|
||||
bytes network_secret_digest = 4;
|
||||
uint32 my_peer_id_for_this_network = 5;
|
||||
}
|
||||
|
||||
message RouteForeignNetworkInfos {
|
||||
message Info {
|
||||
ForeignNetworkRouteInfoKey key = 1;
|
||||
ForeignNetworkRouteInfoEntry value = 2;
|
||||
}
|
||||
repeated Info infos = 1;
|
||||
}
|
||||
|
||||
message RouteForeignNetworkSummary {
|
||||
message Info {
|
||||
uint32 peer_id = 1;
|
||||
uint32 network_count = 2;
|
||||
uint32 peer_count = 3;
|
||||
}
|
||||
|
||||
map<uint32, Info> info_map = 1;
|
||||
}
|
||||
|
||||
message PeerGroupInfo {
|
||||
string group_name = 1;
|
||||
bytes group_proof = 2;
|
||||
}
|
||||
|
||||
message SyncRouteInfoRequest {
|
||||
uint32 my_peer_id = 1;
|
||||
uint64 my_session_id = 2;
|
||||
bool is_initiator = 3;
|
||||
RoutePeerInfos peer_infos = 4;
|
||||
oneof conn_info {
|
||||
RouteConnBitmap conn_bitmap = 5;
|
||||
RouteConnPeerList conn_peer_list = 7;
|
||||
}
|
||||
RouteForeignNetworkInfos foreign_network_infos = 6;
|
||||
}
|
||||
|
||||
enum SyncRouteInfoError {
|
||||
DuplicatePeerId = 0;
|
||||
Stopped = 1;
|
||||
}
|
||||
|
||||
message SyncRouteInfoResponse {
|
||||
bool is_initiator = 1;
|
||||
uint64 session_id = 2;
|
||||
optional SyncRouteInfoError error = 3;
|
||||
}
|
||||
|
||||
service OspfRouteRpc {
|
||||
// Generates a "hello" greeting based on the supplied info.
|
||||
rpc SyncRouteInfo(SyncRouteInfoRequest) returns (SyncRouteInfoResponse);
|
||||
}
|
||||
|
||||
message AcquireIpv6PublicAddrLeaseRequest {
|
||||
uint32 peer_id = 1;
|
||||
common.UUID inst_id = 2;
|
||||
}
|
||||
|
||||
message RenewIpv6PublicAddrLeaseRequest {
|
||||
uint32 peer_id = 1;
|
||||
common.UUID inst_id = 2;
|
||||
common.Ipv6Inet leased_addr = 3;
|
||||
}
|
||||
|
||||
message ReleaseIpv6PublicAddrLeaseRequest {
|
||||
uint32 peer_id = 1;
|
||||
common.UUID inst_id = 2;
|
||||
}
|
||||
|
||||
message GetIpv6PublicAddrLeaseRequest {
|
||||
uint32 peer_id = 1;
|
||||
common.UUID inst_id = 2;
|
||||
}
|
||||
|
||||
message Ipv6PublicAddrLeaseReply {
|
||||
uint32 provider_peer_id = 1;
|
||||
common.UUID provider_inst_id = 2;
|
||||
common.Ipv6Inet provider_prefix = 3;
|
||||
common.Ipv6Inet leased_addr = 4;
|
||||
google.protobuf.Timestamp valid_until = 5;
|
||||
bool reused = 6;
|
||||
optional string error_msg = 7;
|
||||
}
|
||||
|
||||
service PublicIpv6AddrRpc {
|
||||
rpc AcquireLease(AcquireIpv6PublicAddrLeaseRequest)
|
||||
returns (Ipv6PublicAddrLeaseReply);
|
||||
rpc RenewLease(RenewIpv6PublicAddrLeaseRequest)
|
||||
returns (Ipv6PublicAddrLeaseReply);
|
||||
rpc ReleaseLease(ReleaseIpv6PublicAddrLeaseRequest) returns (common.Void);
|
||||
rpc GetLease(GetIpv6PublicAddrLeaseRequest) returns (Ipv6PublicAddrLeaseReply);
|
||||
}
|
||||
|
||||
message GetIpListRequest {}
|
||||
|
||||
message GetIpListResponse {
|
||||
common.Ipv4Addr public_ipv4 = 1;
|
||||
repeated common.Ipv4Addr interface_ipv4s = 2;
|
||||
common.Ipv6Addr public_ipv6 = 3;
|
||||
repeated common.Ipv6Addr interface_ipv6s = 4;
|
||||
repeated common.Url listeners = 5;
|
||||
}
|
||||
|
||||
message SendUdpHolePunchPacketRequest {
|
||||
common.SocketAddr connector_addr = 1;
|
||||
uint32 listener_port = 2;
|
||||
common.Ipv6Addr preferred_src_ipv6 = 3;
|
||||
repeated common.SocketAddr connector_addrs = 4;
|
||||
}
|
||||
|
||||
service DirectConnectorRpc {
|
||||
rpc GetIpList(GetIpListRequest) returns (GetIpListResponse);
|
||||
rpc SendUdpHolePunchPacket(SendUdpHolePunchPacketRequest) returns (common.Void);
|
||||
}
|
||||
|
||||
message SelectPunchListenerRequest {
|
||||
bool force_new = 1;
|
||||
bool prefer_port_mapping = 2;
|
||||
}
|
||||
|
||||
message SelectPunchListenerResponse {
|
||||
common.SocketAddr listener_mapped_addr = 1;
|
||||
}
|
||||
|
||||
message SendPunchPacketConeRequest {
|
||||
common.SocketAddr listener_mapped_addr = 1;
|
||||
common.SocketAddr dest_addr = 2;
|
||||
uint32 transaction_id = 3;
|
||||
// send this many packets in a batch
|
||||
uint32 packet_count_per_batch = 4;
|
||||
// send total this batch count, total packet count = packet_batch_size * packet_batch_count
|
||||
uint32 packet_batch_count = 5;
|
||||
// interval between each batch
|
||||
uint32 packet_interval_ms = 6;
|
||||
}
|
||||
|
||||
message SendPunchPacketHardSymRequest {
|
||||
common.SocketAddr listener_mapped_addr = 1;
|
||||
|
||||
repeated common.Ipv4Addr public_ips = 2;
|
||||
uint32 transaction_id = 3;
|
||||
uint32 port_index = 4;
|
||||
uint32 round = 5;
|
||||
}
|
||||
|
||||
message SendPunchPacketHardSymResponse { uint32 next_port_index = 1; }
|
||||
|
||||
message SendPunchPacketEasySymRequest {
|
||||
common.SocketAddr listener_mapped_addr = 1;
|
||||
repeated common.Ipv4Addr public_ips = 2;
|
||||
uint32 transaction_id = 3;
|
||||
|
||||
uint32 base_port_num = 4;
|
||||
uint32 max_port_num = 5;
|
||||
bool is_incremental = 6;
|
||||
}
|
||||
|
||||
message SendPunchPacketBothEasySymRequest {
|
||||
uint32 udp_socket_count = 1;
|
||||
common.Ipv4Addr public_ip = 2;
|
||||
uint32 transaction_id = 3;
|
||||
|
||||
uint32 dst_port_num = 4;
|
||||
uint32 wait_time_ms = 5;
|
||||
}
|
||||
|
||||
message SendPunchPacketBothEasySymResponse {
|
||||
// is doing punch with other peer
|
||||
bool is_busy = 1;
|
||||
common.SocketAddr base_mapped_addr = 2;
|
||||
}
|
||||
|
||||
service UdpHolePunchRpc {
|
||||
rpc SelectPunchListener(SelectPunchListenerRequest)
|
||||
returns (SelectPunchListenerResponse);
|
||||
|
||||
// send packet to one remote_addr, used by nat1-3 to nat1-3
|
||||
rpc SendPunchPacketCone(SendPunchPacketConeRequest) returns (common.Void);
|
||||
|
||||
// send packet to multiple remote_addr (birthday attack), used by nat4 to nat1-3
|
||||
rpc SendPunchPacketHardSym(SendPunchPacketHardSymRequest)
|
||||
returns (SendPunchPacketHardSymResponse);
|
||||
rpc SendPunchPacketEasySym(SendPunchPacketEasySymRequest)
|
||||
returns (common.Void);
|
||||
|
||||
// nat4 to nat4 (both predictably)
|
||||
rpc SendPunchPacketBothEasySym(SendPunchPacketBothEasySymRequest)
|
||||
returns (SendPunchPacketBothEasySymResponse);
|
||||
}
|
||||
|
||||
message TcpHolePunchRequest { common.SocketAddr connector_mapped_addr = 1; }
|
||||
|
||||
message TcpHolePunchResponse { common.SocketAddr listener_mapped_addr = 1; }
|
||||
|
||||
service TcpHolePunchRpc {
|
||||
rpc ExchangeMappedAddr(TcpHolePunchRequest) returns (TcpHolePunchResponse);
|
||||
}
|
||||
|
||||
message DirectConnectedPeerInfo { int32 latency_ms = 1; }
|
||||
|
||||
message PeerInfoForGlobalMap {
|
||||
map<uint32, DirectConnectedPeerInfo> direct_peers = 1;
|
||||
}
|
||||
|
||||
message ReportPeersRequest {
|
||||
uint32 my_peer_id = 1;
|
||||
PeerInfoForGlobalMap peer_infos = 2;
|
||||
}
|
||||
|
||||
message ReportPeersResponse {}
|
||||
|
||||
message GlobalPeerMap { map<uint32, PeerInfoForGlobalMap> map = 1; }
|
||||
|
||||
message GetGlobalPeerMapRequest { uint64 digest = 1; }
|
||||
|
||||
message GetGlobalPeerMapResponse {
|
||||
map<uint32, PeerInfoForGlobalMap> global_peer_map = 1;
|
||||
optional uint64 digest = 2;
|
||||
}
|
||||
|
||||
service PeerCenterRpc {
|
||||
rpc ReportPeers(ReportPeersRequest) returns (ReportPeersResponse);
|
||||
rpc GetGlobalPeerMap(GetGlobalPeerMapRequest)
|
||||
returns (GetGlobalPeerMapResponse);
|
||||
}
|
||||
|
||||
message HandshakeRequest {
|
||||
uint32 magic = 1;
|
||||
uint32 my_peer_id = 2;
|
||||
uint32 version = 3;
|
||||
repeated string features = 4;
|
||||
string network_name = 5;
|
||||
bytes network_secret_digest = 6;
|
||||
}
|
||||
|
||||
message KcpConnData {
|
||||
common.SocketAddr src = 1;
|
||||
common.SocketAddr dst = 4;
|
||||
}
|
||||
|
||||
enum SecureAuthLevel {
|
||||
None = 0;
|
||||
EncryptedUnauthenticated = 1;
|
||||
PeerVerified = 2;
|
||||
NetworkSecretConfirmed = 3;
|
||||
}
|
||||
|
||||
enum PeerIdentityType {
|
||||
Admin = 0;
|
||||
Credential = 1;
|
||||
SharedNode = 2;
|
||||
}
|
||||
|
||||
enum PeerConnSessionActionPb {
|
||||
Join = 0;
|
||||
Sync = 1;
|
||||
Create = 2;
|
||||
}
|
||||
|
||||
message PeerConnNoiseMsg1Pb {
|
||||
uint32 version = 1;
|
||||
string a_network_name = 2;
|
||||
optional uint32 a_session_generation = 3;
|
||||
common.UUID a_conn_id = 4;
|
||||
string client_encryption_algorithm = 5;
|
||||
}
|
||||
|
||||
message PeerConnNoiseMsg2Pb {
|
||||
string b_network_name = 1;
|
||||
uint32 role_hint = 2;
|
||||
PeerConnSessionActionPb action = 3;
|
||||
uint32 b_session_generation = 4;
|
||||
optional bytes root_key_32 = 5;
|
||||
uint32 initial_epoch = 6;
|
||||
common.UUID b_conn_id = 7;
|
||||
common.UUID a_conn_id_echo = 8;
|
||||
optional bytes secret_proof_32 = 9;
|
||||
string server_encryption_algorithm = 10;
|
||||
}
|
||||
|
||||
message RelayNoiseMsg1Pb {
|
||||
uint32 version = 1;
|
||||
optional uint32 a_session_generation = 3;
|
||||
common.UUID a_conn_id = 4;
|
||||
string client_encryption_algorithm = 5;
|
||||
}
|
||||
|
||||
message RelayNoiseMsg2Pb {
|
||||
PeerConnSessionActionPb action = 3;
|
||||
uint32 b_session_generation = 4;
|
||||
optional bytes root_key_32 = 5;
|
||||
uint32 initial_epoch = 6;
|
||||
common.UUID b_conn_id = 7;
|
||||
common.UUID a_conn_id_echo = 8;
|
||||
string server_encryption_algorithm = 10;
|
||||
}
|
||||
|
||||
message PeerConnNoiseMsg3Pb {
|
||||
common.UUID a_conn_id_echo = 1;
|
||||
common.UUID b_conn_id_echo = 2;
|
||||
optional bytes secret_proof_32 = 3;
|
||||
bytes secret_digest = 4;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package tests;
|
||||
|
||||
/// The Greeting service. This service is used to generate greetings for various
|
||||
/// use-cases.
|
||||
service Greeting {
|
||||
// Generates a "hello" greeting based on the supplied info.
|
||||
rpc SayHello(SayHelloRequest) returns (SayHelloResponse);
|
||||
// Generates a "goodbye" greeting based on the supplied info.
|
||||
rpc SayGoodbye(SayGoodbyeRequest) returns (SayGoodbyeResponse);
|
||||
}
|
||||
|
||||
// The request for an `Greeting.SayHello` call.
|
||||
message SayHelloRequest { string name = 1; }
|
||||
|
||||
// The response for an `Greeting.SayHello` call.
|
||||
message SayHelloResponse { string greeting = 1; }
|
||||
|
||||
// The request for an `Greeting.SayGoodbye` call.
|
||||
message SayGoodbyeRequest { string name = 1; }
|
||||
|
||||
// The response for an `Greeting.SayGoodbye` call.
|
||||
message SayGoodbyeResponse { string greeting = 1; }
|
||||
@@ -0,0 +1,38 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "common.proto";
|
||||
|
||||
package web;
|
||||
|
||||
message DeviceOsInfo {
|
||||
string os_type = 1;
|
||||
string version = 2;
|
||||
string distribution = 3;
|
||||
}
|
||||
|
||||
message HeartbeatRequest {
|
||||
common.UUID machine_id = 1;
|
||||
common.UUID inst_id = 2;
|
||||
string user_token = 3;
|
||||
|
||||
string easytier_version = 4;
|
||||
string report_time = 5;
|
||||
string hostname = 6;
|
||||
|
||||
repeated common.UUID running_network_instances = 7;
|
||||
DeviceOsInfo device_os = 8;
|
||||
bool support_config_source = 9;
|
||||
}
|
||||
|
||||
message HeartbeatResponse {}
|
||||
|
||||
message GetFeatureRequest {}
|
||||
|
||||
message GetFeatureResponse {
|
||||
bool support_encryption = 1;
|
||||
}
|
||||
|
||||
service WebServerService {
|
||||
rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
|
||||
rpc GetFeature(GetFeatureRequest) returns (GetFeatureResponse);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/acl.rs"));
|
||||
include!(concat!(env!("OUT_DIR"), "/acl.serde.rs"));
|
||||
|
||||
impl Acl {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.acl_v1.as_ref().map(|v1| v1.is_empty()).unwrap_or(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl AclV1 {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let has_chains = !self.chains.is_empty();
|
||||
let has_groups = self.group.as_ref().map(|g| !g.is_empty()).unwrap_or(false);
|
||||
!has_chains && !has_groups
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupInfo {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.declares.is_empty() && self.members.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api")]
|
||||
impl Display for ConnTrackEntry {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let src = self
|
||||
.src_addr
|
||||
.as_ref()
|
||||
.map(|a| a.to_string())
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
let dst = self
|
||||
.dst_addr
|
||||
.as_ref()
|
||||
.map(|a| a.to_string())
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
let last_seen = chrono::DateTime::<chrono::Utc>::from_timestamp(self.last_seen as i64, 0)
|
||||
.unwrap()
|
||||
.with_timezone(&chrono::Local);
|
||||
let created_at = chrono::DateTime::<chrono::Utc>::from_timestamp(self.created_at as i64, 0)
|
||||
.unwrap()
|
||||
.with_timezone(&chrono::Local);
|
||||
write!(
|
||||
f,
|
||||
"[src: {}, dst: {}, proto: {:?}, state: {:?}, pkts: {}, bytes: {}, created: {}, last_seen: {}]",
|
||||
src,
|
||||
dst,
|
||||
Protocol::try_from(self.protocol).unwrap_or(Protocol::Unspecified),
|
||||
ConnState::try_from(self.state).unwrap_or(ConnState::Invalid),
|
||||
self.packet_count,
|
||||
self.byte_count,
|
||||
created_at,
|
||||
last_seen
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Rule {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[name: '{}', prio: {}, action: {:?}, enabled: {}, proto: {:?}, ports: {:?}, src_ports: {:?}, src_ips: {:?}, dst_ips: {:?}, stateful: {}, rate: {}, burst: {}]",
|
||||
self.name,
|
||||
self.priority,
|
||||
Action::try_from(self.action).unwrap_or(Action::Noop),
|
||||
self.enabled,
|
||||
Protocol::try_from(self.protocol).unwrap_or(Protocol::Unspecified),
|
||||
self.ports,
|
||||
self.source_ports,
|
||||
self.source_ips,
|
||||
self.destination_ips,
|
||||
self.stateful,
|
||||
self.rate_limit,
|
||||
self.burst_limit
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StatItem {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[pkts: {}, bytes: {}]",
|
||||
self.packet_count, self.byte_count
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api")]
|
||||
impl Display for AclStats {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "AclStats:")?;
|
||||
writeln!(f, " Global:")?;
|
||||
for (k, v) in &self.global {
|
||||
writeln!(f, " {}: {}", k, v)?;
|
||||
}
|
||||
writeln!(f, " ConnTrack:")?;
|
||||
for entry in &self.conn_track {
|
||||
writeln!(f, " {}", entry)?;
|
||||
}
|
||||
writeln!(f, " Rules:")?;
|
||||
for rule_stat in &self.rules {
|
||||
if let Some(rule) = &rule_stat.rule {
|
||||
write!(f, " {} ", rule)?;
|
||||
} else {
|
||||
write!(f, " <default/none> ")?;
|
||||
}
|
||||
if let Some(stat) = &rule_stat.stat {
|
||||
writeln!(f, "{}", stat)?;
|
||||
} else {
|
||||
writeln!(f)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
pub mod config {
|
||||
include!(concat!(env!("OUT_DIR"), "/api.config.rs"));
|
||||
#[cfg(feature = "json-rpc")]
|
||||
include!(concat!(env!("OUT_DIR"), "/api.config.serde.rs"));
|
||||
|
||||
pub struct Patchable<T> {
|
||||
pub action: Option<ConfigPatchAction>,
|
||||
pub value: Option<T>,
|
||||
}
|
||||
|
||||
impl From<RoutePatch> for Patchable<cidr::Ipv4Cidr> {
|
||||
fn from(value: RoutePatch) -> Self {
|
||||
Patchable {
|
||||
action: ConfigPatchAction::try_from(value.action).ok(),
|
||||
value: value.cidr.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExitNodePatch> for Patchable<std::net::IpAddr> {
|
||||
fn from(value: ExitNodePatch) -> Self {
|
||||
Patchable {
|
||||
action: ConfigPatchAction::try_from(value.action).ok(),
|
||||
value: value.node.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StringPatch> for Patchable<String> {
|
||||
fn from(value: StringPatch) -> Self {
|
||||
Patchable {
|
||||
action: ConfigPatchAction::try_from(value.action).ok(),
|
||||
value: Some(value.value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UrlPatch> for Patchable<url::Url> {
|
||||
fn from(value: UrlPatch) -> Self {
|
||||
Patchable {
|
||||
action: ConfigPatchAction::try_from(value.action).ok(),
|
||||
value: value.url.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn patch_vec<T>(v: &mut Vec<T>, patches: Vec<Patchable<T>>)
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
for patch in patches {
|
||||
match patch.action {
|
||||
Some(ConfigPatchAction::Add) => {
|
||||
if let Some(value) = patch.value {
|
||||
v.push(value);
|
||||
}
|
||||
}
|
||||
Some(ConfigPatchAction::Remove) => {
|
||||
if let Some(value) = patch.value {
|
||||
v.retain(|x| x != &value);
|
||||
}
|
||||
}
|
||||
Some(ConfigPatchAction::Clear) => {
|
||||
v.clear();
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod instance {
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/api.instance.rs"));
|
||||
#[cfg(feature = "json-rpc")]
|
||||
include!(concat!(env!("OUT_DIR"), "/api.instance.serde.rs"));
|
||||
|
||||
impl From<crate::core_peer::peer::PeerConnStats> for PeerConnStats {
|
||||
fn from(value: crate::core_peer::peer::PeerConnStats) -> Self {
|
||||
Self {
|
||||
rx_bytes: value.rx_bytes,
|
||||
tx_bytes: value.tx_bytes,
|
||||
rx_packets: value.rx_packets,
|
||||
tx_packets: value.tx_packets,
|
||||
latency_us: value.latency_us,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::core_peer::peer::PeerConnInfo> for PeerConnInfo {
|
||||
fn from(value: crate::core_peer::peer::PeerConnInfo) -> Self {
|
||||
Self {
|
||||
conn_id: value.conn_id,
|
||||
my_peer_id: value.my_peer_id,
|
||||
peer_id: value.peer_id,
|
||||
features: value.features,
|
||||
tunnel: value.tunnel,
|
||||
stats: value.stats.map(Into::into),
|
||||
loss_rate: value.loss_rate,
|
||||
is_client: value.is_client,
|
||||
network_name: value.network_name,
|
||||
is_closed: value.is_closed,
|
||||
noise_local_static_pubkey: value.noise_local_static_pubkey,
|
||||
noise_remote_static_pubkey: value.noise_remote_static_pubkey,
|
||||
secure_auth_level: value.secure_auth_level,
|
||||
peer_identity_type: value.peer_identity_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::core_peer::peer::PeerInfo> for PeerInfo {
|
||||
fn from(value: crate::core_peer::peer::PeerInfo) -> Self {
|
||||
Self {
|
||||
peer_id: value.peer_id,
|
||||
conns: value.conns.into_iter().map(Into::into).collect(),
|
||||
default_conn_id: value.default_conn_id,
|
||||
directly_connected_conns: value.directly_connected_conns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::core_peer::peer::Route> for Route {
|
||||
fn from(value: crate::core_peer::peer::Route) -> Self {
|
||||
Self {
|
||||
peer_id: value.peer_id,
|
||||
ipv4_addr: value.ipv4_addr,
|
||||
next_hop_peer_id: value.next_hop_peer_id,
|
||||
cost: value.cost,
|
||||
path_latency: value.path_latency,
|
||||
proxy_cidrs: value.proxy_cidrs,
|
||||
hostname: value.hostname,
|
||||
stun_info: value.stun_info,
|
||||
inst_id: value.inst_id,
|
||||
version: value.version,
|
||||
feature_flag: value.feature_flag,
|
||||
next_hop_peer_id_latency_first: value.next_hop_peer_id_latency_first,
|
||||
cost_latency_first: value.cost_latency_first,
|
||||
path_latency_latency_first: value.path_latency_latency_first,
|
||||
ipv6_addr: value.ipv6_addr,
|
||||
public_ipv6_addr: value.public_ipv6_addr,
|
||||
ipv6_public_addr_prefix: value.ipv6_public_addr_prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::core_peer::peer::PublicIpv6LeaseInfo> for PublicIpv6LeaseInfo {
|
||||
fn from(value: crate::core_peer::peer::PublicIpv6LeaseInfo) -> Self {
|
||||
Self {
|
||||
peer_id: value.peer_id,
|
||||
inst_id: value.inst_id,
|
||||
leased_addr: value.leased_addr,
|
||||
valid_until_unix_seconds: value.valid_until_unix_seconds,
|
||||
reused: value.reused,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::core_peer::peer::ListPublicIpv6InfoResponse> for ListPublicIpv6InfoResponse {
|
||||
fn from(value: crate::core_peer::peer::ListPublicIpv6InfoResponse) -> Self {
|
||||
Self {
|
||||
provider_prefix: value.provider_prefix,
|
||||
provider_leases: value.provider_leases.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PeerConnInfo {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("PeerConnInfo")
|
||||
.field("my_peer_id", &self.my_peer_id)
|
||||
.field("dst_peer_id", &self.peer_id)
|
||||
.field("tunnel_info", &self.tunnel)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PeerRoutePair {
|
||||
pub fn get_latency_ms(&self) -> Option<f64> {
|
||||
let mut ret = u64::MAX;
|
||||
let p = self.peer.as_ref()?;
|
||||
let default_conn_id = p.default_conn_id.map(|id| id.to_string());
|
||||
for conn in p.conns.iter() {
|
||||
let Some(stats) = &conn.stats else {
|
||||
continue;
|
||||
};
|
||||
if default_conn_id == Some(conn.conn_id.to_string()) {
|
||||
return Some(f64::from(stats.latency_us as u32) / 1000.0);
|
||||
}
|
||||
ret = ret.min(stats.latency_us);
|
||||
}
|
||||
|
||||
if ret == u64::MAX {
|
||||
None
|
||||
} else {
|
||||
Some(f64::from(ret as u32) / 1000.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rx_bytes(&self) -> Option<u64> {
|
||||
let mut ret = 0;
|
||||
let p = self.peer.as_ref()?;
|
||||
for conn in p.conns.iter() {
|
||||
let Some(stats) = &conn.stats else {
|
||||
continue;
|
||||
};
|
||||
ret += stats.rx_bytes;
|
||||
}
|
||||
|
||||
if ret == 0 { None } else { Some(ret) }
|
||||
}
|
||||
|
||||
pub fn get_tx_bytes(&self) -> Option<u64> {
|
||||
let mut ret = 0;
|
||||
let p = self.peer.as_ref()?;
|
||||
for conn in p.conns.iter() {
|
||||
let Some(stats) = &conn.stats else {
|
||||
continue;
|
||||
};
|
||||
ret += stats.tx_bytes;
|
||||
}
|
||||
|
||||
if ret == 0 { None } else { Some(ret) }
|
||||
}
|
||||
|
||||
pub fn get_loss_rate(&self) -> Option<f64> {
|
||||
let p = self.peer.as_ref()?;
|
||||
let default_conn_id = p.default_conn_id.map(|id| id.to_string());
|
||||
let mut ret = None;
|
||||
for conn in p.conns.iter() {
|
||||
if default_conn_id == Some(conn.conn_id.to_string()) {
|
||||
return Some(conn.loss_rate as f64);
|
||||
}
|
||||
|
||||
ret.get_or_insert(conn.loss_rate as f64);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn get_tunnel_proto_str(tunnel_info: &super::super::common::TunnelInfo) -> String {
|
||||
tunnel_info.display_tunnel_type()
|
||||
}
|
||||
|
||||
pub fn get_conn_protos(&self) -> Option<Vec<String>> {
|
||||
let mut ret = vec![];
|
||||
let p = self.peer.as_ref()?;
|
||||
for conn in p.conns.iter() {
|
||||
let Some(tunnel_info) = &conn.tunnel else {
|
||||
continue;
|
||||
};
|
||||
// insert if not exists
|
||||
let tunnel_type = Self::get_tunnel_proto_str(tunnel_info);
|
||||
if !ret.contains(&tunnel_type) {
|
||||
ret.push(tunnel_type);
|
||||
}
|
||||
}
|
||||
|
||||
if ret.is_empty() { None } else { Some(ret) }
|
||||
}
|
||||
|
||||
pub fn get_udp_nat_type(&self) -> String {
|
||||
use crate::proto::common::NatType;
|
||||
let mut ret = NatType::Unknown;
|
||||
if let Some(r) = &self.route.clone().unwrap_or_default().stun_info {
|
||||
ret = NatType::try_from(r.udp_nat_type).unwrap();
|
||||
}
|
||||
format!("{:?}", ret)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_peer_route_pair(peers: Vec<PeerInfo>, routes: Vec<Route>) -> Vec<PeerRoutePair> {
|
||||
let mut pairs: Vec<PeerRoutePair> = vec![];
|
||||
|
||||
for route in routes.iter() {
|
||||
let peer = peers.iter().find(|peer| peer.peer_id == route.peer_id);
|
||||
let pair = PeerRoutePair {
|
||||
route: Some(route.clone()),
|
||||
peer: peer.cloned(),
|
||||
};
|
||||
|
||||
pairs.push(pair);
|
||||
}
|
||||
|
||||
pairs.sort_by(|a, b| {
|
||||
let a_is_public_server = a
|
||||
.route
|
||||
.as_ref()
|
||||
.and_then(|r| r.feature_flag.as_ref())
|
||||
.is_some_and(|f| f.is_public_server);
|
||||
|
||||
let b_is_public_server = b
|
||||
.route
|
||||
.as_ref()
|
||||
.and_then(|r| r.feature_flag.as_ref())
|
||||
.is_some_and(|f| f.is_public_server);
|
||||
|
||||
if a_is_public_server != b_is_public_server {
|
||||
return if a_is_public_server {
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Greater
|
||||
};
|
||||
}
|
||||
|
||||
let a_ip = a
|
||||
.route
|
||||
.as_ref()
|
||||
.and_then(|r| r.ipv4_addr.as_ref())
|
||||
.and_then(|ipv4| ipv4.address.as_ref())
|
||||
.map_or(0, |addr| addr.addr);
|
||||
|
||||
let b_ip = b
|
||||
.route
|
||||
.as_ref()
|
||||
.and_then(|r| r.ipv4_addr.as_ref())
|
||||
.and_then(|ipv4| ipv4.address.as_ref())
|
||||
.map_or(0, |addr| addr.addr);
|
||||
|
||||
a_ip.cmp(&b_ip)
|
||||
});
|
||||
|
||||
pairs
|
||||
}
|
||||
}
|
||||
|
||||
pub mod logger {
|
||||
include!(concat!(env!("OUT_DIR"), "/api.logger.rs"));
|
||||
#[cfg(feature = "json-rpc")]
|
||||
include!(concat!(env!("OUT_DIR"), "/api.logger.serde.rs"));
|
||||
}
|
||||
|
||||
pub mod manage {
|
||||
include!(concat!(env!("OUT_DIR"), "/api.manage.rs"));
|
||||
#[cfg(feature = "json-rpc")]
|
||||
include!(concat!(env!("OUT_DIR"), "/api.manage.serde.rs"));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bytes::Bytes;
|
||||
use prost::Message;
|
||||
|
||||
use super::instance::{PeerConnInfo, PeerInfo, PeerRoutePair};
|
||||
use super::manage::{
|
||||
ListNetworkInstanceRequest, ListNetworkInstanceResponse, WebClientService,
|
||||
WebClientServiceClient, WebClientServiceDescriptor, WebClientServiceMethodDescriptor,
|
||||
};
|
||||
use crate::proto::common::Uuid;
|
||||
use crate::proto::rpc_types::controller::BaseController;
|
||||
use crate::proto::rpc_types::descriptor::ServiceDescriptor;
|
||||
use crate::proto::rpc_types::error::Error;
|
||||
use crate::proto::rpc_types::handler::Handler;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct WebClientServiceJsonCallHandler;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Handler for WebClientServiceJsonCallHandler {
|
||||
type Descriptor = WebClientServiceDescriptor;
|
||||
type Controller = BaseController;
|
||||
|
||||
async fn call(
|
||||
&self,
|
||||
_ctrl: Self::Controller,
|
||||
method: <Self::Descriptor as ServiceDescriptor>::Method,
|
||||
input: Bytes,
|
||||
) -> crate::proto::rpc_types::error::Result<Bytes> {
|
||||
match method {
|
||||
WebClientServiceMethodDescriptor::ListNetworkInstance => {
|
||||
let _req = ListNetworkInstanceRequest::decode(input.as_ref()).unwrap();
|
||||
let resp = ListNetworkInstanceResponse {
|
||||
inst_ids: vec![Uuid {
|
||||
part1: 1,
|
||||
part2: 2,
|
||||
part3: 3,
|
||||
part4: 4,
|
||||
}],
|
||||
};
|
||||
Ok(Bytes::from(resp.encode_to_vec()))
|
||||
}
|
||||
_ => Err(Error::ExecutionError(anyhow::anyhow!(
|
||||
"unsupported method in test handler"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn web_client_service_call_json_method_supports_snake_and_proto_method_name() {
|
||||
let client = WebClientServiceClient::new(WebClientServiceJsonCallHandler);
|
||||
|
||||
let snake_result = client
|
||||
.json_call_method(
|
||||
BaseController::default(),
|
||||
"list_network_instance",
|
||||
serde_json::json!({}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
snake_result["inst_ids"][0],
|
||||
serde_json::json!({
|
||||
"part1": 1,
|
||||
"part2": 2,
|
||||
"part3": 3,
|
||||
"part4": 4
|
||||
})
|
||||
);
|
||||
|
||||
let proto_result = client
|
||||
.json_call_method(
|
||||
BaseController::default(),
|
||||
"ListNetworkInstance",
|
||||
serde_json::json!({}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(proto_result["inst_ids"].as_array().unwrap().len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn web_client_service_call_json_method_rejects_unknown_method() {
|
||||
let client = WebClientServiceClient::new(WebClientServiceJsonCallHandler);
|
||||
let ret = client
|
||||
.json_call_method(
|
||||
BaseController::default(),
|
||||
"not_exist_method",
|
||||
serde_json::json!({}),
|
||||
)
|
||||
.await;
|
||||
assert!(ret.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peer_route_pair_loss_rate_uses_default_conn() {
|
||||
let default_conn_id = uuid::Uuid::new_v4();
|
||||
let pair = PeerRoutePair {
|
||||
peer: Some(PeerInfo {
|
||||
default_conn_id: Some(default_conn_id.into()),
|
||||
conns: vec![
|
||||
PeerConnInfo {
|
||||
conn_id: uuid::Uuid::new_v4().to_string(),
|
||||
loss_rate: 0.8,
|
||||
..Default::default()
|
||||
},
|
||||
PeerConnInfo {
|
||||
conn_id: default_conn_id.to_string(),
|
||||
loss_rate: 0.4,
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert!(
|
||||
pair.get_loss_rate()
|
||||
.is_some_and(|loss_rate| (loss_rate - 0.4).abs() < 1e-6)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peer_route_pair_loss_rate_falls_back_to_first_conn() {
|
||||
let pair = PeerRoutePair {
|
||||
peer: Some(PeerInfo {
|
||||
conns: vec![
|
||||
PeerConnInfo {
|
||||
conn_id: uuid::Uuid::new_v4().to_string(),
|
||||
loss_rate: 0.0,
|
||||
..Default::default()
|
||||
},
|
||||
PeerConnInfo {
|
||||
conn_id: uuid::Uuid::new_v4().to_string(),
|
||||
loss_rate: 0.7,
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(pair.get_loss_rate(), Some(0.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,659 @@
|
||||
use anyhow::Context;
|
||||
use base64::{Engine as _, prelude::BASE64_STANDARD};
|
||||
use std::time::SystemTime;
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
const IP_SCHEMES: &[&str] = &["tcp", "udp", "wg", "quic", "ws", "wss", "faketcp"];
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/common.rs"));
|
||||
include!(concat!(env!("OUT_DIR"), "/common.serde.rs"));
|
||||
|
||||
pub trait TimestampExt {
|
||||
fn now() -> Self;
|
||||
}
|
||||
|
||||
#[cfg(feature = "json-rpc")]
|
||||
pub type RuntimeTimestamp = prost_wkt_types::Timestamp;
|
||||
#[cfg(not(feature = "json-rpc"))]
|
||||
pub type RuntimeTimestamp = prost_types::Timestamp;
|
||||
|
||||
impl TimestampExt for RuntimeTimestamp {
|
||||
fn now() -> Self {
|
||||
SystemTime::now().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<uuid::Uuid> for Uuid {
|
||||
fn from(uuid: uuid::Uuid) -> Self {
|
||||
let (high, low) = uuid.as_u64_pair();
|
||||
Uuid {
|
||||
part1: (high >> 32) as u32,
|
||||
part2: (high & 0xFFFFFFFF) as u32,
|
||||
part3: (low >> 32) as u32,
|
||||
part4: (low & 0xFFFFFFFF) as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Uuid> for uuid::Uuid {
|
||||
fn from(uuid: Uuid) -> Self {
|
||||
uuid::Uuid::from_u64_pair(
|
||||
(u64::from(uuid.part1) << 32) | u64::from(uuid.part2),
|
||||
(u64::from(uuid.part3) << 32) | u64::from(uuid.part4),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Uuid {
|
||||
fn from(value: String) -> Self {
|
||||
uuid::Uuid::parse_str(&value).unwrap().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Uuid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", uuid::Uuid::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Uuid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", uuid::Uuid::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::net::Ipv4Addr> for Ipv4Addr {
|
||||
fn from(value: std::net::Ipv4Addr) -> Self {
|
||||
Self {
|
||||
addr: u32::from_be_bytes(value.octets()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv4Addr> for std::net::Ipv4Addr {
|
||||
fn from(value: Ipv4Addr) -> Self {
|
||||
std::net::Ipv4Addr::from(value.addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ipv4Addr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", std::net::Ipv4Addr::from(self.addr))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::net::Ipv6Addr> for Ipv6Addr {
|
||||
fn from(value: std::net::Ipv6Addr) -> Self {
|
||||
let b = value.octets();
|
||||
Self {
|
||||
part1: u32::from_be_bytes([b[0], b[1], b[2], b[3]]),
|
||||
part2: u32::from_be_bytes([b[4], b[5], b[6], b[7]]),
|
||||
part3: u32::from_be_bytes([b[8], b[9], b[10], b[11]]),
|
||||
part4: u32::from_be_bytes([b[12], b[13], b[14], b[15]]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv6Addr> for std::net::Ipv6Addr {
|
||||
fn from(value: Ipv6Addr) -> Self {
|
||||
let part1 = value.part1.to_be_bytes();
|
||||
let part2 = value.part2.to_be_bytes();
|
||||
let part3 = value.part3.to_be_bytes();
|
||||
let part4 = value.part4.to_be_bytes();
|
||||
std::net::Ipv6Addr::from([
|
||||
part1[0], part1[1], part1[2], part1[3], part2[0], part2[1], part2[2], part2[3],
|
||||
part3[0], part3[1], part3[2], part3[3], part4[0], part4[1], part4[2], part4[3],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ipv6Addr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", std::net::Ipv6Addr::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cidr::Ipv4Inet> for Ipv4Inet {
|
||||
fn from(value: cidr::Ipv4Inet) -> Self {
|
||||
Ipv4Inet {
|
||||
address: Some(value.address().into()),
|
||||
network_length: value.network_length() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::net::IpAddr> for IpAddr {
|
||||
fn from(value: std::net::IpAddr) -> Self {
|
||||
match value {
|
||||
std::net::IpAddr::V4(v4) => IpAddr {
|
||||
ip: Some(ip_addr::Ip::Ipv4(Ipv4Addr::from(v4))),
|
||||
},
|
||||
std::net::IpAddr::V6(v6) => IpAddr {
|
||||
ip: Some(ip_addr::Ip::Ipv6(Ipv6Addr::from(v6))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IpAddr> for std::net::IpAddr {
|
||||
fn from(value: IpAddr) -> Self {
|
||||
match value.ip {
|
||||
Some(ip_addr::Ip::Ipv4(v4)) => std::net::IpAddr::V4(v4.into()),
|
||||
Some(ip_addr::Ip::Ipv6(v6)) => std::net::IpAddr::V6(v6.into()),
|
||||
None => panic!("IpAddr is None"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IpAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", std::net::IpAddr::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IpAddr {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(IpAddr::from(std::net::IpAddr::from_str(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv4Inet> for cidr::Ipv4Inet {
|
||||
fn from(value: Ipv4Inet) -> Self {
|
||||
cidr::Ipv4Inet::new(
|
||||
value.address.unwrap_or_default().into(),
|
||||
value.network_length as u8,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv4Inet> for cidr::Ipv4Cidr {
|
||||
fn from(value: Ipv4Inet) -> Self {
|
||||
cidr::Ipv4Cidr::new(
|
||||
value.address.unwrap_or_default().into(),
|
||||
value.network_length as u8,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Ipv4Inet {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", cidr::Ipv4Inet::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Ipv4Inet {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Ipv4Inet::from(
|
||||
cidr::Ipv4Inet::from_str(s).with_context(|| "Failed to parse Ipv4Inet")?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cidr::Ipv6Inet> for Ipv6Inet {
|
||||
fn from(value: cidr::Ipv6Inet) -> Self {
|
||||
Ipv6Inet {
|
||||
address: Some(value.address().into()),
|
||||
network_length: value.network_length() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ipv6Inet> for cidr::Ipv6Inet {
|
||||
fn from(value: Ipv6Inet) -> Self {
|
||||
cidr::Ipv6Inet::new(
|
||||
value.address.unwrap_or_default().into(),
|
||||
value.network_length as u8,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Ipv6Inet {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", cidr::Ipv6Inet::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Ipv6Inet {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Ipv6Inet::from(
|
||||
cidr::Ipv6Inet::from_str(s).with_context(|| "Failed to parse Ipv6Inet")?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cidr::IpInet> for IpInet {
|
||||
fn from(value: cidr::IpInet) -> Self {
|
||||
match value {
|
||||
cidr::IpInet::V4(v4) => IpInet {
|
||||
ip: Some(ip_inet::Ip::Ipv4(Ipv4Inet::from(v4))),
|
||||
},
|
||||
cidr::IpInet::V6(v6) => IpInet {
|
||||
ip: Some(ip_inet::Ip::Ipv6(Ipv6Inet::from(v6))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IpInet> for cidr::IpInet {
|
||||
fn from(value: IpInet) -> Self {
|
||||
match value.ip {
|
||||
Some(ip_inet::Ip::Ipv4(v4)) => cidr::IpInet::V4(v4.into()),
|
||||
Some(ip_inet::Ip::Ipv6(v6)) => cidr::IpInet::V6(v6.into()),
|
||||
None => panic!("IpInet is None"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IpInet {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", cidr::IpInet::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IpInet {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(IpInet::from(cidr::IpInet::from_str(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::Url> for Url {
|
||||
fn from(value: url::Url) -> Self {
|
||||
Url { url: value.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Url> for url::Url {
|
||||
type Error = url::ParseError;
|
||||
|
||||
fn try_from(value: &Url) -> Result<Self, Self::Error> {
|
||||
value.url.parse()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Url> for url::Url {
|
||||
fn from(value: Url) -> Self {
|
||||
(&value).try_into().expect("failed to parse url")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Url {
|
||||
type Err = url::ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
url::Url::try_from(s).map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Url {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.url)
|
||||
}
|
||||
}
|
||||
|
||||
fn split_tunnel_scheme(raw_scheme: &str) -> Option<(&str, &'static str, bool)> {
|
||||
for &scheme in IP_SCHEMES {
|
||||
if let Some(base) = raw_scheme.strip_suffix('6')
|
||||
&& let Some(prefix) = base.strip_suffix(scheme)
|
||||
&& (prefix.is_empty() || prefix.ends_with('-'))
|
||||
{
|
||||
return Some((prefix, scheme, true));
|
||||
}
|
||||
|
||||
if let Some(prefix) = raw_scheme.strip_suffix(scheme)
|
||||
&& (prefix.is_empty() || prefix.ends_with('-'))
|
||||
{
|
||||
return Some((prefix, scheme, false));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn normalize_tunnel_scheme(raw_scheme: &str, is_ipv6: bool) -> Option<String> {
|
||||
let (prefix, scheme, had_ipv6_suffix) = split_tunnel_scheme(raw_scheme)?;
|
||||
let suffix = if is_ipv6 || had_ipv6_suffix { "6" } else { "" };
|
||||
Some(format!("{prefix}{scheme}{suffix}"))
|
||||
}
|
||||
|
||||
fn infer_tunnel_ipv6(raw: &str) -> Option<bool> {
|
||||
let (_, rest) = raw.split_once("://")?;
|
||||
if rest.starts_with('[') {
|
||||
return Some(true);
|
||||
}
|
||||
|
||||
match url::Url::parse(raw).ok()?.host() {
|
||||
Some(url::Host::Ipv4(_)) => Some(false),
|
||||
Some(url::Host::Ipv6(_)) => Some(true),
|
||||
Some(url::Host::Domain(_)) | None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_tunnel_port(raw_port: &str, is_ipv6: bool) -> Option<u16> {
|
||||
if let Ok(port) = raw_port.parse::<u16>() {
|
||||
return Some(port);
|
||||
}
|
||||
|
||||
if is_ipv6 && raw_port.ends_with('6') {
|
||||
return raw_port[..raw_port.len() - 1].parse::<u16>().ok();
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn normalize_tunnel_url(raw: &str, fallback_ipv6: Option<bool>) -> Option<String> {
|
||||
let (raw_scheme, rest) = raw.split_once("://")?;
|
||||
|
||||
if let Some(rest) = rest.strip_prefix('[') {
|
||||
let (host, remainder) = rest.split_once(']')?;
|
||||
let scheme = normalize_tunnel_scheme(raw_scheme, true)?;
|
||||
|
||||
if remainder.is_empty() {
|
||||
return Some(format!("{scheme}://[{host}]"));
|
||||
}
|
||||
|
||||
let raw_port = remainder.strip_prefix(':')?;
|
||||
let port = normalize_tunnel_port(raw_port, true)?;
|
||||
return Some(format!("{scheme}://[{host}]:{port}"));
|
||||
}
|
||||
|
||||
let is_ipv6 = infer_tunnel_ipv6(raw).or(fallback_ipv6).unwrap_or(false);
|
||||
let scheme = normalize_tunnel_scheme(raw_scheme, is_ipv6)?;
|
||||
|
||||
if let Ok(url) = url::Url::parse(raw) {
|
||||
let host = match url.host()? {
|
||||
url::Host::Ipv4(host) => host.to_string(),
|
||||
url::Host::Ipv6(host) => format!("[{host}]"),
|
||||
url::Host::Domain(host) => host.to_string(),
|
||||
};
|
||||
|
||||
return Some(match url.port_or_known_default() {
|
||||
Some(port) => format!("{scheme}://{host}:{port}"),
|
||||
None => format!("{scheme}://{host}"),
|
||||
});
|
||||
}
|
||||
|
||||
let (host, raw_port) = rest.rsplit_once(':')?;
|
||||
let port = normalize_tunnel_port(raw_port, is_ipv6)?;
|
||||
Some(format!("{scheme}://{host}:{port}"))
|
||||
}
|
||||
|
||||
impl Url {
|
||||
pub fn is_ipv6_tunnel_endpoint(&self) -> bool {
|
||||
infer_tunnel_ipv6(&self.url).unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn normalized_tunnel_display(&self) -> String {
|
||||
normalize_tunnel_url(&self.url, None).unwrap_or_else(|| self.url.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::net::SocketAddr> for SocketAddr {
|
||||
fn from(value: std::net::SocketAddr) -> Self {
|
||||
match value {
|
||||
std::net::SocketAddr::V4(v4) => SocketAddr {
|
||||
ip: Some(socket_addr::Ip::Ipv4((*v4.ip()).into())),
|
||||
port: v4.port() as u32,
|
||||
},
|
||||
std::net::SocketAddr::V6(v6) => SocketAddr {
|
||||
ip: Some(socket_addr::Ip::Ipv6((*v6.ip()).into())),
|
||||
port: v6.port() as u32,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SocketAddr> for std::net::SocketAddr {
|
||||
fn from(value: SocketAddr) -> Self {
|
||||
if value.ip.is_none() {
|
||||
return "0.0.0.0:0".parse().unwrap();
|
||||
}
|
||||
match value.ip.unwrap() {
|
||||
socket_addr::Ip::Ipv4(ip) => std::net::SocketAddr::V4(std::net::SocketAddrV4::new(
|
||||
std::net::Ipv4Addr::from(ip),
|
||||
value.port as u16,
|
||||
)),
|
||||
socket_addr::Ip::Ipv6(ip) => std::net::SocketAddr::V6(std::net::SocketAddrV6::new(
|
||||
std::net::Ipv6Addr::from(ip),
|
||||
value.port as u16,
|
||||
0,
|
||||
0,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SocketAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", std::net::SocketAddr::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TunnelInfo {
|
||||
pub fn effective_remote_addr(&self) -> Option<&Url> {
|
||||
self.resolved_remote_addr
|
||||
.as_ref()
|
||||
.or(self.remote_addr.as_ref())
|
||||
}
|
||||
|
||||
pub fn display_tunnel_type(&self) -> String {
|
||||
let is_ipv6 = infer_tunnel_ipv6(&self.tunnel_type).or_else(|| {
|
||||
self.resolved_remote_addr
|
||||
.as_ref()
|
||||
.or(self.local_addr.as_ref())
|
||||
.or(self.remote_addr.as_ref())
|
||||
.map(Url::is_ipv6_tunnel_endpoint)
|
||||
});
|
||||
|
||||
if self.tunnel_type.contains("://") {
|
||||
normalize_tunnel_url(&self.tunnel_type, is_ipv6)
|
||||
.unwrap_or_else(|| self.tunnel_type.clone())
|
||||
} else {
|
||||
is_ipv6
|
||||
.and_then(|is_ipv6| normalize_tunnel_scheme(&self.tunnel_type, is_ipv6))
|
||||
.unwrap_or_else(|| self.tunnel_type.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_remote_addr(&self) -> Option<String> {
|
||||
self.effective_remote_addr()
|
||||
.map(Url::normalized_tunnel_display)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Ipv4Addr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let std_ipv4_addr = std::net::Ipv4Addr::from(*self);
|
||||
write!(f, "{}", std_ipv4_addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Ipv6Addr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let std_ipv6_addr = std::net::Ipv6Addr::from(*self);
|
||||
write!(f, "{}", std_ipv6_addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl SecureModeConfig {
|
||||
pub fn private_key(&self) -> anyhow::Result<x25519_dalek::StaticSecret> {
|
||||
let local_private_key = self
|
||||
.local_private_key
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("local private key is not set"))?;
|
||||
let k = BASE64_STANDARD
|
||||
.decode(local_private_key)
|
||||
.with_context(|| format!("failed to decode private key: {}", local_private_key))?;
|
||||
// convert vec to 32b array
|
||||
let len = k.len();
|
||||
let k: [u8; 32] = k
|
||||
.try_into()
|
||||
.map_err(|_| anyhow::anyhow!("invalid private key length: {}", len))?;
|
||||
Ok(x25519_dalek::StaticSecret::from(k))
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> anyhow::Result<x25519_dalek::PublicKey> {
|
||||
let local_public_key = self
|
||||
.local_public_key
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("local public key is not set"))?;
|
||||
let k = BASE64_STANDARD
|
||||
.decode(local_public_key)
|
||||
.with_context(|| format!("failed to decode public key: {}", local_public_key))?;
|
||||
// convert vec to 32b array
|
||||
let len = k.len();
|
||||
let k: [u8; 32] = k
|
||||
.try_into()
|
||||
.map_err(|_| anyhow::anyhow!("invalid public key length: {}", len))?;
|
||||
Ok(x25519_dalek::PublicKey::from(k))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{TunnelInfo, Url, normalize_tunnel_url};
|
||||
|
||||
fn assert_ipv6_tunnel_normalization(scheme: &str, port: u16) {
|
||||
let expected = format!("{scheme}6://[2001:db8::1]:{port}");
|
||||
assert_eq!(
|
||||
normalize_tunnel_url(&format!("{scheme}://[2001:db8::1]:{port}"), None).as_deref(),
|
||||
Some(expected.as_str())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_plain_ipv6_tunnel_url() {
|
||||
let url = Url {
|
||||
url: "tcp://[2001:db8::1]:11010".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
url.normalized_tunnel_display(),
|
||||
"tcp6://[2001:db8::1]:11010"
|
||||
);
|
||||
assert!(url.is_ipv6_tunnel_endpoint());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_all_ipv6_tunnel_urls() {
|
||||
assert_ipv6_tunnel_normalization("tcp", 11010);
|
||||
assert_ipv6_tunnel_normalization("udp", 11010);
|
||||
assert_ipv6_tunnel_normalization("wg", 11011);
|
||||
assert_ipv6_tunnel_normalization("quic", 11012);
|
||||
assert_ipv6_tunnel_normalization("ws", 80);
|
||||
assert_ipv6_tunnel_normalization("wss", 443);
|
||||
assert_ipv6_tunnel_normalization("faketcp", 11013);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_composite_ipv6_tunnel_url() {
|
||||
assert_eq!(
|
||||
normalize_tunnel_url("txt-tcp://[2001:db8::1]:11010", None).as_deref(),
|
||||
Some("txt-tcp6://[2001:db8::1]:11010")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recover_malformed_composite_ipv6_tunnel_url() {
|
||||
assert_eq!(
|
||||
normalize_tunnel_url("txt-tcp://[2001:db8::1]:110106", None).as_deref(),
|
||||
Some("txt-tcp6://[2001:db8::1]:11010")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keep_normalized_ipv6_tunnel_url_stable() {
|
||||
assert_eq!(
|
||||
normalize_tunnel_url("tcp6://[2001:db8::1]:11010", None).as_deref(),
|
||||
Some("tcp6://[2001:db8::1]:11010")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_ipv6_tunnel_url_without_explicit_port() {
|
||||
assert_eq!(
|
||||
normalize_tunnel_url("tcp://[2001:db8::1]", None).as_deref(),
|
||||
Some("tcp6://[2001:db8::1]")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keep_domain_host_unbracketed_when_ipv6_falls_back() {
|
||||
assert_eq!(
|
||||
normalize_tunnel_url("tcp://localhost:11010", Some(true)).as_deref(),
|
||||
Some("tcp6://localhost:11010")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tunnel_info_display_tunnel_type_preserves_composite_prefix() {
|
||||
let tunnel = TunnelInfo {
|
||||
tunnel_type: "txt-tcp://[2001:db8::2]:110106".to_string(),
|
||||
local_addr: None,
|
||||
remote_addr: Some(Url {
|
||||
url: "txt://et.example.com".to_string(),
|
||||
}),
|
||||
resolved_remote_addr: None,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
tunnel.display_tunnel_type(),
|
||||
"txt-tcp6://[2001:db8::2]:11010"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tunnel_info_display_tunnel_type_uses_remote_addr_fallback() {
|
||||
let tunnel = TunnelInfo {
|
||||
tunnel_type: "tcp".to_string(),
|
||||
local_addr: None,
|
||||
remote_addr: Some(Url {
|
||||
url: "tcp://[2001:db8::2]:11010".to_string(),
|
||||
}),
|
||||
resolved_remote_addr: None,
|
||||
};
|
||||
|
||||
assert_eq!(tunnel.display_tunnel_type(), "tcp6");
|
||||
assert_eq!(
|
||||
tunnel.display_remote_addr().as_deref(),
|
||||
Some("tcp6://[2001:db8::2]:11010")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tunnel_info_prefers_resolved_remote_addr() {
|
||||
let tunnel = TunnelInfo {
|
||||
tunnel_type: "txt-tcp".to_string(),
|
||||
local_addr: None,
|
||||
remote_addr: Some(Url {
|
||||
url: "txt://et.example.com".to_string(),
|
||||
}),
|
||||
resolved_remote_addr: Some(Url {
|
||||
url: "tcp://[2001:db8::3]:11010".to_string(),
|
||||
}),
|
||||
};
|
||||
|
||||
assert_eq!(tunnel.display_tunnel_type(), "txt-tcp6");
|
||||
assert_eq!(
|
||||
tunnel.display_remote_addr().as_deref(),
|
||||
Some("tcp6://[2001:db8::3]:11010")
|
||||
);
|
||||
assert_eq!(
|
||||
tunnel.effective_remote_addr().map(|url| url.url.as_str()),
|
||||
Some("tcp://[2001:db8::3]:11010")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/core_config.rs"));
|
||||
#[cfg(feature = "json-rpc")]
|
||||
include!(concat!(env!("OUT_DIR"), "/core_config.serde.rs"));
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod peer {
|
||||
include!(concat!(env!("OUT_DIR"), "/core.peer.rs"));
|
||||
#[cfg(feature = "json-rpc")]
|
||||
include!(concat!(env!("OUT_DIR"), "/core.peer.serde.rs"));
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
|
||||
use super::rpc_types;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/error.rs"));
|
||||
include!(concat!(env!("OUT_DIR"), "/error.serde.rs"));
|
||||
|
||||
impl From<&rpc_types::error::Error> for Error {
|
||||
fn from(e: &rpc_types::error::Error) -> Self {
|
||||
use super::error::error::ErrorKind as ProtoError;
|
||||
match e {
|
||||
rpc_types::error::Error::ExecutionError(e) => Self {
|
||||
error_kind: Some(ProtoError::ExecuteError(ExecuteError {
|
||||
error_message: format!("{:?}", e),
|
||||
})),
|
||||
},
|
||||
rpc_types::error::Error::DecodeError => Self {
|
||||
error_kind: Some(ProtoError::ProstDecodeError(ProstDecodeError {})),
|
||||
},
|
||||
rpc_types::error::Error::EncodeError => Self {
|
||||
error_kind: Some(ProtoError::ProstEncodeError(ProstEncodeError {})),
|
||||
},
|
||||
rpc_types::error::Error::InvalidMethodIndex(m, s) => Self {
|
||||
error_kind: Some(ProtoError::InvalidMethodIndex(InvalidMethodIndex {
|
||||
method_index: *m as u32,
|
||||
service_name: format!("{:?}", s),
|
||||
})),
|
||||
},
|
||||
rpc_types::error::Error::InvalidServiceKey(s, _) => Self {
|
||||
error_kind: Some(ProtoError::InvalidService(InvalidService {
|
||||
service_name: format!("{:?}", s),
|
||||
})),
|
||||
},
|
||||
rpc_types::error::Error::MalformatRpcPacket(e) => Self {
|
||||
error_kind: Some(ProtoError::MalformatRpcPacket(MalformatRpcPacket {
|
||||
error_message: format!("{:?}", e),
|
||||
})),
|
||||
},
|
||||
rpc_types::error::Error::Timeout(e) => Self {
|
||||
error_kind: Some(ProtoError::Timeout(Timeout {
|
||||
error_message: format!("{:?}", e),
|
||||
})),
|
||||
},
|
||||
#[allow(unreachable_patterns)]
|
||||
e => Self {
|
||||
error_kind: Some(ProtoError::OtherError(OtherError {
|
||||
error_message: format!("{:?}", e),
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Error> for rpc_types::error::Error {
|
||||
fn from(e: &Error) -> Self {
|
||||
use super::error::error::ErrorKind as ProtoError;
|
||||
match &e.error_kind {
|
||||
Some(ProtoError::ExecuteError(e)) => {
|
||||
Self::ExecutionError(anyhow::anyhow!(e.error_message.clone()))
|
||||
}
|
||||
Some(ProtoError::ProstDecodeError(_)) => Self::DecodeError,
|
||||
Some(ProtoError::ProstEncodeError(_)) => Self::EncodeError,
|
||||
Some(ProtoError::InvalidMethodIndex(e)) => {
|
||||
Self::InvalidMethodIndex(e.method_index as u8, e.service_name.clone())
|
||||
}
|
||||
Some(ProtoError::InvalidService(e)) => {
|
||||
Self::InvalidServiceKey(e.service_name.clone(), "".to_string())
|
||||
}
|
||||
Some(ProtoError::MalformatRpcPacket(e)) => {
|
||||
Self::MalformatRpcPacket(e.error_message.clone())
|
||||
}
|
||||
Some(ProtoError::Timeout(e)) => {
|
||||
Self::ExecutionError(anyhow::anyhow!(e.error_message.clone()))
|
||||
}
|
||||
Some(ProtoError::OtherError(e)) => {
|
||||
Self::ExecutionError(anyhow::anyhow!(e.error_message.clone()))
|
||||
}
|
||||
None => Self::ExecutionError(anyhow::anyhow!("unknown error {:?}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#[cfg(feature = "core")]
|
||||
pub mod rpc_types;
|
||||
|
||||
#[cfg(feature = "core")]
|
||||
pub mod acl;
|
||||
#[cfg(feature = "api")]
|
||||
pub mod api;
|
||||
#[cfg(feature = "core")]
|
||||
pub mod common;
|
||||
#[cfg(feature = "core")]
|
||||
pub mod core_config;
|
||||
#[cfg(feature = "core")]
|
||||
pub mod core_peer;
|
||||
#[cfg(feature = "core")]
|
||||
pub mod error;
|
||||
#[cfg(all(feature = "api", feature = "magic-dns"))]
|
||||
pub mod magic_dns;
|
||||
#[cfg(feature = "core")]
|
||||
pub mod peer_rpc;
|
||||
#[cfg(feature = "api")]
|
||||
pub mod tests;
|
||||
#[cfg(feature = "api")]
|
||||
pub mod web;
|
||||
|
||||
pub const DESCRIPTOR_POOL_BYTES: &[u8] =
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin"));
|
||||
|
||||
pub const ALL_DESCRIPTOR_BYTES: &[u8] =
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/descriptors.bin"));
|
||||
|
||||
pub mod proto {
|
||||
pub use crate::*;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/magic_dns.rs"));
|
||||
#[cfg(feature = "json-rpc")]
|
||||
include!(concat!(env!("OUT_DIR"), "/magic_dns.serde.rs"));
|
||||
@@ -0,0 +1,512 @@
|
||||
use hmac::{Hmac, Mac};
|
||||
use prost::Message;
|
||||
use sha2::Sha256;
|
||||
#[cfg(feature = "api")]
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
type PeerId = u32;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/peer_rpc.rs"));
|
||||
#[cfg(feature = "json-rpc")]
|
||||
include!(concat!(env!("OUT_DIR"), "/peer_rpc.serde.rs"));
|
||||
|
||||
impl PeerGroupInfo {
|
||||
pub fn generate_with_proof(group_name: String, group_secret: String, peer_id: PeerId) -> Self {
|
||||
let mut mac = Hmac::<Sha256>::new_from_slice(group_secret.as_bytes())
|
||||
.expect("HMAC can take key of any size");
|
||||
|
||||
let mut data_to_sign = group_name.as_bytes().to_vec();
|
||||
data_to_sign.push(0x00); // Add a delimiter byte
|
||||
data_to_sign.extend_from_slice(&peer_id.to_be_bytes());
|
||||
|
||||
mac.update(&data_to_sign);
|
||||
|
||||
let proof = mac.finalize().into_bytes().to_vec();
|
||||
|
||||
PeerGroupInfo {
|
||||
group_name,
|
||||
group_proof: proof,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(&self, group_secret: &str, peer_id: PeerId) -> bool {
|
||||
let mut verifier = Hmac::<Sha256>::new_from_slice(group_secret.as_bytes())
|
||||
.expect("HMAC can take key of any size");
|
||||
|
||||
let mut data_to_sign = self.group_name.as_bytes().to_vec();
|
||||
data_to_sign.push(0x00); // Add a delimiter byte
|
||||
data_to_sign.extend_from_slice(&peer_id.to_be_bytes());
|
||||
|
||||
verifier.update(&data_to_sign);
|
||||
|
||||
verifier.verify_slice(&self.group_proof).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl TrustedCredentialPubkeyProof {
|
||||
pub fn generate_credential_hmac_from_bytes(
|
||||
credential_bytes: &[u8],
|
||||
network_secret: &str,
|
||||
) -> Vec<u8> {
|
||||
let mut mac = Hmac::<Sha256>::new_from_slice(network_secret.as_bytes())
|
||||
.expect("HMAC can take key of any size");
|
||||
mac.update(b"easytier credential proof");
|
||||
mac.update(credential_bytes);
|
||||
mac.finalize().into_bytes().to_vec()
|
||||
}
|
||||
|
||||
pub fn generate_credential_hmac(
|
||||
credential: &TrustedCredentialPubkey,
|
||||
network_secret: &str,
|
||||
) -> Vec<u8> {
|
||||
Self::generate_credential_hmac_from_bytes(&credential.encode_to_vec(), network_secret)
|
||||
}
|
||||
|
||||
pub fn new_signed(credential: TrustedCredentialPubkey, network_secret: &str) -> Self {
|
||||
let credential_hmac = Self::generate_credential_hmac(&credential, network_secret);
|
||||
Self {
|
||||
credential: Some(credential),
|
||||
credential_hmac,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_credential_hmac(&self, network_secret: &str) -> bool {
|
||||
let Some(credential) = self.credential.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
self.verify_credential_hmac_with_bytes(&credential.encode_to_vec(), network_secret)
|
||||
}
|
||||
|
||||
pub fn verify_credential_hmac_with_bytes(
|
||||
&self,
|
||||
credential_bytes: &[u8],
|
||||
network_secret: &str,
|
||||
) -> bool {
|
||||
if self.credential_hmac.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut mac = Hmac::<Sha256>::new_from_slice(network_secret.as_bytes())
|
||||
.expect("HMAC can take key of any size");
|
||||
mac.update(b"easytier credential proof");
|
||||
mac.update(credential_bytes);
|
||||
mac.verify_slice(&self.credential_hmac).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RouteConnBitmap> for sync_route_info_request::ConnInfo {
|
||||
fn from(val: RouteConnBitmap) -> Self {
|
||||
Self::ConnBitmap(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RouteConnPeerList> for sync_route_info_request::ConnInfo {
|
||||
fn from(val: RouteConnPeerList) -> Self {
|
||||
Self::ConnPeerList(val)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api")]
|
||||
impl From<Vec<crate::api::instance::PeerInfo>> for PeerInfoForGlobalMap {
|
||||
fn from(peers: Vec<crate::api::instance::PeerInfo>) -> Self {
|
||||
let mut peer_map = BTreeMap::new();
|
||||
for peer in peers {
|
||||
let Some(min_lat) = peer
|
||||
.conns
|
||||
.iter()
|
||||
.map(|conn| conn.stats.as_ref().unwrap().latency_us)
|
||||
.min()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let dp_info = DirectConnectedPeerInfo {
|
||||
latency_ms: std::cmp::max(1, (min_lat as u32 / 1000) as i32),
|
||||
};
|
||||
|
||||
peer_map.insert(peer.peer_id, dp_info);
|
||||
}
|
||||
PeerInfoForGlobalMap {
|
||||
direct_peers: peer_map,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoutePeerInfo> for crate::core_peer::peer::Route {
|
||||
fn from(val: RoutePeerInfo) -> Self {
|
||||
let network_length = if val.network_length == 0 {
|
||||
24
|
||||
} else {
|
||||
val.network_length
|
||||
};
|
||||
|
||||
crate::core_peer::peer::Route {
|
||||
peer_id: val.peer_id,
|
||||
ipv4_addr: val.ipv4_addr.map(|ipv4_addr| crate::common::Ipv4Inet {
|
||||
address: Some(ipv4_addr),
|
||||
network_length,
|
||||
}),
|
||||
next_hop_peer_id: 0,
|
||||
cost: 0,
|
||||
path_latency: 0,
|
||||
proxy_cidrs: val.proxy_cidrs.clone(),
|
||||
hostname: val.hostname.unwrap_or_default(),
|
||||
stun_info: {
|
||||
let mut stun_info = crate::common::StunInfo::default();
|
||||
if let Ok(udp_nat_type) = crate::common::NatType::try_from(val.udp_nat_type) {
|
||||
stun_info.set_udp_nat_type(udp_nat_type);
|
||||
}
|
||||
if let Ok(tcp_nat_type) = crate::common::NatType::try_from(val.tcp_nat_type) {
|
||||
stun_info.set_tcp_nat_type(tcp_nat_type);
|
||||
}
|
||||
Some(stun_info)
|
||||
},
|
||||
inst_id: val.inst_id.map(|x| x.to_string()).unwrap_or_default(),
|
||||
version: val.easytier_version,
|
||||
feature_flag: val.feature_flag,
|
||||
|
||||
next_hop_peer_id_latency_first: None,
|
||||
cost_latency_first: None,
|
||||
path_latency_latency_first: None,
|
||||
|
||||
ipv6_addr: val.ipv6_addr,
|
||||
public_ipv6_addr: val.ipv6_public_addr_lease,
|
||||
ipv6_public_addr_prefix: val.ipv6_public_addr_prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api")]
|
||||
impl From<RoutePeerInfo> for crate::api::instance::Route {
|
||||
fn from(val: RoutePeerInfo) -> Self {
|
||||
let network_length = if val.network_length == 0 {
|
||||
24
|
||||
} else {
|
||||
val.network_length
|
||||
};
|
||||
|
||||
crate::api::instance::Route {
|
||||
peer_id: val.peer_id,
|
||||
ipv4_addr: val.ipv4_addr.map(|ipv4_addr| crate::common::Ipv4Inet {
|
||||
address: Some(ipv4_addr),
|
||||
network_length,
|
||||
}),
|
||||
next_hop_peer_id: 0,
|
||||
cost: 0,
|
||||
path_latency: 0,
|
||||
proxy_cidrs: val.proxy_cidrs.clone(),
|
||||
hostname: val.hostname.unwrap_or_default(),
|
||||
stun_info: {
|
||||
let mut stun_info = crate::common::StunInfo::default();
|
||||
if let Ok(udp_nat_type) = crate::common::NatType::try_from(val.udp_nat_type) {
|
||||
stun_info.set_udp_nat_type(udp_nat_type);
|
||||
}
|
||||
if let Ok(tcp_nat_type) = crate::common::NatType::try_from(val.tcp_nat_type) {
|
||||
stun_info.set_tcp_nat_type(tcp_nat_type);
|
||||
}
|
||||
Some(stun_info)
|
||||
},
|
||||
inst_id: val.inst_id.map(|x| x.to_string()).unwrap_or_default(),
|
||||
version: val.easytier_version,
|
||||
feature_flag: val.feature_flag,
|
||||
|
||||
next_hop_peer_id_latency_first: None,
|
||||
cost_latency_first: None,
|
||||
path_latency_latency_first: None,
|
||||
|
||||
ipv6_addr: val.ipv6_addr,
|
||||
public_ipv6_addr: val.ipv6_public_addr_lease,
|
||||
ipv6_public_addr_prefix: val.ipv6_public_addr_prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RouteConnBitmap {
|
||||
pub fn get_bit(&self, idx: usize) -> bool {
|
||||
let byte_idx = idx / 8;
|
||||
let bit_idx = idx % 8;
|
||||
let byte = self.bitmap[byte_idx];
|
||||
(byte >> bit_idx) & 1 == 1
|
||||
}
|
||||
|
||||
pub fn get_connected_peers(&self, peer_idx: usize) -> BTreeSet<PeerId> {
|
||||
let mut connected_peers = BTreeSet::new();
|
||||
for (idx, peer_id_version) in self.peer_ids.iter().enumerate() {
|
||||
if self.get_bit(peer_idx * self.peer_ids.len() + idx) {
|
||||
connected_peers.insert(peer_id_version.peer_id);
|
||||
}
|
||||
}
|
||||
connected_peers
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_peer_group_info_new() {
|
||||
let group_name = "test_group".to_string();
|
||||
let group_secret = "secret123".to_string();
|
||||
let peer_id = 42u32;
|
||||
|
||||
let peer_group_info =
|
||||
PeerGroupInfo::generate_with_proof(group_name.clone(), group_secret, peer_id);
|
||||
|
||||
assert_eq!(peer_group_info.group_name, group_name);
|
||||
assert!(!peer_group_info.group_proof.is_empty());
|
||||
// HMAC-SHA256 produces a 32-byte output
|
||||
assert_eq!(peer_group_info.group_proof.len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_group_info_verify_valid() {
|
||||
let group_name = "test_group".to_string();
|
||||
let group_secret = "secret123".to_string();
|
||||
let peer_id = 42u32;
|
||||
|
||||
let peer_group_info =
|
||||
PeerGroupInfo::generate_with_proof(group_name, group_secret.clone(), peer_id);
|
||||
|
||||
// Verification should succeed using the same secret and peer_id
|
||||
assert!(peer_group_info.verify(&group_secret, peer_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_group_info_verify_invalid_secret() {
|
||||
let group_name = "test_group".to_string();
|
||||
let group_secret = "secret123".to_string();
|
||||
let peer_id = 42u32;
|
||||
|
||||
let peer_group_info = PeerGroupInfo::generate_with_proof(group_name, group_secret, peer_id);
|
||||
|
||||
// Verification should fail with a wrong secret
|
||||
assert!(!peer_group_info.verify("wrong_secret", peer_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_group_info_verify_invalid_peer_id() {
|
||||
let group_name = "test_group".to_string();
|
||||
let group_secret = "secret123".to_string();
|
||||
let peer_id = 42u32;
|
||||
|
||||
let peer_group_info =
|
||||
PeerGroupInfo::generate_with_proof(group_name, group_secret.clone(), peer_id);
|
||||
|
||||
// Verification should fail with a wrong peer_id
|
||||
assert!(!peer_group_info.verify(&group_secret, 999u32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_group_info_different_groups_different_proofs() {
|
||||
let group_secret = "secret123".to_string();
|
||||
let peer_id = 42u32;
|
||||
|
||||
let group1 =
|
||||
PeerGroupInfo::generate_with_proof("group1".to_string(), group_secret.clone(), peer_id);
|
||||
let group2 =
|
||||
PeerGroupInfo::generate_with_proof("group2".to_string(), group_secret, peer_id);
|
||||
|
||||
// Different group names should produce different proofs
|
||||
assert_ne!(group1.group_proof, group2.group_proof);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_group_info_same_params_same_proof() {
|
||||
let group_name = "test_group".to_string();
|
||||
let group_secret = "secret123".to_string();
|
||||
let peer_id = 42u32;
|
||||
|
||||
let group1 =
|
||||
PeerGroupInfo::generate_with_proof(group_name.clone(), group_secret.clone(), peer_id);
|
||||
let group2 = PeerGroupInfo::generate_with_proof(group_name, group_secret, peer_id);
|
||||
|
||||
// Same parameters should produce the same proof
|
||||
assert_eq!(group1.group_proof, group2.group_proof);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_group_info_empty_group_name() {
|
||||
let group_name = "".to_string();
|
||||
let group_secret = "secret123".to_string();
|
||||
let peer_id = 42u32;
|
||||
|
||||
let peer_group_info =
|
||||
PeerGroupInfo::generate_with_proof(group_name.clone(), group_secret.clone(), peer_id);
|
||||
|
||||
assert_eq!(peer_group_info.group_name, group_name);
|
||||
assert!(peer_group_info.verify(&group_secret, peer_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_group_info_empty_secret() {
|
||||
let group_name = "test_group".to_string();
|
||||
let group_secret = "".to_string();
|
||||
let peer_id = 42u32;
|
||||
|
||||
let peer_group_info =
|
||||
PeerGroupInfo::generate_with_proof(group_name, group_secret.clone(), peer_id);
|
||||
|
||||
assert!(peer_group_info.verify(&group_secret, peer_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_group_info_unicode_group_name() {
|
||||
let group_name = "测试组🚀".to_string();
|
||||
let group_secret = "secret123".to_string();
|
||||
let peer_id = 42u32;
|
||||
|
||||
let peer_group_info =
|
||||
PeerGroupInfo::generate_with_proof(group_name.clone(), group_secret.clone(), peer_id);
|
||||
|
||||
assert_eq!(peer_group_info.group_name, group_name);
|
||||
assert!(peer_group_info.verify(&group_secret, peer_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_group_info_unicode_secret() {
|
||||
let group_name = "test_group".to_string();
|
||||
let group_secret = "密码123🔐".to_string();
|
||||
let peer_id = 42u32;
|
||||
|
||||
let peer_group_info =
|
||||
PeerGroupInfo::generate_with_proof(group_name, group_secret.clone(), peer_id);
|
||||
|
||||
assert!(peer_group_info.verify(&group_secret, peer_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_group_info_zero_peer_id() {
|
||||
let group_name = "test_group".to_string();
|
||||
let group_secret = "secret123".to_string();
|
||||
let peer_id = 0u32;
|
||||
|
||||
let peer_group_info =
|
||||
PeerGroupInfo::generate_with_proof(group_name, group_secret.clone(), peer_id);
|
||||
|
||||
assert!(peer_group_info.verify(&group_secret, peer_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_group_info_max_peer_id() {
|
||||
let group_name = "test_group".to_string();
|
||||
let group_secret = "secret123".to_string();
|
||||
let peer_id = u32::MAX;
|
||||
|
||||
let peer_group_info =
|
||||
PeerGroupInfo::generate_with_proof(group_name, group_secret.clone(), peer_id);
|
||||
|
||||
assert!(peer_group_info.verify(&group_secret, peer_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn perf_test_generate_with_proof() {
|
||||
let group_name = "test_group".to_string();
|
||||
let group_secret = "secret123".to_string();
|
||||
let peer_id = 42u32;
|
||||
let iterations = 100000;
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..iterations {
|
||||
let _ = PeerGroupInfo::generate_with_proof(
|
||||
group_name.clone(),
|
||||
group_secret.clone(),
|
||||
peer_id,
|
||||
);
|
||||
}
|
||||
let duration = start.elapsed();
|
||||
|
||||
println!(
|
||||
"generate_with_proof took {:?} for {} iterations",
|
||||
duration, iterations
|
||||
);
|
||||
println!("Avg time per iteration: {:?}", duration / iterations as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn perf_test_verify() {
|
||||
let group_name = "test_group".to_string();
|
||||
let group_secret = "secret123".to_string();
|
||||
let peer_id = 42u32;
|
||||
let iterations = 100000;
|
||||
|
||||
let peer_group_info =
|
||||
PeerGroupInfo::generate_with_proof(group_name, group_secret.clone(), peer_id);
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..iterations {
|
||||
assert!(peer_group_info.verify(&group_secret, peer_id));
|
||||
}
|
||||
let duration = start.elapsed();
|
||||
|
||||
println!("verify took {:?} for {} iterations", duration, iterations);
|
||||
println!("Avg time per iteration: {:?}", duration / iterations as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trusted_credential_pubkey_hmac_valid() {
|
||||
let credential = TrustedCredentialPubkey {
|
||||
pubkey: vec![7u8; 32],
|
||||
groups: vec!["ops".to_string(), "guest".to_string()],
|
||||
allow_relay: true,
|
||||
expiry_unix: 123456,
|
||||
allowed_proxy_cidrs: vec!["10.0.0.0/24".to_string()],
|
||||
reusable: Some(true),
|
||||
};
|
||||
let tc = TrustedCredentialPubkeyProof::new_signed(credential, "sec-1");
|
||||
|
||||
assert!(tc.verify_credential_hmac("sec-1"));
|
||||
assert!(!tc.verify_credential_hmac("sec-2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trusted_credential_pubkey_hmac_tampered() {
|
||||
let credential = TrustedCredentialPubkey {
|
||||
pubkey: vec![8u8; 32],
|
||||
groups: vec!["g1".to_string()],
|
||||
allow_relay: false,
|
||||
expiry_unix: 1,
|
||||
allowed_proxy_cidrs: vec![],
|
||||
reusable: Some(true),
|
||||
};
|
||||
let tc = TrustedCredentialPubkeyProof::new_signed(credential, "sec-1");
|
||||
|
||||
let mut tampered = tc.clone();
|
||||
tampered.credential.as_mut().unwrap().allow_relay = true;
|
||||
assert!(!tampered.verify_credential_hmac("sec-1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trusted_credential_pubkey_hmac_with_raw_bytes() {
|
||||
let credential = TrustedCredentialPubkey {
|
||||
pubkey: vec![9u8; 32],
|
||||
groups: vec!["raw".to_string()],
|
||||
allow_relay: true,
|
||||
expiry_unix: 123456,
|
||||
allowed_proxy_cidrs: vec![],
|
||||
reusable: Some(true),
|
||||
};
|
||||
|
||||
let mut raw_credential_bytes = credential.encode_to_vec();
|
||||
prost::encoding::encode_key(
|
||||
9999,
|
||||
prost::encoding::WireType::Varint,
|
||||
&mut raw_credential_bytes,
|
||||
);
|
||||
prost::encoding::encode_varint(42, &mut raw_credential_bytes);
|
||||
|
||||
let proof = TrustedCredentialPubkeyProof {
|
||||
credential: Some(credential),
|
||||
credential_hmac: TrustedCredentialPubkeyProof::generate_credential_hmac_from_bytes(
|
||||
&raw_credential_bytes,
|
||||
"sec-1",
|
||||
),
|
||||
};
|
||||
|
||||
assert!(proof.verify_credential_hmac_with_bytes(&raw_credential_bytes, "sec-1"));
|
||||
assert!(!proof.verify_credential_hmac("sec-1"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//! Utility functions used by generated code; this is *not* part of the crate's public API!
|
||||
use bytes;
|
||||
use prost;
|
||||
|
||||
use super::controller;
|
||||
use super::descriptor;
|
||||
use super::descriptor::ServiceDescriptor;
|
||||
use super::error;
|
||||
use super::handler;
|
||||
use super::handler::Handler;
|
||||
|
||||
/// Efficiently decode a particular message type from a byte buffer.
|
||||
pub fn decode<M>(buf: bytes::Bytes) -> error::Result<M>
|
||||
where
|
||||
M: prost::Message + Default,
|
||||
{
|
||||
let message = prost::Message::decode(buf)?;
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
/// Efficiently encode a particular message into a byte buffer.
|
||||
pub fn encode<M>(message: M) -> error::Result<bytes::Bytes>
|
||||
where
|
||||
M: prost::Message,
|
||||
{
|
||||
let len = prost::Message::encoded_len(&message);
|
||||
let mut buf = ::bytes::BytesMut::with_capacity(len);
|
||||
prost::Message::encode(&message, &mut buf)?;
|
||||
Ok(buf.freeze())
|
||||
}
|
||||
|
||||
pub async fn call_method<H, I, O>(
|
||||
handler: H,
|
||||
ctrl: H::Controller,
|
||||
method: <H::Descriptor as descriptor::ServiceDescriptor>::Method,
|
||||
input: I,
|
||||
) -> super::error::Result<O>
|
||||
where
|
||||
H: handler::Handler,
|
||||
I: prost::Message,
|
||||
O: prost::Message + Default,
|
||||
{
|
||||
let input_bytes = encode(input)?;
|
||||
let ret_msg = handler.call(ctrl, method, input_bytes).await?;
|
||||
decode(ret_msg)
|
||||
}
|
||||
|
||||
pub trait RpcClientFactory: Clone + Send + Sync + 'static {
|
||||
type Descriptor: ServiceDescriptor + Default;
|
||||
type ClientImpl;
|
||||
type Controller: controller::Controller;
|
||||
|
||||
fn new(
|
||||
handler: impl Handler<Descriptor = Self::Descriptor, Controller = Self::Controller>,
|
||||
) -> Self::ClientImpl;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
use crate::proto::common::TunnelInfo;
|
||||
|
||||
// Controller must impl clone and all cloned controllers share the same data
|
||||
pub trait Controller: Send + Sync + Clone + 'static {
|
||||
fn timeout_ms(&self) -> i32 {
|
||||
5000
|
||||
}
|
||||
|
||||
fn set_timeout_ms(&mut self, _timeout_ms: i32) {}
|
||||
|
||||
fn set_trace_id(&mut self, _trace_id: i32) {}
|
||||
|
||||
fn trace_id(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn set_raw_input(&mut self, _raw_input: Bytes) {}
|
||||
fn get_raw_input(&self) -> Option<Bytes> {
|
||||
None
|
||||
}
|
||||
|
||||
fn set_tunnel_info(&mut self, _tunnel_info: Option<TunnelInfo>) {}
|
||||
fn get_tunnel_info(&self) -> Option<&TunnelInfo> {
|
||||
None
|
||||
}
|
||||
|
||||
fn set_raw_output(&mut self, _raw_output: Bytes) {}
|
||||
fn get_raw_output(&self) -> Option<Bytes> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BaseControllerRawData {
|
||||
pub raw_input: Option<Bytes>,
|
||||
pub raw_output: Option<Bytes>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BaseController {
|
||||
pub timeout_ms: i32,
|
||||
pub trace_id: i32,
|
||||
pub raw_data: Arc<Mutex<BaseControllerRawData>>,
|
||||
pub tunnel_info: Option<TunnelInfo>,
|
||||
}
|
||||
|
||||
impl Controller for BaseController {
|
||||
fn timeout_ms(&self) -> i32 {
|
||||
self.timeout_ms
|
||||
}
|
||||
|
||||
fn set_timeout_ms(&mut self, timeout_ms: i32) {
|
||||
self.timeout_ms = timeout_ms;
|
||||
}
|
||||
|
||||
fn set_trace_id(&mut self, trace_id: i32) {
|
||||
self.trace_id = trace_id;
|
||||
}
|
||||
|
||||
fn trace_id(&self) -> i32 {
|
||||
self.trace_id
|
||||
}
|
||||
|
||||
fn set_raw_input(&mut self, raw_input: Bytes) {
|
||||
self.raw_data.lock().unwrap().raw_input = Some(raw_input);
|
||||
}
|
||||
|
||||
fn get_raw_input(&self) -> Option<Bytes> {
|
||||
self.raw_data.lock().unwrap().raw_input.clone()
|
||||
}
|
||||
|
||||
fn set_raw_output(&mut self, raw_output: Bytes) {
|
||||
self.raw_data.lock().unwrap().raw_output = Some(raw_output);
|
||||
}
|
||||
|
||||
fn get_raw_output(&self) -> Option<Bytes> {
|
||||
self.raw_data.lock().unwrap().raw_output.clone()
|
||||
}
|
||||
|
||||
fn get_tunnel_info(&self) -> Option<&TunnelInfo> {
|
||||
self.tunnel_info.as_ref()
|
||||
}
|
||||
|
||||
fn set_tunnel_info(&mut self, tunnel_info: Option<TunnelInfo>) {
|
||||
self.tunnel_info = tunnel_info;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BaseController {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
timeout_ms: 5000,
|
||||
trace_id: 0,
|
||||
raw_data: Arc::new(Mutex::new(BaseControllerRawData {
|
||||
raw_input: None,
|
||||
raw_output: None,
|
||||
})),
|
||||
tunnel_info: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//! Traits for defining generic service descriptor definitions.
|
||||
//!
|
||||
//! These traits are built on the assumption that some form of code generation is being used (e.g.
|
||||
//! using only `&'static str`s) but it's of course possible to implement these traits manually.
|
||||
use std::any;
|
||||
use std::fmt;
|
||||
|
||||
/// A descriptor for an available RPC service.
|
||||
pub trait ServiceDescriptor: Clone + fmt::Debug + Send + Sync {
|
||||
/// The associated type of method descriptors.
|
||||
type Method: MethodDescriptor + fmt::Debug + TryFrom<u8>;
|
||||
|
||||
/// The name of the service, used in Rust code and perhaps for human readability.
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// The raw protobuf name of the service.
|
||||
fn proto_name(&self) -> &'static str;
|
||||
|
||||
/// The package name of the service.
|
||||
fn package(&self) -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
/// All of the available methods on the service.
|
||||
fn methods(&self) -> &'static [Self::Method];
|
||||
}
|
||||
|
||||
/// A descriptor for a method available on an RPC service.
|
||||
pub trait MethodDescriptor: Clone + Copy + fmt::Debug + Send + Sync {
|
||||
/// The name of the service, used in Rust code and perhaps for human readability.
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// The raw protobuf name of the service.
|
||||
fn proto_name(&self) -> &'static str;
|
||||
|
||||
/// The Rust `TypeId` for the input that this method accepts.
|
||||
fn input_type(&self) -> any::TypeId;
|
||||
|
||||
/// The raw protobuf name for the input type that this method accepts.
|
||||
fn input_proto_type(&self) -> &'static str;
|
||||
|
||||
/// The Rust `TypeId` for the output that this method produces.
|
||||
fn output_type(&self) -> any::TypeId;
|
||||
|
||||
/// The raw protobuf name for the output type that this method produces.
|
||||
fn output_proto_type(&self) -> &'static str;
|
||||
|
||||
/// The index of the method in the service descriptor.
|
||||
fn index(&self) -> u8;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//! Error type definitions for errors that can occur during RPC interactions.
|
||||
use std::result;
|
||||
|
||||
use prost;
|
||||
use thiserror;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Rust error: {0}")]
|
||||
ExecutionError(#[from] anyhow::Error),
|
||||
|
||||
#[error("Decode error")]
|
||||
DecodeError,
|
||||
|
||||
#[error("Encode error")]
|
||||
EncodeError,
|
||||
|
||||
#[error("Invalid method index: {0}, service: {1}")]
|
||||
InvalidMethodIndex(u8, String),
|
||||
|
||||
#[error("Invalid service name: {0}, proto name: {1}")]
|
||||
InvalidServiceKey(String, String),
|
||||
|
||||
#[error("Invalid packet: {0}")]
|
||||
MalformatRpcPacket(String),
|
||||
|
||||
#[error("Timeout: {0}")]
|
||||
Timeout(#[from] tokio::time::error::Elapsed),
|
||||
|
||||
#[error("Tunnel error: {0}")]
|
||||
TunnelError(String),
|
||||
|
||||
#[error("Shutdown")]
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
impl From<prost::DecodeError> for Error {
|
||||
fn from(_: prost::DecodeError) -> Self {
|
||||
Error::DecodeError
|
||||
}
|
||||
}
|
||||
|
||||
impl From<prost::EncodeError> for Error {
|
||||
fn from(_: prost::EncodeError) -> Self {
|
||||
Error::EncodeError
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@@ -0,0 +1,76 @@
|
||||
//! Traits for defining generic RPC handlers.
|
||||
use crate::proto::rpc_types::descriptor::MethodDescriptor;
|
||||
|
||||
use super::{
|
||||
controller::Controller,
|
||||
descriptor::{self, ServiceDescriptor},
|
||||
};
|
||||
use bytes;
|
||||
|
||||
/// An implementation of a specific RPC handler.
|
||||
///
|
||||
/// This can be an actual implementation of a service, or something that will send a request over
|
||||
/// a network to fulfill a request.
|
||||
#[async_trait::async_trait]
|
||||
pub trait Handler: Clone + Send + Sync + 'static {
|
||||
/// The service descriptor for the service whose requests this handler can handle.
|
||||
type Descriptor: descriptor::ServiceDescriptor + Default;
|
||||
|
||||
type Controller: super::controller::Controller;
|
||||
|
||||
/// Perform a raw call to the specified service and method.
|
||||
async fn call(
|
||||
&self,
|
||||
ctrl: Self::Controller,
|
||||
method: <Self::Descriptor as descriptor::ServiceDescriptor>::Method,
|
||||
input: bytes::Bytes,
|
||||
) -> super::error::Result<bytes::Bytes>;
|
||||
|
||||
fn service_descriptor(&self) -> Self::Descriptor {
|
||||
Self::Descriptor::default()
|
||||
}
|
||||
|
||||
fn get_method_from_index(
|
||||
&self,
|
||||
index: u8,
|
||||
) -> super::error::Result<<Self::Descriptor as descriptor::ServiceDescriptor>::Method> {
|
||||
let desc = self.service_descriptor();
|
||||
<Self::Descriptor as descriptor::ServiceDescriptor>::Method::try_from(index)
|
||||
.map_err(|_| super::error::Error::InvalidMethodIndex(index, desc.name().to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait HandlerExt: Send + Sync + 'static {
|
||||
type Controller;
|
||||
|
||||
async fn call_method(
|
||||
&self,
|
||||
ctrl: Self::Controller,
|
||||
method_index: u8,
|
||||
input: bytes::Bytes,
|
||||
) -> super::error::Result<bytes::Bytes>;
|
||||
|
||||
fn get_method_name(&self, method_index: u8) -> super::error::Result<String>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<C: Controller, T: Handler<Controller = C>> HandlerExt for T {
|
||||
type Controller = C;
|
||||
|
||||
async fn call_method(
|
||||
&self,
|
||||
ctrl: Self::Controller,
|
||||
method_index: u8,
|
||||
input: bytes::Bytes,
|
||||
) -> super::error::Result<bytes::Bytes> {
|
||||
let method = self.get_method_from_index(method_index)?;
|
||||
self.call(ctrl, method, input).await
|
||||
}
|
||||
|
||||
fn get_method_name(&self, method_index: u8) -> super::error::Result<String> {
|
||||
let method = self.get_method_from_index(method_index)?;
|
||||
let name = method.name().to_string();
|
||||
Ok(name)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod __rt;
|
||||
pub mod controller;
|
||||
pub mod descriptor;
|
||||
pub mod error;
|
||||
pub mod handler;
|
||||
@@ -0,0 +1,2 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/tests.rs"));
|
||||
include!(concat!(env!("OUT_DIR"), "/tests.serde.rs"));
|
||||
@@ -0,0 +1,2 @@
|
||||
include!(concat!(env!("OUT_DIR"), "/web.rs"));
|
||||
include!(concat!(env!("OUT_DIR"), "/web.serde.rs"));
|
||||
Reference in New Issue
Block a user