mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-11 05:26:14 -08:00
refactor(config): preserve explicit flags with FlagsPatch and derive CLI arguments from schema (#2585)
This PR refactors EasyTier's configuration flags system across protobuf definitions, TOML/API persistence, runtime reconciliation, and the CLI. It introduces a synthesized FlagsPatch protobuf message with optionize and pbjson support to track field presence reliably, replaces manual CLI flag boilerplate with dynamic schema-driven clap argument generation, and establishes Flags::resolve(patch) as the single source of truth for runtime defaults. Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
018750a785
commit
250c2ad8d6
52 files changed
+1909
-1405
No files matched your search
Generated
+42
-1
@@ -2572,6 +2572,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"pnet_datalink",
|
||||
"prost 0.14.4",
|
||||
"prost-types 0.14.4",
|
||||
"quanta",
|
||||
"quinn",
|
||||
"quinn-proto",
|
||||
@@ -2669,6 +2670,7 @@ dependencies = [
|
||||
"hyper-util",
|
||||
"idna",
|
||||
"openssl",
|
||||
"optionize",
|
||||
"ordered_hash_map",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
@@ -2676,6 +2678,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"prefix-trie 0.10.1",
|
||||
"prost 0.14.4",
|
||||
"prost-reflect 0.16.5",
|
||||
"prost-types 0.14.4",
|
||||
"quanta",
|
||||
"rand 0.8.8",
|
||||
@@ -2794,7 +2797,7 @@ dependencies = [
|
||||
"flate2",
|
||||
"gethostname",
|
||||
"once_cell",
|
||||
"prost-reflect",
|
||||
"prost-reflect 0.14.7",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2842,11 +2845,13 @@ dependencies = [
|
||||
"cidr",
|
||||
"hmac 0.12.1",
|
||||
"indoc",
|
||||
"optionize",
|
||||
"pbjson",
|
||||
"pbjson-build",
|
||||
"proc-macro2",
|
||||
"prost 0.14.4",
|
||||
"prost-build",
|
||||
"prost-reflect 0.16.5",
|
||||
"prost-types 0.14.4",
|
||||
"prost-wkt-types",
|
||||
"quote",
|
||||
@@ -2854,6 +2859,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"strum 0.28.0",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"url",
|
||||
@@ -2917,6 +2923,7 @@ dependencies = [
|
||||
"mimalloc",
|
||||
"oauth2-reqwest",
|
||||
"openidconnect",
|
||||
"optionize",
|
||||
"password-auth",
|
||||
"prost 0.14.4",
|
||||
"rand 0.8.8",
|
||||
@@ -6384,6 +6391,30 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "optionize"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b6a0111dde86433acde952fdb2757eb65a79ae0e3e58acf378544280d9c3104"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"optionize-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "optionize-macros"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92f30b98a08383cf48fd3d5cc3e3dd27b4a537d94e8488aadff7f3625288b360"
|
||||
dependencies = [
|
||||
"darling 0.24.1",
|
||||
"derive_more",
|
||||
"proc-macro-crate 3.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "2.10.1"
|
||||
@@ -7248,6 +7279,16 @@ dependencies = [
|
||||
"prost-types 0.13.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-reflect"
|
||||
version = "0.16.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01b80ea363c31af2de2b92e3c07ed1156628f7838c4afb4df75ee78a37fedbd1"
|
||||
dependencies = [
|
||||
"prost 0.14.4",
|
||||
"prost-types 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-reflect-derive"
|
||||
version = "0.14.0"
|
||||
|
||||
@@ -22,6 +22,7 @@ edition = "2024"
|
||||
rust-version = "1.95"
|
||||
|
||||
[workspace.dependencies]
|
||||
optionize = "0.5"
|
||||
# Shared versions and sources; members select features and optional dependencies.
|
||||
anyhow = "1.0"
|
||||
arc-swap = "1.9"
|
||||
|
||||
+55
-18
@@ -1,6 +1,8 @@
|
||||
use easytier::proto::ALL_DESCRIPTOR_BYTES;
|
||||
use once_cell::sync::Lazy;
|
||||
use prost_reflect::{Cardinality, DescriptorPool, FieldDescriptor, Kind, MessageDescriptor};
|
||||
use prost_reflect::{
|
||||
Cardinality, DescriptorPool, FieldDescriptor, FileDescriptor, Kind, MessageDescriptor,
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
@@ -204,7 +206,15 @@ fn build_node(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_map_entry_node(message_desc: &MessageDescriptor) -> NetworkConfigSchema {
|
||||
/// The messages being expanded further up, by full name. The pool carries the
|
||||
/// descriptor's own schema, whose messages refer back to each other, so a type
|
||||
/// that reaches one it is already inside stops instead of recursing forever.
|
||||
type ExpansionPath = Vec<String>;
|
||||
|
||||
fn build_map_entry_node(
|
||||
message_desc: &MessageDescriptor,
|
||||
path: &mut ExpansionPath,
|
||||
) -> NetworkConfigSchema {
|
||||
let key_field = message_desc.map_entry_key_field();
|
||||
let value_field = message_desc.map_entry_value_field();
|
||||
|
||||
@@ -221,35 +231,49 @@ fn build_map_entry_node(message_desc: &MessageDescriptor) -> NetworkConfigSchema
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
vec![
|
||||
build_schema_field_node(&key_field),
|
||||
build_schema_field_node(&value_field),
|
||||
build_schema_field_node(&key_field, path),
|
||||
build_schema_field_node(&value_field, path),
|
||||
],
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn field_children(field: &FieldDescriptor) -> Vec<NetworkConfigSchema> {
|
||||
fn field_children(field: &FieldDescriptor, path: &mut ExpansionPath) -> Vec<NetworkConfigSchema> {
|
||||
if field.is_map()
|
||||
&& let Kind::Message(message_desc) = field.kind()
|
||||
{
|
||||
return vec![build_map_entry_node(&message_desc)];
|
||||
return vec![build_map_entry_node(&message_desc, path)];
|
||||
}
|
||||
|
||||
match field.kind() {
|
||||
Kind::Message(message_desc) => build_message_children(&message_desc),
|
||||
Kind::Message(message_desc) => build_message_children(&message_desc, path),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_message_children(message_desc: &MessageDescriptor) -> Vec<NetworkConfigSchema> {
|
||||
message_desc
|
||||
.fields()
|
||||
.filter(should_expose_field)
|
||||
.map(|field| build_schema_field_node(&field))
|
||||
.collect()
|
||||
fn build_message_children(
|
||||
message_desc: &MessageDescriptor,
|
||||
path: &mut ExpansionPath,
|
||||
) -> Vec<NetworkConfigSchema> {
|
||||
let full_name = message_desc.full_name().to_string();
|
||||
if path.contains(&full_name) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
path.push(full_name);
|
||||
let mut children = Vec::new();
|
||||
for field in message_desc.fields().filter(should_expose_field) {
|
||||
children.push(build_schema_field_node(&field, path));
|
||||
}
|
||||
path.pop();
|
||||
|
||||
children
|
||||
}
|
||||
|
||||
fn build_schema_field_node(field: &FieldDescriptor) -> NetworkConfigSchema {
|
||||
fn build_schema_field_node(
|
||||
field: &FieldDescriptor,
|
||||
path: &mut ExpansionPath,
|
||||
) -> NetworkConfigSchema {
|
||||
build_node(
|
||||
"field",
|
||||
field.name().to_string(),
|
||||
@@ -262,17 +286,26 @@ fn build_schema_field_node(field: &FieldDescriptor) -> NetworkConfigSchema {
|
||||
field_default_value_text(field),
|
||||
enum_options(field.kind()),
|
||||
build_validations(field),
|
||||
field_children(field),
|
||||
field_children(field, path),
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
/// The descriptor's own schema is not configuration surface, and its messages
|
||||
/// are the ones that refer back to each other.
|
||||
fn is_descriptor_schema(file: &FileDescriptor) -> bool {
|
||||
file.name() == "google/protobuf/descriptor.proto"
|
||||
}
|
||||
|
||||
fn collect_definitions() -> Vec<NetworkConfigSchema> {
|
||||
let mut definitions = Vec::new();
|
||||
|
||||
for message_desc in descriptor_pool().all_messages() {
|
||||
let full_name = message_desc.full_name();
|
||||
if full_name == NETWORK_CONFIG_MESSAGE_NAME || message_desc.is_map_entry() {
|
||||
if full_name == NETWORK_CONFIG_MESSAGE_NAME
|
||||
|| message_desc.is_map_entry()
|
||||
|| is_descriptor_schema(&message_desc.parent_file())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -288,12 +321,16 @@ fn collect_definitions() -> Vec<NetworkConfigSchema> {
|
||||
None,
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
build_message_children(&message_desc),
|
||||
build_message_children(&message_desc, &mut Vec::new()),
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
|
||||
for enum_desc in descriptor_pool().all_enums() {
|
||||
if is_descriptor_schema(&enum_desc.parent_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
definitions.push(build_node(
|
||||
"enum",
|
||||
enum_desc.full_name().to_string(),
|
||||
@@ -329,7 +366,7 @@ fn build_network_config_schema() -> NetworkConfigSchema {
|
||||
None,
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
build_message_children(&network_config),
|
||||
build_message_children(&network_config, &mut Vec::new()),
|
||||
collect_definitions(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ crate-type = ["rlib", "cdylib"]
|
||||
wasm-opt = ["-Oz", "--enable-bulk-memory", "--enable-nontrapping-float-to-int"]
|
||||
|
||||
[dependencies]
|
||||
optionize.workspace = true
|
||||
anyhow.workspace = true
|
||||
ariadne = { version = "0.6", optional = true }
|
||||
arc-swap.workspace = true
|
||||
@@ -164,6 +165,8 @@ zstd = ["dep:zstd"]
|
||||
|
||||
[dev-dependencies]
|
||||
futures = { workspace = true, features = ["executor"] }
|
||||
# Tests read the (easytier.flag) annotations off the schema itself.
|
||||
prost-reflect = "0.16.4"
|
||||
|
||||
[target.'cfg(not(target_os = "wasi"))'.dev-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "test-util"] }
|
||||
|
||||
@@ -4,8 +4,10 @@ use easytier_proto::api::manage::{
|
||||
self, NetworkConfig, NetworkingMethod, PortForwardConfig as ApiPortForwardConfig,
|
||||
};
|
||||
|
||||
use optionize::Optionizable as _;
|
||||
|
||||
use super::{
|
||||
api_input::managed_credential_to_proto,
|
||||
api_input::set_network_flags,
|
||||
toml::{ConfigLoader as _, TomlConfig},
|
||||
};
|
||||
|
||||
@@ -125,61 +127,12 @@ pub fn network_config_from_toml(config: &TomlConfig) -> NetworkConfig {
|
||||
result.managed_credentials = config
|
||||
.get_managed_credentials()
|
||||
.into_iter()
|
||||
.map(managed_credential_to_proto)
|
||||
.map(|credential| credential.downgrade())
|
||||
.collect();
|
||||
|
||||
let flags = config.get_flags();
|
||||
let default_flags = default_config.get_flags();
|
||||
result.latency_first = Some(flags.latency_first);
|
||||
result.dev_name = Some(flags.dev_name.clone());
|
||||
result.use_smoltcp = Some(flags.use_smoltcp);
|
||||
result.disable_ipv6 = Some(!flags.enable_ipv6);
|
||||
result.enable_kcp_proxy = Some(flags.enable_kcp_proxy);
|
||||
result.disable_kcp_input = Some(flags.disable_kcp_input);
|
||||
result.enable_quic_proxy = Some(flags.enable_quic_proxy);
|
||||
result.disable_quic_input = Some(flags.disable_quic_input);
|
||||
result.disable_p2p = Some(flags.disable_p2p);
|
||||
result.p2p_only = Some(flags.p2p_only);
|
||||
result.lazy_p2p = Some(flags.lazy_p2p);
|
||||
result.bind_device = Some(flags.bind_device);
|
||||
result.socket_mark = flags.socket_mark;
|
||||
result.no_tun = Some(flags.no_tun);
|
||||
result.enable_exit_node = Some(flags.enable_exit_node);
|
||||
result.relay_all_peer_rpc = Some(flags.relay_all_peer_rpc);
|
||||
result.need_p2p = Some(flags.need_p2p);
|
||||
result.multi_thread = Some(flags.multi_thread);
|
||||
result.proxy_forward_by_system = Some(flags.proxy_forward_by_system);
|
||||
result.disable_encryption = Some(!flags.enable_encryption);
|
||||
result.disable_tcp_hole_punching = Some(flags.disable_tcp_hole_punching);
|
||||
result.disable_udp_hole_punching = Some(flags.disable_udp_hole_punching);
|
||||
result.disable_upnp = Some(flags.disable_upnp);
|
||||
result.disable_relay_data = Some(flags.disable_relay_data);
|
||||
result.prefer_peer_relay = Some(flags.prefer_peer_relay);
|
||||
result.enable_udp_broadcast_relay = Some(flags.enable_udp_broadcast_relay);
|
||||
result.disable_sym_hole_punching = Some(flags.disable_sym_hole_punching);
|
||||
result.enable_magic_dns = Some(flags.accept_dns);
|
||||
result.mtu = Some(flags.mtu as i32);
|
||||
result.data_compress_algo = (flags.data_compress_algo != default_flags.data_compress_algo)
|
||||
.then_some(flags.data_compress_algo);
|
||||
result.encryption_algorithm = (flags.encryption_algorithm
|
||||
!= default_flags.encryption_algorithm)
|
||||
.then_some(flags.encryption_algorithm);
|
||||
result.instance_recv_bps_limit =
|
||||
(flags.instance_recv_bps_limit != u64::MAX).then_some(flags.instance_recv_bps_limit);
|
||||
result.enable_private_mode = Some(flags.private_mode);
|
||||
set_network_flags(&mut result, config.get_flags_patch());
|
||||
result.acl = config.get_acl();
|
||||
|
||||
if flags.relay_network_whitelist == "*" {
|
||||
result.enable_relay_network_whitelist = Some(false);
|
||||
} else {
|
||||
result.enable_relay_network_whitelist = Some(true);
|
||||
result.relay_network_whitelist = flags
|
||||
.relay_network_whitelist
|
||||
.split_whitespace()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,20 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use anyhow::Context;
|
||||
use easytier_proto::api::manage;
|
||||
use easytier_proto::{api::manage, common::FlagsPatch};
|
||||
|
||||
#[cfg(all(
|
||||
feature = "browser-config",
|
||||
any(test, all(target_arch = "wasm32", target_os = "unknown"))
|
||||
))]
|
||||
use easytier_proto::common::Flags;
|
||||
use optionize::{Optionizable, Optionized};
|
||||
|
||||
use crate::config::{
|
||||
MappedListenerPolicy, normalize_secure_mode_config,
|
||||
toml::{
|
||||
ConfigLoader, ManagedCredentialConfig, NetworkIdentity, PeerConfig, PortForwardConfig,
|
||||
TomlConfigLoader, VpnPortalClientConfig, VpnPortalConfig, gen_default_flags,
|
||||
ConfigLoader, NetworkIdentity, PeerConfig, PortForwardConfig, TomlConfigLoader,
|
||||
VpnPortalClientConfig, VpnPortalConfig,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -51,32 +58,50 @@ pub fn add_proxy_network_to_config(
|
||||
pub type NetworkingMethod = easytier_proto::api::manage::NetworkingMethod;
|
||||
pub type NetworkConfig = easytier_proto::api::manage::NetworkConfig;
|
||||
|
||||
pub(crate) fn managed_credential_from_proto(
|
||||
credential: &manage::ManagedCredentialConfig,
|
||||
) -> ManagedCredentialConfig {
|
||||
ManagedCredentialConfig {
|
||||
credential_id: credential.credential_id.clone(),
|
||||
credential_secret: credential.credential_secret.clone(),
|
||||
groups: credential.groups.clone(),
|
||||
allow_relay: credential.allow_relay,
|
||||
allowed_proxy_cidrs: credential.allowed_proxy_cidrs.clone(),
|
||||
expiry_unix: credential.expiry_unix,
|
||||
reusable: credential.reusable.unwrap_or(true),
|
||||
}
|
||||
}
|
||||
pub(crate) fn set_network_flags(result: &mut NetworkConfig, flags: FlagsPatch) {
|
||||
result.latency_first = flags.latency_first;
|
||||
result.dev_name = flags.dev_name;
|
||||
result.use_smoltcp = flags.use_smoltcp;
|
||||
result.disable_ipv6 = flags.enable_ipv6.map(|enabled| !enabled);
|
||||
result.enable_kcp_proxy = flags.enable_kcp_proxy;
|
||||
result.disable_kcp_input = flags.disable_kcp_input;
|
||||
result.enable_quic_proxy = flags.enable_quic_proxy;
|
||||
result.disable_quic_input = flags.disable_quic_input;
|
||||
result.disable_p2p = flags.disable_p2p;
|
||||
result.p2p_only = flags.p2p_only;
|
||||
result.lazy_p2p = flags.lazy_p2p;
|
||||
result.bind_device = flags.bind_device;
|
||||
result.socket_mark = flags.socket_mark;
|
||||
result.no_tun = flags.no_tun;
|
||||
result.enable_exit_node = flags.enable_exit_node;
|
||||
result.relay_all_peer_rpc = flags.relay_all_peer_rpc;
|
||||
result.need_p2p = flags.need_p2p;
|
||||
result.multi_thread = flags.multi_thread;
|
||||
result.proxy_forward_by_system = flags.proxy_forward_by_system;
|
||||
result.disable_encryption = flags.enable_encryption.map(|enabled| !enabled);
|
||||
result.disable_tcp_hole_punching = flags.disable_tcp_hole_punching;
|
||||
result.disable_udp_hole_punching = flags.disable_udp_hole_punching;
|
||||
result.disable_upnp = flags.disable_upnp;
|
||||
result.disable_relay_data = flags.disable_relay_data;
|
||||
result.prefer_peer_relay = flags.prefer_peer_relay;
|
||||
result.enable_udp_broadcast_relay = flags.enable_udp_broadcast_relay;
|
||||
result.disable_sym_hole_punching = flags.disable_sym_hole_punching;
|
||||
result.enable_magic_dns = flags.accept_dns;
|
||||
result.mtu = flags.mtu.map(|mtu| mtu as i32);
|
||||
result.data_compress_algo = flags.data_compress_algo;
|
||||
result.encryption_algorithm = flags.encryption_algorithm;
|
||||
result.instance_recv_bps_limit = flags.instance_recv_bps_limit;
|
||||
result.enable_private_mode = flags.private_mode;
|
||||
|
||||
pub(crate) fn managed_credential_to_proto(
|
||||
credential: ManagedCredentialConfig,
|
||||
) -> manage::ManagedCredentialConfig {
|
||||
manage::ManagedCredentialConfig {
|
||||
credential_id: credential.credential_id,
|
||||
credential_secret: credential.credential_secret,
|
||||
groups: credential.groups,
|
||||
allow_relay: credential.allow_relay,
|
||||
allowed_proxy_cidrs: credential.allowed_proxy_cidrs,
|
||||
expiry_unix: credential.expiry_unix,
|
||||
reusable: Some(credential.reusable),
|
||||
}
|
||||
result.enable_relay_network_whitelist = flags
|
||||
.relay_network_whitelist
|
||||
.as_ref()
|
||||
.map(|list| list != "*");
|
||||
result.relay_network_whitelist = flags
|
||||
.relay_network_whitelist
|
||||
.filter(|list| list != "*")
|
||||
.map(|list| list.split_whitespace().map(ToOwned::to_owned).collect())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
pub trait NetworkConfigExt {
|
||||
@@ -112,47 +137,6 @@ const FORM_MANAGED_TOML_FIELDS: &[&str] = &[
|
||||
"managed_credentials",
|
||||
];
|
||||
|
||||
#[cfg(all(
|
||||
feature = "browser-config",
|
||||
any(test, all(target_arch = "wasm32", target_os = "unknown"))
|
||||
))]
|
||||
const FORM_MANAGED_FLAG_FIELDS: &[&str] = &[
|
||||
"latency_first",
|
||||
"dev_name",
|
||||
"use_smoltcp",
|
||||
"enable_ipv6",
|
||||
"enable_kcp_proxy",
|
||||
"disable_kcp_input",
|
||||
"enable_quic_proxy",
|
||||
"disable_quic_input",
|
||||
"disable_p2p",
|
||||
"p2p_only",
|
||||
"lazy_p2p",
|
||||
"bind_device",
|
||||
"socket_mark",
|
||||
"no_tun",
|
||||
"enable_exit_node",
|
||||
"relay_all_peer_rpc",
|
||||
"need_p2p",
|
||||
"multi_thread",
|
||||
"proxy_forward_by_system",
|
||||
"enable_encryption",
|
||||
"relay_network_whitelist",
|
||||
"disable_tcp_hole_punching",
|
||||
"disable_udp_hole_punching",
|
||||
"disable_upnp",
|
||||
"disable_relay_data",
|
||||
"prefer_peer_relay",
|
||||
"enable_udp_broadcast_relay",
|
||||
"disable_sym_hole_punching",
|
||||
"accept_dns",
|
||||
"mtu",
|
||||
"instance_recv_bps_limit",
|
||||
"private_mode",
|
||||
"encryption_algorithm",
|
||||
"data_compress_algo",
|
||||
];
|
||||
|
||||
#[cfg(all(
|
||||
feature = "browser-config",
|
||||
any(test, all(target_arch = "wasm32", target_os = "unknown"))
|
||||
@@ -184,7 +168,22 @@ pub(crate) fn merge_network_config_toml(
|
||||
.remove("flags")
|
||||
.and_then(|value| value.try_into().ok())
|
||||
.unwrap_or_default();
|
||||
for key in FORM_MANAGED_FLAG_FIELDS {
|
||||
|
||||
// Normalize any camelCase aliases in original [flags] to their canonical field names
|
||||
// so each field appears at most once in the merged table.
|
||||
for field in Flags::flags() {
|
||||
if let (Some(name), Some(json_name)) = (field.name.as_deref(), field.json_name.as_deref()) {
|
||||
if name != json_name
|
||||
&& let Some(val) = merged_flags.remove(json_name)
|
||||
{
|
||||
merged_flags.entry(name.to_owned()).or_insert(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The keys a config form owns are the flags it offers a control for, which
|
||||
// the schema declares with `(easytier.flag)`.
|
||||
for key in Flags::form() {
|
||||
if let Some(value) = generated_flags.get(*key) {
|
||||
merged_flags.insert((*key).to_owned(), value.clone());
|
||||
} else {
|
||||
@@ -444,8 +443,8 @@ impl NetworkConfigExt for NetworkConfig {
|
||||
cfg.set_managed_credentials(
|
||||
self.managed_credentials
|
||||
.iter()
|
||||
.map(managed_credential_from_proto)
|
||||
.collect(),
|
||||
.map(|credential| credential.clone().upgrade())
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
);
|
||||
|
||||
if let Some(credential_secret) = credential_secret {
|
||||
@@ -465,19 +464,6 @@ impl NetworkConfigExt for NetworkConfig {
|
||||
);
|
||||
}
|
||||
|
||||
let mut flags = gen_default_flags();
|
||||
if let Some(latency_first) = self.latency_first {
|
||||
flags.latency_first = latency_first;
|
||||
}
|
||||
|
||||
if let Some(dev_name) = self.dev_name.clone() {
|
||||
flags.dev_name = dev_name;
|
||||
}
|
||||
|
||||
if let Some(use_smoltcp) = self.use_smoltcp {
|
||||
flags.use_smoltcp = use_smoltcp;
|
||||
}
|
||||
|
||||
if let Some(ipv6_public_addr_provider) = self.ipv6_public_addr_provider {
|
||||
cfg.set_ipv6_public_addr_provider(ipv6_public_addr_provider);
|
||||
}
|
||||
@@ -496,145 +482,59 @@ impl NetworkConfigExt for NetworkConfig {
|
||||
)?));
|
||||
}
|
||||
|
||||
if let Some(disable_ipv6) = self.disable_ipv6 {
|
||||
flags.enable_ipv6 = !disable_ipv6;
|
||||
}
|
||||
|
||||
if let Some(enable_kcp_proxy) = self.enable_kcp_proxy {
|
||||
flags.enable_kcp_proxy = enable_kcp_proxy;
|
||||
}
|
||||
|
||||
if let Some(disable_kcp_input) = self.disable_kcp_input {
|
||||
flags.disable_kcp_input = disable_kcp_input;
|
||||
}
|
||||
|
||||
if let Some(enable_quic_proxy) = self.enable_quic_proxy {
|
||||
flags.enable_quic_proxy = enable_quic_proxy;
|
||||
}
|
||||
|
||||
if let Some(disable_quic_input) = self.disable_quic_input {
|
||||
flags.disable_quic_input = disable_quic_input;
|
||||
}
|
||||
|
||||
if let Some(disable_p2p) = self.disable_p2p {
|
||||
flags.disable_p2p = disable_p2p;
|
||||
}
|
||||
|
||||
if let Some(p2p_only) = self.p2p_only {
|
||||
flags.p2p_only = p2p_only;
|
||||
}
|
||||
|
||||
if let Some(lazy_p2p) = self.lazy_p2p {
|
||||
flags.lazy_p2p = lazy_p2p;
|
||||
}
|
||||
|
||||
if let Some(bind_device) = self.bind_device {
|
||||
flags.bind_device = bind_device;
|
||||
}
|
||||
|
||||
if self.socket_mark.is_some() {
|
||||
flags.socket_mark = self.socket_mark;
|
||||
}
|
||||
|
||||
if let Some(no_tun) = self.no_tun {
|
||||
flags.no_tun = no_tun;
|
||||
}
|
||||
|
||||
if let Some(enable_exit_node) = self.enable_exit_node {
|
||||
flags.enable_exit_node = enable_exit_node;
|
||||
}
|
||||
|
||||
if let Some(relay_all_peer_rpc) = self.relay_all_peer_rpc {
|
||||
flags.relay_all_peer_rpc = relay_all_peer_rpc;
|
||||
}
|
||||
|
||||
if let Some(need_p2p) = self.need_p2p {
|
||||
flags.need_p2p = need_p2p;
|
||||
}
|
||||
|
||||
if let Some(multi_thread) = self.multi_thread {
|
||||
flags.multi_thread = multi_thread;
|
||||
}
|
||||
|
||||
if let Some(proxy_forward_by_system) = self.proxy_forward_by_system {
|
||||
flags.proxy_forward_by_system = proxy_forward_by_system;
|
||||
}
|
||||
|
||||
if let Some(disable_encryption) = self.disable_encryption {
|
||||
flags.enable_encryption = !disable_encryption;
|
||||
}
|
||||
|
||||
if self.enable_relay_network_whitelist.unwrap_or_default() {
|
||||
if !self.relay_network_whitelist.is_empty() {
|
||||
flags.relay_network_whitelist = self.relay_network_whitelist.join(" ");
|
||||
} else {
|
||||
flags.relay_network_whitelist = "".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(disable_tcp_hole_punching) = self.disable_tcp_hole_punching {
|
||||
flags.disable_tcp_hole_punching = disable_tcp_hole_punching;
|
||||
}
|
||||
|
||||
if let Some(disable_udp_hole_punching) = self.disable_udp_hole_punching {
|
||||
flags.disable_udp_hole_punching = disable_udp_hole_punching;
|
||||
}
|
||||
|
||||
if let Some(disable_upnp) = self.disable_upnp {
|
||||
flags.disable_upnp = disable_upnp;
|
||||
}
|
||||
|
||||
if let Some(disable_relay_data) = self.disable_relay_data {
|
||||
flags.disable_relay_data = disable_relay_data;
|
||||
}
|
||||
|
||||
if let Some(prefer_peer_relay) = self.prefer_peer_relay {
|
||||
flags.prefer_peer_relay = prefer_peer_relay;
|
||||
}
|
||||
|
||||
if let Some(enable_udp_broadcast_relay) = self.enable_udp_broadcast_relay {
|
||||
flags.enable_udp_broadcast_relay = enable_udp_broadcast_relay;
|
||||
}
|
||||
|
||||
if let Some(disable_sym_hole_punching) = self.disable_sym_hole_punching {
|
||||
flags.disable_sym_hole_punching = disable_sym_hole_punching;
|
||||
}
|
||||
|
||||
if let Some(enable_magic_dns) = self.enable_magic_dns {
|
||||
flags.accept_dns = enable_magic_dns;
|
||||
}
|
||||
|
||||
if let Some(mtu) = self.mtu {
|
||||
flags.mtu = mtu as u32;
|
||||
}
|
||||
|
||||
if let Some(instance_recv_bps_limit) = self.instance_recv_bps_limit {
|
||||
flags.instance_recv_bps_limit = instance_recv_bps_limit;
|
||||
}
|
||||
|
||||
if let Some(enable_private_mode) = self.enable_private_mode {
|
||||
flags.private_mode = enable_private_mode;
|
||||
}
|
||||
|
||||
if let Some(encryption_algorithm) = self.encryption_algorithm.clone() {
|
||||
flags.encryption_algorithm = encryption_algorithm;
|
||||
}
|
||||
|
||||
if let Some(acl) = self.acl.as_ref()
|
||||
&& !acl.is_empty()
|
||||
{
|
||||
cfg.set_acl(Some(acl.clone()));
|
||||
}
|
||||
|
||||
if let Some(data_compress_algo) = self.data_compress_algo {
|
||||
if data_compress_algo < 1 {
|
||||
flags.data_compress_algo = 1;
|
||||
} else {
|
||||
flags.data_compress_algo = data_compress_algo
|
||||
}
|
||||
}
|
||||
|
||||
cfg.set_flags(flags);
|
||||
cfg.patch_flags(FlagsPatch {
|
||||
dev_name: self.dev_name.clone(),
|
||||
enable_ipv6: self.disable_ipv6.map(|disabled| !disabled),
|
||||
socket_mark: self.socket_mark,
|
||||
enable_encryption: self.disable_encryption.map(|disabled| !disabled),
|
||||
relay_network_whitelist: self.enable_relay_network_whitelist.map(|enabled| {
|
||||
if enabled {
|
||||
self.relay_network_whitelist.join(" ")
|
||||
} else {
|
||||
"*".to_owned()
|
||||
}
|
||||
}),
|
||||
accept_dns: self.enable_magic_dns,
|
||||
mtu: self
|
||||
.mtu
|
||||
.map(u32::try_from)
|
||||
.transpose()
|
||||
.context("invalid mtu: expected a non-negative integer")?,
|
||||
private_mode: self.enable_private_mode,
|
||||
encryption_algorithm: self.encryption_algorithm.clone(),
|
||||
data_compress_algo: self.data_compress_algo.map(|algorithm| algorithm.max(1)),
|
||||
latency_first: self.latency_first,
|
||||
use_smoltcp: self.use_smoltcp,
|
||||
enable_kcp_proxy: self.enable_kcp_proxy,
|
||||
disable_kcp_input: self.disable_kcp_input,
|
||||
enable_quic_proxy: self.enable_quic_proxy,
|
||||
disable_quic_input: self.disable_quic_input,
|
||||
disable_p2p: self.disable_p2p,
|
||||
p2p_only: self.p2p_only,
|
||||
lazy_p2p: self.lazy_p2p,
|
||||
bind_device: self.bind_device,
|
||||
no_tun: self.no_tun,
|
||||
enable_exit_node: self.enable_exit_node,
|
||||
relay_all_peer_rpc: self.relay_all_peer_rpc,
|
||||
need_p2p: self.need_p2p,
|
||||
multi_thread: self.multi_thread,
|
||||
proxy_forward_by_system: self.proxy_forward_by_system,
|
||||
disable_tcp_hole_punching: self.disable_tcp_hole_punching,
|
||||
disable_udp_hole_punching: self.disable_udp_hole_punching,
|
||||
disable_upnp: self.disable_upnp,
|
||||
disable_relay_data: self.disable_relay_data,
|
||||
prefer_peer_relay: self.prefer_peer_relay,
|
||||
enable_udp_broadcast_relay: self.enable_udp_broadcast_relay,
|
||||
disable_sym_hole_punching: self.disable_sym_hole_punching,
|
||||
instance_recv_bps_limit: self.instance_recv_bps_limit,
|
||||
..Default::default()
|
||||
});
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
@@ -764,65 +664,11 @@ impl NetworkConfigExt for NetworkConfig {
|
||||
result.managed_credentials = config
|
||||
.get_managed_credentials()
|
||||
.into_iter()
|
||||
.map(managed_credential_to_proto)
|
||||
.map(|credential| credential.downgrade())
|
||||
.collect();
|
||||
let flags = config.get_flags();
|
||||
let default_flags = default_config.get_flags();
|
||||
result.latency_first = Some(flags.latency_first);
|
||||
result.dev_name = Some(flags.dev_name.clone());
|
||||
result.use_smoltcp = Some(flags.use_smoltcp);
|
||||
result.disable_ipv6 = Some(!flags.enable_ipv6);
|
||||
result.enable_kcp_proxy = Some(flags.enable_kcp_proxy);
|
||||
result.disable_kcp_input = Some(flags.disable_kcp_input);
|
||||
result.enable_quic_proxy = Some(flags.enable_quic_proxy);
|
||||
result.disable_quic_input = Some(flags.disable_quic_input);
|
||||
result.disable_p2p = Some(flags.disable_p2p);
|
||||
result.p2p_only = Some(flags.p2p_only);
|
||||
result.lazy_p2p = Some(flags.lazy_p2p);
|
||||
result.bind_device = Some(flags.bind_device);
|
||||
result.socket_mark = flags.socket_mark;
|
||||
result.no_tun = Some(flags.no_tun);
|
||||
result.enable_exit_node = Some(flags.enable_exit_node);
|
||||
result.relay_all_peer_rpc = Some(flags.relay_all_peer_rpc);
|
||||
result.need_p2p = Some(flags.need_p2p);
|
||||
result.multi_thread = Some(flags.multi_thread);
|
||||
result.proxy_forward_by_system = Some(flags.proxy_forward_by_system);
|
||||
result.disable_encryption = Some(!flags.enable_encryption);
|
||||
result.disable_tcp_hole_punching = Some(flags.disable_tcp_hole_punching);
|
||||
result.disable_udp_hole_punching = Some(flags.disable_udp_hole_punching);
|
||||
result.disable_upnp = Some(flags.disable_upnp);
|
||||
result.disable_relay_data = Some(flags.disable_relay_data);
|
||||
result.prefer_peer_relay = Some(flags.prefer_peer_relay);
|
||||
result.enable_udp_broadcast_relay = Some(flags.enable_udp_broadcast_relay);
|
||||
result.disable_sym_hole_punching = Some(flags.disable_sym_hole_punching);
|
||||
result.enable_magic_dns = Some(flags.accept_dns);
|
||||
result.mtu = Some(flags.mtu as i32);
|
||||
result.data_compress_algo = (flags.data_compress_algo != default_flags.data_compress_algo)
|
||||
.then_some(flags.data_compress_algo);
|
||||
result.encryption_algorithm = (flags.encryption_algorithm
|
||||
!= default_flags.encryption_algorithm)
|
||||
.then_some(flags.encryption_algorithm.clone());
|
||||
result.instance_recv_bps_limit =
|
||||
(flags.instance_recv_bps_limit != u64::MAX).then_some(flags.instance_recv_bps_limit);
|
||||
result.enable_private_mode = Some(flags.private_mode);
|
||||
|
||||
set_network_flags(&mut result, config.get_flags_patch());
|
||||
result.acl = config.get_acl();
|
||||
|
||||
if flags.relay_network_whitelist == "*" {
|
||||
result.enable_relay_network_whitelist = Some(false);
|
||||
} else {
|
||||
result.enable_relay_network_whitelist = Some(true);
|
||||
if flags.relay_network_whitelist.is_empty() {
|
||||
result.relay_network_whitelist = vec![];
|
||||
} else {
|
||||
result.relay_network_whitelist = flags
|
||||
.relay_network_whitelist
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
@@ -832,6 +678,7 @@ mod tests {
|
||||
#![allow(deprecated)]
|
||||
|
||||
use super::*;
|
||||
use easytier_proto::common::Flags;
|
||||
|
||||
fn api_portal_config() -> manage::VpnPortalConfig {
|
||||
manage::VpnPortalConfig {
|
||||
@@ -853,6 +700,103 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The management API's name for a flag is the schema's to state, and the
|
||||
/// projection is what actually names the fields. Turning the flag the
|
||||
/// annotation names must turn the field it points at, in the direction the
|
||||
/// annotation declares.
|
||||
#[test]
|
||||
fn declared_api_names_match_the_projection() {
|
||||
use prost_reflect::{DescriptorPool, Value};
|
||||
|
||||
let pool = DescriptorPool::decode(easytier_proto::ALL_DESCRIPTOR_BYTES).unwrap();
|
||||
let flags = pool.get_message_by_name("common.Flags").unwrap();
|
||||
let extension = pool.get_extension_by_name("easytier.flag").unwrap();
|
||||
|
||||
// A patch states every field, so the values survive serialization:
|
||||
// protobuf JSON omits a field holding its zero value.
|
||||
let patch: FlagsPatch = Flags::defaults().downgrade();
|
||||
let defaults = serde_json::to_value(&patch).unwrap();
|
||||
|
||||
for field in flags.fields() {
|
||||
let name = field.name();
|
||||
let options = field.options();
|
||||
let annotation = options.get_extension(&extension).into_owned();
|
||||
let Value::Message(meta) = annotation else {
|
||||
panic!("{name}: the annotation is not a message");
|
||||
};
|
||||
if !meta.has_field_by_name("api") {
|
||||
continue;
|
||||
}
|
||||
let Value::Message(spelling) = meta.get_field_by_name("api").unwrap().into_owned()
|
||||
else {
|
||||
panic!("{name}: the api spelling is not a message");
|
||||
};
|
||||
let api_field = match spelling.get_field_by_name("field").as_deref() {
|
||||
Some(Value::String(api_field)) if !api_field.is_empty() => api_field.clone(),
|
||||
_ => continue,
|
||||
};
|
||||
let negate = matches!(
|
||||
spelling.get_field_by_name("negate").as_deref(),
|
||||
Some(Value::Bool(true))
|
||||
);
|
||||
|
||||
let declared = defaults[name]
|
||||
.as_bool()
|
||||
.unwrap_or_else(|| panic!("{name} is not a boolean flag"));
|
||||
|
||||
let mut flipped = defaults.clone();
|
||||
flipped[name] = serde_json::json!(!declared);
|
||||
let patch: FlagsPatch = serde_json::from_value(flipped).unwrap();
|
||||
|
||||
let mut projected = NetworkConfig::default();
|
||||
set_network_flags(&mut projected, patch);
|
||||
|
||||
// Protobuf JSON omits a field holding its zero value.
|
||||
let projected = serde_json::to_value(&projected).unwrap();
|
||||
let value = projected[&api_field].as_bool().unwrap_or(false);
|
||||
assert_eq!(
|
||||
value,
|
||||
if negate { declared } else { !declared },
|
||||
"flipping {name} should leave {api_field} as the annotation declares"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "config-write")]
|
||||
#[test]
|
||||
fn api_flags_round_trip_preserves_missing_default_and_custom_values() {
|
||||
for disabled in [None, Some(false), Some(true)] {
|
||||
for whitelist in [None, Some(false), Some(true)] {
|
||||
let input = NetworkConfig {
|
||||
disable_encryption: disabled,
|
||||
enable_relay_network_whitelist: whitelist,
|
||||
latency_first: Some(false),
|
||||
mtu: Some(0),
|
||||
instance_recv_bps_limit: Some(u64::MAX),
|
||||
..standalone_config()
|
||||
};
|
||||
let config = input.gen_config().unwrap();
|
||||
let config = TomlConfigLoader::new_from_str(&config.dump()).unwrap();
|
||||
let output = NetworkConfig::new_from_config(&config).unwrap();
|
||||
assert_eq!(output.disable_encryption, disabled);
|
||||
assert_eq!(output.enable_relay_network_whitelist, whitelist);
|
||||
assert_eq!(output.latency_first, Some(false));
|
||||
assert_eq!(output.mtu, Some(0));
|
||||
assert_eq!(output.instance_recv_bps_limit, Some(u64::MAX));
|
||||
assert_eq!(output.disable_ipv6, None);
|
||||
assert_eq!(output.encryption_algorithm, None);
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
NetworkConfig {
|
||||
mtu: Some(-1),
|
||||
..standalone_config()
|
||||
}
|
||||
.gen_config()
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vpn_portal_api_config_round_trips_through_toml_model() {
|
||||
let input = NetworkConfig {
|
||||
@@ -969,6 +913,46 @@ mod tests {
|
||||
assert!(config.get_vpn_portal_config().is_none());
|
||||
}
|
||||
|
||||
#[cfg(feature = "browser-config")]
|
||||
#[test]
|
||||
fn browser_merge_reconciles_renamed_flags() {
|
||||
let original = r#"
|
||||
[flags]
|
||||
default_protocol = "udp"
|
||||
enable_encryption = false
|
||||
enable_ipv6 = false
|
||||
accept_dns = false
|
||||
private_mode = false
|
||||
"#;
|
||||
|
||||
for value in [Some(false), Some(true), None] {
|
||||
let network_config = NetworkConfig {
|
||||
disable_encryption: value,
|
||||
disable_ipv6: value,
|
||||
enable_magic_dns: value,
|
||||
enable_private_mode: value,
|
||||
..standalone_config()
|
||||
};
|
||||
let merged = merge_network_config_toml(original, &network_config).unwrap();
|
||||
let merged: toml::Table = toml::from_str(&merged).unwrap();
|
||||
let flags = merged["flags"].as_table().unwrap();
|
||||
|
||||
assert_eq!(flags["default_protocol"].as_str(), Some("udp"));
|
||||
for (field, expected) in [
|
||||
("enable_encryption", value.map(|disabled| !disabled)),
|
||||
("enable_ipv6", value.map(|disabled| !disabled)),
|
||||
("accept_dns", value),
|
||||
("private_mode", value),
|
||||
] {
|
||||
assert_eq!(
|
||||
flags.get(field).and_then(toml::Value::as_bool),
|
||||
expected,
|
||||
"merging {field} from API value {value:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "browser-config")]
|
||||
#[test]
|
||||
fn browser_merge_preserves_fields_outside_the_shared_form() {
|
||||
@@ -1006,11 +990,74 @@ disable_p2p = true
|
||||
Some("edited-network")
|
||||
);
|
||||
assert_eq!(merged["flags"]["default_protocol"].as_str(), Some("udp"));
|
||||
assert!(
|
||||
!merged["flags"]
|
||||
.as_table()
|
||||
.unwrap()
|
||||
.contains_key("disable_p2p")
|
||||
assert_eq!(merged["flags"]["disable_p2p"].as_bool(), Some(false));
|
||||
}
|
||||
|
||||
#[cfg(feature = "browser-config")]
|
||||
#[test]
|
||||
fn browser_merge_normalizes_camel_case_flag_aliases() {
|
||||
let original = r#"
|
||||
[flags]
|
||||
disableP2p = true
|
||||
enableEncryption = false
|
||||
foreignRelayBpsLimit = 1234
|
||||
"#;
|
||||
let parsed_orig = TomlConfigLoader::new_from_str(original).unwrap();
|
||||
let mut network_config = NetworkConfig::new_from_config(&parsed_orig).unwrap();
|
||||
network_config.disable_p2p = Some(false);
|
||||
|
||||
let merged = merge_network_config_toml(original, &network_config).unwrap();
|
||||
let parsed = TomlConfigLoader::new_from_str(&merged)
|
||||
.expect("merged config should be parseable without duplicate-field error");
|
||||
assert!(!parsed.get_flags().disable_p2p);
|
||||
assert!(!parsed.get_flags().enable_encryption);
|
||||
assert_eq!(parsed.get_flags().foreign_relay_bps_limit, 1234);
|
||||
|
||||
let merged_table: toml::Table = toml::from_str(&merged).unwrap();
|
||||
let flags = merged_table["flags"].as_table().unwrap();
|
||||
assert_eq!(
|
||||
flags.get("disable_p2p").and_then(toml::Value::as_bool),
|
||||
Some(false)
|
||||
);
|
||||
assert!(flags.get("disableP2p").is_none());
|
||||
assert_eq!(
|
||||
flags
|
||||
.get("enable_encryption")
|
||||
.and_then(toml::Value::as_bool),
|
||||
Some(false)
|
||||
);
|
||||
assert!(flags.get("enableEncryption").is_none());
|
||||
assert_eq!(
|
||||
flags
|
||||
.get("foreign_relay_bps_limit")
|
||||
.and_then(toml::Value::as_integer),
|
||||
Some(1234)
|
||||
);
|
||||
assert!(flags.get("foreignRelayBpsLimit").is_none());
|
||||
}
|
||||
|
||||
#[cfg(feature = "browser-config")]
|
||||
#[test]
|
||||
fn browser_merge_repro_camel_case_disable_p2p_collision() {
|
||||
let original = r#"
|
||||
[flags]
|
||||
disableP2p = false
|
||||
"#;
|
||||
let mut network_config = standalone_config();
|
||||
network_config.hostname = Some("new-hostname".to_owned());
|
||||
network_config.disable_p2p = Some(false);
|
||||
|
||||
let merged = merge_network_config_toml(original, &network_config).unwrap();
|
||||
let parsed = TomlConfigLoader::new_from_str(&merged)
|
||||
.expect("saving config with camelCase alias must not fail on reload");
|
||||
assert!(!parsed.get_flags().disable_p2p);
|
||||
|
||||
let merged_table: toml::Table = toml::from_str(&merged).unwrap();
|
||||
let flags = merged_table["flags"].as_table().unwrap();
|
||||
assert_eq!(
|
||||
flags.get("disable_p2p").and_then(toml::Value::as_bool),
|
||||
Some(false)
|
||||
);
|
||||
assert!(flags.get("disableP2p").is_none());
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,59 @@
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::VariantArray;
|
||||
|
||||
/// Stable configuration vocabulary for every known encryption algorithm.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, VariantArray)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
VariantArray,
|
||||
strum::Display,
|
||||
strum::EnumString,
|
||||
strum::IntoStaticStr,
|
||||
strum::AsRefStr,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[strum(ascii_case_insensitive)]
|
||||
pub enum EncryptionAlgorithm {
|
||||
#[strum(to_string = "xor", serialize = "xor")]
|
||||
#[serde(rename = "xor")]
|
||||
Xor,
|
||||
|
||||
#[default]
|
||||
#[strum(
|
||||
to_string = "aes-gcm",
|
||||
serialize = "aes-gcm",
|
||||
serialize = "openssl-aes-gcm"
|
||||
)]
|
||||
#[serde(rename = "aes-gcm", alias = "openssl-aes-gcm")]
|
||||
AesGcm,
|
||||
|
||||
#[strum(
|
||||
to_string = "aes-256-gcm",
|
||||
serialize = "aes-256-gcm",
|
||||
serialize = "openssl-aes-256-gcm"
|
||||
)]
|
||||
#[serde(rename = "aes-256-gcm", alias = "openssl-aes-256-gcm")]
|
||||
Aes256Gcm,
|
||||
|
||||
#[strum(
|
||||
to_string = "chacha20",
|
||||
serialize = "chacha20",
|
||||
serialize = "chacha20-poly1305",
|
||||
serialize = "openssl-chacha20"
|
||||
)]
|
||||
#[serde(
|
||||
rename = "chacha20",
|
||||
alias = "chacha20-poly1305",
|
||||
alias = "openssl-chacha20"
|
||||
)]
|
||||
ChaCha20,
|
||||
}
|
||||
|
||||
impl EncryptionAlgorithm {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Xor => "xor",
|
||||
Self::AesGcm => "aes-gcm",
|
||||
Self::Aes256Gcm => "aes-256-gcm",
|
||||
Self::ChaCha20 => "chacha20",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EncryptionAlgorithm {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for EncryptionAlgorithm {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
match value.to_ascii_lowercase().as_str() {
|
||||
"xor" => Ok(Self::Xor),
|
||||
"aes-gcm" | "openssl-aes-gcm" => Ok(Self::AesGcm),
|
||||
"aes-256-gcm" | "openssl-aes-256-gcm" => Ok(Self::Aes256Gcm),
|
||||
"chacha20" | "chacha20-poly1305" | "openssl-chacha20" => Ok(Self::ChaCha20),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -48,6 +48,7 @@ use std::{
|
||||
use anyhow::Context as _;
|
||||
use base64::{Engine as _, prelude::BASE64_STANDARD};
|
||||
use easytier_proto::{common as common_pb, core_config as pb};
|
||||
use optionize::Optionizable;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
@@ -280,6 +281,8 @@ pub struct ForeignNetworkConfig {
|
||||
pub cidrs: Vec<IpPrefix>,
|
||||
}
|
||||
|
||||
#[optionize::optionized]
|
||||
#[optionize(object = pb::PeerPolicyConfig)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PeerPolicyConfig {
|
||||
pub p2p_enabled: bool,
|
||||
@@ -522,28 +525,15 @@ impl From<ForeignNetworkConfig> for pb::ForeignNetworkConfig {
|
||||
|
||||
impl From<pb::PeerPolicyConfig> for PeerPolicyConfig {
|
||||
fn from(value: pb::PeerPolicyConfig) -> Self {
|
||||
let default = Self::default();
|
||||
Self {
|
||||
p2p_enabled: value.p2p_enabled.unwrap_or(default.p2p_enabled),
|
||||
relay_peer_rpc: value.relay_peer_rpc.unwrap_or(default.relay_peer_rpc),
|
||||
relay_data: value.relay_data.unwrap_or(default.relay_data),
|
||||
latency_first: value.latency_first.unwrap_or(default.latency_first),
|
||||
encryption_required: value
|
||||
.encryption_required
|
||||
.unwrap_or(default.encryption_required),
|
||||
}
|
||||
let mut config = Self::default();
|
||||
config.load(value);
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PeerPolicyConfig> for pb::PeerPolicyConfig {
|
||||
fn from(value: PeerPolicyConfig) -> Self {
|
||||
Self {
|
||||
p2p_enabled: Some(value.p2p_enabled),
|
||||
relay_peer_rpc: Some(value.relay_peer_rpc),
|
||||
relay_data: Some(value.relay_data),
|
||||
latency_first: Some(value.latency_first),
|
||||
encryption_required: Some(value.encryption_required),
|
||||
}
|
||||
value.downgrade()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use anyhow::Context as _;
|
||||
use cidr::Ipv6Cidr;
|
||||
use easytier_proto::common::{FlagsInConfig, PeerFeatureFlag, SecureModeConfig, StunInfo};
|
||||
use easytier_proto::common::{Flags, PeerFeatureFlag, SecureModeConfig, StunInfo};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::proto::acl::{Acl, AclV1, Action, Chain, ChainType, GroupInfo, Protocol, Rule};
|
||||
@@ -236,7 +236,7 @@ pub struct PeerRuntimeSnapshot {
|
||||
pub runtime: PeerRuntimeConfig,
|
||||
pub easytier_version: String,
|
||||
pub avoid_relay_data_preference: bool,
|
||||
pub flags: FlagsInConfig,
|
||||
pub flags: Flags,
|
||||
pub pinned_peers: Vec<(url::Url, Option<String>)>,
|
||||
pub peer_group_memberships: Vec<PeerGroupIdentity>,
|
||||
pub acl_group_declarations: Vec<PeerGroupIdentity>,
|
||||
@@ -246,7 +246,7 @@ pub struct PeerRuntimeSnapshot {
|
||||
}
|
||||
|
||||
impl PeerRuntimeSnapshot {
|
||||
pub fn new(runtime: PeerRuntimeConfig, flags: FlagsInConfig) -> Self {
|
||||
pub fn new(runtime: PeerRuntimeConfig, flags: Flags) -> Self {
|
||||
let avoid_relay_data_preference = runtime.feature_flags.avoid_relay_data;
|
||||
Self {
|
||||
runtime,
|
||||
@@ -274,7 +274,7 @@ impl Default for PeerRuntimeSnapshot {
|
||||
secure_mode: None,
|
||||
host_routing: HostRoutingPolicy::default(),
|
||||
},
|
||||
FlagsInConfig::default(),
|
||||
Flags::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+124
-212
@@ -11,167 +11,19 @@ pub use super::{EncryptionAlgorithm, gateway::PortForwardConfig};
|
||||
use anyhow::Context;
|
||||
#[cfg(feature = "rich-config-errors")]
|
||||
use ariadne::{CharSet, Config as AriadneConfig, IndexType, Label, Report, ReportKind, Source};
|
||||
use optionize::Optionizable;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::proto::{
|
||||
acl::Acl,
|
||||
common::{CompressionAlgoPb, SecureModeConfig},
|
||||
};
|
||||
use crate::proto::{acl::Acl, common::SecureModeConfig};
|
||||
|
||||
pub const DEFAULT_ET_DNS_ZONE: &str = "et.net.";
|
||||
|
||||
pub type Flags = crate::proto::common::FlagsInConfig;
|
||||
pub use crate::proto::common::{Flags, FlagsPatch};
|
||||
|
||||
pub(crate) fn default_instance_name() -> String {
|
||||
"default".to_owned()
|
||||
}
|
||||
|
||||
pub fn gen_default_flags() -> Flags {
|
||||
#[allow(deprecated)]
|
||||
Flags {
|
||||
default_protocol: "tcp".to_string(),
|
||||
dev_name: "".to_string(),
|
||||
enable_encryption: true,
|
||||
enable_ipv6: true,
|
||||
mtu: 1380,
|
||||
latency_first: false,
|
||||
enable_exit_node: false,
|
||||
proxy_forward_by_system: false,
|
||||
no_tun: false,
|
||||
use_smoltcp: false,
|
||||
relay_network_whitelist: "*".to_string(),
|
||||
disable_p2p: false,
|
||||
p2p_only: false,
|
||||
lazy_p2p: false,
|
||||
relay_all_peer_rpc: false,
|
||||
disable_tcp_hole_punching: false,
|
||||
disable_udp_hole_punching: false,
|
||||
multi_thread: true,
|
||||
data_compress_algo: CompressionAlgoPb::None.into(),
|
||||
bind_device: true,
|
||||
enable_kcp_proxy: false,
|
||||
disable_kcp_input: false,
|
||||
disable_relay_kcp: false,
|
||||
enable_relay_foreign_network_kcp: false,
|
||||
accept_dns: false,
|
||||
private_mode: false,
|
||||
enable_quic_proxy: false,
|
||||
disable_quic_input: false,
|
||||
disable_relay_quic: false,
|
||||
enable_relay_foreign_network_quic: false,
|
||||
foreign_relay_bps_limit: u64::MAX,
|
||||
multi_thread_count: 2,
|
||||
encryption_algorithm: EncryptionAlgorithm::default().to_string(),
|
||||
disable_sym_hole_punching: false,
|
||||
tld_dns_zone: DEFAULT_ET_DNS_ZONE.to_string(),
|
||||
|
||||
quic_listen_port: u32::MAX,
|
||||
need_p2p: false,
|
||||
instance_recv_bps_limit: u64::MAX,
|
||||
disable_upnp: false,
|
||||
disable_relay_data: false,
|
||||
prefer_peer_relay: false,
|
||||
enable_udp_broadcast_relay: false,
|
||||
socket_mark: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "config-write")]
|
||||
macro_rules! define_flags_diff {
|
||||
(
|
||||
fields: [$($field:ident),* $(,)?],
|
||||
u64s: [$($u64_field:ident),* $(,)?],
|
||||
enums: [$($enum_field:ident),* $(,)?]
|
||||
) => {
|
||||
#[allow(deprecated)]
|
||||
fn flags_diff_from_default(flags: &Flags) -> serde_json::Map<String, serde_json::Value> {
|
||||
let defaults = gen_default_flags();
|
||||
let mut changed = serde_json::Map::new();
|
||||
$(
|
||||
if flags.$field != defaults.$field {
|
||||
changed.insert(
|
||||
stringify!($field).to_owned(),
|
||||
serde_json::to_value(&flags.$field)
|
||||
.expect("FlagsInConfig field should serialize to JSON"),
|
||||
);
|
||||
}
|
||||
)*
|
||||
$(
|
||||
if flags.$u64_field != defaults.$u64_field {
|
||||
changed.insert(
|
||||
stringify!($u64_field).to_owned(),
|
||||
serde_json::json!(flags.$u64_field.to_string()),
|
||||
);
|
||||
}
|
||||
)*
|
||||
$(
|
||||
if flags.$enum_field != defaults.$enum_field {
|
||||
let value = CompressionAlgoPb::try_from(flags.$enum_field)
|
||||
.map(|value| serde_json::to_value(value).expect("enum should serialize"))
|
||||
.unwrap_or_else(|_| serde_json::json!(flags.$enum_field));
|
||||
changed.insert(stringify!($enum_field).to_owned(), value);
|
||||
}
|
||||
)*
|
||||
changed
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "config-write"))]
|
||||
const FLAGS_DIFF_FIELDS: &[&str] = &[
|
||||
$(stringify!($field),)*
|
||||
$(stringify!($u64_field),)*
|
||||
$(stringify!($enum_field),)*
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "config-write")]
|
||||
define_flags_diff! {
|
||||
fields: [
|
||||
default_protocol,
|
||||
dev_name,
|
||||
enable_encryption,
|
||||
enable_ipv6,
|
||||
mtu,
|
||||
latency_first,
|
||||
enable_exit_node,
|
||||
no_tun,
|
||||
use_smoltcp,
|
||||
relay_network_whitelist,
|
||||
disable_p2p,
|
||||
relay_all_peer_rpc,
|
||||
disable_udp_hole_punching,
|
||||
multi_thread,
|
||||
bind_device,
|
||||
enable_kcp_proxy,
|
||||
disable_kcp_input,
|
||||
disable_relay_kcp,
|
||||
proxy_forward_by_system,
|
||||
accept_dns,
|
||||
private_mode,
|
||||
enable_quic_proxy,
|
||||
disable_quic_input,
|
||||
disable_relay_quic,
|
||||
quic_listen_port,
|
||||
multi_thread_count,
|
||||
enable_relay_foreign_network_kcp,
|
||||
enable_relay_foreign_network_quic,
|
||||
encryption_algorithm,
|
||||
disable_sym_hole_punching,
|
||||
tld_dns_zone,
|
||||
p2p_only,
|
||||
disable_tcp_hole_punching,
|
||||
lazy_p2p,
|
||||
need_p2p,
|
||||
disable_upnp,
|
||||
disable_relay_data,
|
||||
prefer_peer_relay,
|
||||
enable_udp_broadcast_relay,
|
||||
socket_mark,
|
||||
],
|
||||
u64s: [foreign_relay_bps_limit, instance_recv_bps_limit],
|
||||
enums: [data_compress_algo]
|
||||
}
|
||||
|
||||
#[auto_impl::auto_impl(Box, &)]
|
||||
pub trait ConfigLoader: Send + Sync {
|
||||
fn get_id(&self) -> uuid::Uuid;
|
||||
@@ -231,7 +83,12 @@ pub trait ConfigLoader: Send + Sync {
|
||||
fn set_vpn_portal_config(&self, config: VpnPortalConfig);
|
||||
|
||||
fn get_flags(&self) -> Flags;
|
||||
/// Replace all flags with explicitly supplied values.
|
||||
fn set_flags(&self, flags: Flags);
|
||||
/// The user-supplied values, before application defaults are resolved.
|
||||
fn get_flags_patch(&self) -> FlagsPatch;
|
||||
/// Apply only supplied fields, preserving absence and explicit defaults.
|
||||
fn patch_flags(&self, flags: FlagsPatch);
|
||||
|
||||
fn get_exit_nodes(&self) -> Vec<IpAddr>;
|
||||
fn set_exit_nodes(&self, nodes: Vec<IpAddr>);
|
||||
@@ -486,19 +343,30 @@ fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[optionize::optionized]
|
||||
#[cfg_attr(any(feature = "web-client", feature = "browser-config"), optionize(object = easytier_proto::api::manage::ManagedCredentialConfig))]
|
||||
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ManagedCredentialConfig {
|
||||
#[optionize(flatten)]
|
||||
pub credential_id: String,
|
||||
#[optionize(flatten)]
|
||||
pub credential_secret: String,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[optionize(flatten)]
|
||||
pub groups: Vec<String>,
|
||||
#[serde(default)]
|
||||
#[optionize(flatten)]
|
||||
pub allow_relay: bool,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
#[optionize(flatten)]
|
||||
pub allowed_proxy_cidrs: Vec<String>,
|
||||
#[optionize(flatten)]
|
||||
pub expiry_unix: i64,
|
||||
#[serde(default = "default_true")]
|
||||
// The mapped field is `Option<bool>` (the protocol spells it `optional bool`),
|
||||
// so this document default does not describe it.
|
||||
#[optionize(attrs(.., -serde), default = |_| default_true())]
|
||||
pub reusable: bool,
|
||||
}
|
||||
|
||||
@@ -548,10 +416,8 @@ struct Config {
|
||||
|
||||
secure_mode: Option<SecureModeConfig>,
|
||||
|
||||
flags: Option<serde_json::Map<String, serde_json::Value>>,
|
||||
|
||||
#[serde(skip)]
|
||||
flags_struct: Option<Flags>,
|
||||
#[serde(default)]
|
||||
flags: FlagsPatch,
|
||||
|
||||
acl: Option<Acl>,
|
||||
|
||||
@@ -631,7 +497,6 @@ impl TomlConfig {
|
||||
fn config_for_dump(&self) -> Config {
|
||||
let mut config = self.config.lock().unwrap().clone();
|
||||
Self::normalize_config_source(&mut config);
|
||||
config.flags = Some(flags_diff_from_default(&self.get_flags()));
|
||||
config
|
||||
}
|
||||
|
||||
@@ -705,10 +570,6 @@ impl TomlConfig {
|
||||
}
|
||||
|
||||
fn new_from_config(mut config: Config) -> Result<Self, anyhow::Error> {
|
||||
config.flags_struct = Some(
|
||||
Self::gen_flags(config.flags.clone().unwrap_or_default())
|
||||
.context("failed to parse flags")?,
|
||||
);
|
||||
config.secure_mode = config
|
||||
.secure_mode
|
||||
.take()
|
||||
@@ -745,17 +606,6 @@ impl TomlConfig {
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn gen_flags(
|
||||
flags_hashmap: serde_json::Map<String, serde_json::Value>,
|
||||
) -> serde_json::Result<Flags> {
|
||||
let mut merged_hashmap = match serde_json::to_value(gen_default_flags()) {
|
||||
Ok(serde_json::Value::Object(map)) => map,
|
||||
_ => serde_json::Map::new(),
|
||||
};
|
||||
merged_hashmap.extend(flags_hashmap);
|
||||
serde_json::from_value(serde_json::Value::Object(merged_hashmap))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "web-client")]
|
||||
@@ -1019,16 +869,27 @@ impl ConfigLoader for TomlConfig {
|
||||
}
|
||||
|
||||
fn get_flags(&self) -> Flags {
|
||||
self.config
|
||||
.lock()
|
||||
.unwrap()
|
||||
.flags_struct
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
Flags::resolve(self.get_flags_patch())
|
||||
}
|
||||
|
||||
fn set_flags(&self, flags: Flags) {
|
||||
self.config.lock().unwrap().flags_struct = Some(flags);
|
||||
self.config.lock().unwrap().flags = flags.downgrade();
|
||||
}
|
||||
|
||||
fn get_flags_patch(&self) -> FlagsPatch {
|
||||
self.config.lock().unwrap().flags.clone()
|
||||
}
|
||||
|
||||
fn patch_flags(&self, flags: FlagsPatch) {
|
||||
// Protobuf merge: an update overwrites exactly the fields it carries,
|
||||
// and presence is the encoding, so a flag explicitly set to its default
|
||||
// value is applied like any other. Optionize's merge cannot express that
|
||||
// here, because the one flattened nullable field would have to assign
|
||||
// unconditionally.
|
||||
let mut config = self.config.lock().unwrap();
|
||||
let update = prost::Message::encode_to_vec(&flags);
|
||||
prost::Message::merge(&mut config.flags, update.as_slice())
|
||||
.expect("decoding the bytes just encoded cannot fail");
|
||||
}
|
||||
|
||||
fn get_exit_nodes(&self) -> Vec<IpAddr> {
|
||||
@@ -1406,37 +1267,9 @@ source = "web"
|
||||
#[cfg(test)]
|
||||
mod compatibility_tests {
|
||||
use super::*;
|
||||
use crate::proto::common::CompressionAlgoPb;
|
||||
use base64::{Engine as _, prelude::BASE64_STANDARD};
|
||||
|
||||
#[cfg(feature = "config-write")]
|
||||
#[test]
|
||||
fn flags_diff_covers_every_protobuf_field() {
|
||||
use prost::Message as _;
|
||||
|
||||
let descriptor_set =
|
||||
prost_types::FileDescriptorSet::decode(crate::proto::DESCRIPTOR_POOL_BYTES).unwrap();
|
||||
let proto_fields = descriptor_set
|
||||
.file
|
||||
.iter()
|
||||
.find(|file| file.package.as_deref() == Some("common"))
|
||||
.and_then(|file| {
|
||||
file.message_type
|
||||
.iter()
|
||||
.find(|message| message.name.as_deref() == Some("FlagsInConfig"))
|
||||
})
|
||||
.unwrap()
|
||||
.field
|
||||
.iter()
|
||||
.map(|field| field.name.as_deref().unwrap())
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
let diff_fields = FLAGS_DIFF_FIELDS
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
|
||||
assert_eq!(diff_fields, proto_fields);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn socket_mark_config_file_roundtrip_none_some_and_zero() {
|
||||
// Omitting the flag leaves socket_mark unset (None) -> SO_MARK untouched.
|
||||
@@ -1486,13 +1319,91 @@ socket_mark = 66
|
||||
..cfg.get_flags()
|
||||
});
|
||||
assert_eq!(cfg.get_flags().socket_mark, None);
|
||||
#[cfg(feature = "config-write")]
|
||||
assert_eq!(
|
||||
TomlConfigLoader::new_from_str(&cfg.dump())
|
||||
.unwrap()
|
||||
.get_flags()
|
||||
.socket_mark,
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "config-write")]
|
||||
#[test]
|
||||
fn flags_presence_survives_parse_patch_and_dump() {
|
||||
for (input, expected) in [
|
||||
("", None),
|
||||
("enable_encryption = true", Some(true)),
|
||||
("enable_encryption = false", Some(false)),
|
||||
] {
|
||||
let config = TomlConfig::new_from_str(&format!("[flags]\n{input}")).unwrap();
|
||||
assert_eq!(config.get_flags_patch().enable_encryption, expected);
|
||||
config.patch_flags(FlagsPatch {
|
||||
latency_first: Some(false),
|
||||
..Default::default()
|
||||
});
|
||||
let restored = TomlConfig::new_from_str(&config.dump()).unwrap();
|
||||
assert_eq!(restored.get_flags_patch().enable_encryption, expected);
|
||||
assert_eq!(restored.get_flags_patch().latency_first, Some(false));
|
||||
assert_eq!(restored.get_flags_patch().mtu, None);
|
||||
assert_eq!(
|
||||
restored.get_flags().enable_encryption,
|
||||
expected.unwrap_or(true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flags_accept_protobuf_aliases_and_numbers_without_default_collisions() {
|
||||
let config = TomlConfig::new_from_str(
|
||||
r#"[flags]
|
||||
enableEncryption = false
|
||||
mtu = "1420"
|
||||
foreignRelayBpsLimit = "18446744073709551615"
|
||||
dataCompressAlgo = "Zstd"
|
||||
socketMark = "0"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let flags = config.get_flags();
|
||||
assert!(!flags.enable_encryption);
|
||||
assert_eq!(flags.mtu, 1420);
|
||||
assert_eq!(flags.foreign_relay_bps_limit, u64::MAX);
|
||||
assert_eq!(flags.data_compress_algo, CompressionAlgoPb::Zstd as i32);
|
||||
assert_eq!(flags.socket_mark, Some(0));
|
||||
for fields in [
|
||||
"enableEncryption = true\nenable_encryption = false",
|
||||
"mtu = -1",
|
||||
"mtu = 4294967296",
|
||||
"unknown_flag = true",
|
||||
"data_compress_algo = 99",
|
||||
] {
|
||||
assert!(
|
||||
TomlConfig::new_from_str(&format!("[flags]\n{fields}")).is_err(),
|
||||
"{fields}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "config-write")]
|
||||
#[test]
|
||||
fn dump_omits_all_default_flags() {
|
||||
let config = TomlConfigLoader::default();
|
||||
let dumped = config.dump();
|
||||
let document: toml::Table = toml::from_str(&dumped).unwrap();
|
||||
assert!(document["flags"].as_table().unwrap().is_empty());
|
||||
assert_eq!(
|
||||
TomlConfigLoader::new_from_str(&dumped).unwrap().get_flags(),
|
||||
Flags::resolve(FlagsPatch::default())
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "config-write")]
|
||||
#[test]
|
||||
fn dump_preserves_flags_that_differ_from_easytier_defaults() {
|
||||
let cfg = TomlConfigLoader::default();
|
||||
let mut flags = gen_default_flags();
|
||||
let mut flags = Flags::resolve(FlagsPatch::default());
|
||||
flags.dev_name = "et_test".to_string();
|
||||
flags.enable_quic_proxy = true;
|
||||
flags.disable_tcp_hole_punching = true;
|
||||
@@ -1996,7 +1907,7 @@ mod diagnostic_compatibility_tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flags_conversion_error_keeps_source_and_cause_chain() {
|
||||
fn flags_parse_error_keeps_source_span_and_cause_chain() {
|
||||
let error = TomlConfig::new_from_str_with_source(
|
||||
"flags-fixture.toml",
|
||||
"[flags]\nsocket_mark = \"bad\"",
|
||||
@@ -2005,12 +1916,13 @@ mod diagnostic_compatibility_tests {
|
||||
let display = error.to_string();
|
||||
|
||||
assert!(display.contains("flags-fixture.toml"));
|
||||
assert!(display.contains("failed to load config"));
|
||||
assert!(display.contains("failed to parse flags"));
|
||||
assert!(display.contains("failed to parse config TOML"));
|
||||
assert!(display.contains("socket_mark = \"bad\""));
|
||||
assert!(display.contains('^'));
|
||||
assert!(
|
||||
error
|
||||
.chain()
|
||||
.any(|cause| cause.to_string().contains("failed to parse flags"))
|
||||
.any(|cause| cause.downcast_ref::<toml::de::Error>().is_some())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,11 @@ use easytier_proto::api::config::{
|
||||
self, AclPatch, ConfigPatchAction, ExitNodePatch, InstanceConfigPatch, Patchable,
|
||||
PortForwardPatch, ProxyNetworkPatch, RoutePatch, UrlPatch, VpnPortalClientPatch,
|
||||
};
|
||||
use easytier_proto::common::FlagsPatch;
|
||||
use optionize::Optionized as _;
|
||||
|
||||
use crate::{
|
||||
config::{
|
||||
api_input::managed_credential_from_proto,
|
||||
peers::AclRuleConfig,
|
||||
runtime::CoreInstanceRuntimeConfig,
|
||||
toml::{ConfigLoader as _, TomlConfig},
|
||||
@@ -93,16 +94,11 @@ where
|
||||
if let Some(ipv6) = patch.ipv6 {
|
||||
candidate.set_ipv6(Some(ipv6.into()));
|
||||
}
|
||||
if let Some(disable_relay_data) = patch.disable_relay_data {
|
||||
let mut flags = candidate.get_flags();
|
||||
flags.disable_relay_data = disable_relay_data;
|
||||
candidate.set_flags(flags);
|
||||
}
|
||||
if let Some(prefer_peer_relay) = patch.prefer_peer_relay {
|
||||
let mut flags = candidate.get_flags();
|
||||
flags.prefer_peer_relay = prefer_peer_relay;
|
||||
candidate.set_flags(flags);
|
||||
}
|
||||
candidate.patch_flags(FlagsPatch {
|
||||
disable_relay_data: patch.disable_relay_data,
|
||||
prefer_peer_relay: patch.prefer_peer_relay,
|
||||
..Default::default()
|
||||
});
|
||||
if let Some(enabled) = patch.ipv6_public_addr_provider {
|
||||
candidate.set_ipv6_public_addr_provider(enabled);
|
||||
provider_config_changed = true;
|
||||
@@ -188,8 +184,8 @@ where
|
||||
let entries = managed
|
||||
.entries
|
||||
.iter()
|
||||
.map(managed_credential_from_proto)
|
||||
.collect::<Vec<_>>();
|
||||
.map(|credential| credential.clone().upgrade())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let replacement = credential_manager
|
||||
.validate_managed_credentials(&entries)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
@@ -13,7 +13,7 @@ use cidr::{Ipv4Cidr, Ipv4Inet, Ipv6Cidr, Ipv6Inet};
|
||||
use dashmap::DashMap;
|
||||
use easytier_proto::{
|
||||
acl::Acl,
|
||||
common::{FlagsInConfig, PeerFeatureFlag, SecureModeConfig, StunInfo, TunnelInfo},
|
||||
common::{Flags, PeerFeatureFlag, SecureModeConfig, StunInfo, TunnelInfo},
|
||||
peer_rpc::{PeerGroupInfo, TrustedCredentialPubkeyProof},
|
||||
};
|
||||
use hmac::{Hmac, Mac};
|
||||
@@ -71,7 +71,7 @@ pub struct PeerRuntimeSnapshotInput {
|
||||
pub routes: RouteConfig,
|
||||
pub network_identity: NetworkIdentity,
|
||||
pub stun_info: StunInfo,
|
||||
pub flags: FlagsInConfig,
|
||||
pub flags: Flags,
|
||||
pub secure_mode: Option<SecureModeConfig>,
|
||||
pub host_routing: HostRoutingPolicy,
|
||||
pub acl: Option<Acl>,
|
||||
@@ -89,7 +89,7 @@ struct PeerTrafficLimits {
|
||||
}
|
||||
|
||||
impl PeerTrafficLimits {
|
||||
fn from_portable(runtime: &PeerRuntimeConfig, flags: &FlagsInConfig) -> Self {
|
||||
fn from_portable(runtime: &PeerRuntimeConfig, flags: &Flags) -> Self {
|
||||
let traffic = &runtime.core.traffic;
|
||||
Self {
|
||||
instance_recv_bps: Self::normalize(
|
||||
@@ -527,7 +527,7 @@ pub(crate) struct PeerPacketPolicy {
|
||||
}
|
||||
|
||||
impl PeerPacketPolicy {
|
||||
fn from_flags(flags: &FlagsInConfig) -> Self {
|
||||
fn from_flags(flags: &Flags) -> Self {
|
||||
Self {
|
||||
disable_relay_data: flags.disable_relay_data,
|
||||
p2p_only: flags.p2p_only,
|
||||
@@ -549,8 +549,8 @@ pub(crate) trait PeerContext: Send + Sync {
|
||||
self.network_identity().network_name
|
||||
}
|
||||
|
||||
fn flags(&self) -> FlagsInConfig {
|
||||
FlagsInConfig::default()
|
||||
fn flags(&self) -> Flags {
|
||||
Flags::default()
|
||||
}
|
||||
|
||||
fn packet_policy(&self) -> PeerPacketPolicy {
|
||||
@@ -743,7 +743,7 @@ impl PeerContext for CorePeerContext {
|
||||
self.snapshot().runtime.network_identity.clone()
|
||||
}
|
||||
|
||||
fn flags(&self) -> FlagsInConfig {
|
||||
fn flags(&self) -> Flags {
|
||||
self.snapshot().flags.clone()
|
||||
}
|
||||
|
||||
@@ -1082,7 +1082,7 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
fn submitted_snapshot(hostname: &str, disable_relay_data: bool) -> PeerRuntimeSnapshot {
|
||||
let mut flags = FlagsInConfig::default();
|
||||
let mut flags = Flags::default();
|
||||
flags.disable_relay_data = disable_relay_data;
|
||||
PeerRuntimeSnapshot::new(
|
||||
PeerRuntimeConfig {
|
||||
@@ -1103,7 +1103,7 @@ pub(crate) mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
fn host_snapshot_input(flags: FlagsInConfig, acl: Option<Acl>) -> PeerRuntimeSnapshotInput {
|
||||
fn host_snapshot_input(flags: Flags, acl: Option<Acl>) -> PeerRuntimeSnapshotInput {
|
||||
PeerRuntimeSnapshotInput {
|
||||
node: NodeConfig {
|
||||
peer_id: None,
|
||||
@@ -1136,7 +1136,7 @@ pub(crate) mod tests {
|
||||
|
||||
#[test]
|
||||
fn host_input_derives_peer_policy_features_and_traffic() {
|
||||
let mut flags = FlagsInConfig::default();
|
||||
let mut flags = Flags::default();
|
||||
flags.disable_p2p = true;
|
||||
flags.need_p2p = true;
|
||||
flags.relay_all_peer_rpc = true;
|
||||
@@ -1196,7 +1196,7 @@ pub(crate) mod tests {
|
||||
}),
|
||||
}),
|
||||
};
|
||||
let mut flags = FlagsInConfig::default();
|
||||
let mut flags = Flags::default();
|
||||
flags.relay_network_whitelist = "other-network".to_owned();
|
||||
|
||||
let snapshot = PeerRuntimeSnapshot::from_host_input(host_snapshot_input(flags, Some(acl)));
|
||||
@@ -1274,7 +1274,7 @@ pub(crate) mod tests {
|
||||
let mut runtime = PeerRuntimeSnapshot::default().runtime;
|
||||
runtime.core.traffic.instance_recv_bps_limit = Some(0);
|
||||
runtime.core.traffic.foreign_relay_bps_limit = Some(2048);
|
||||
let mut flags = FlagsInConfig::default();
|
||||
let mut flags = Flags::default();
|
||||
flags.instance_recv_bps_limit = 1024;
|
||||
flags.foreign_relay_bps_limit = 4096;
|
||||
|
||||
@@ -1287,7 +1287,7 @@ pub(crate) mod tests {
|
||||
#[test]
|
||||
fn legacy_traffic_limits_ignore_unlimited_sentinels() {
|
||||
let runtime = PeerRuntimeSnapshot::default().runtime;
|
||||
let mut flags = FlagsInConfig::default();
|
||||
let mut flags = Flags::default();
|
||||
flags.instance_recv_bps_limit = 1024;
|
||||
flags.foreign_relay_bps_limit = u64::MAX;
|
||||
|
||||
@@ -1302,7 +1302,7 @@ pub(crate) mod tests {
|
||||
let mut runtime = PeerRuntimeSnapshot::default().runtime;
|
||||
runtime.core.traffic.instance_recv_bps_limit = Some(u64::MAX);
|
||||
runtime.core.traffic.foreign_relay_bps_limit = Some(u64::MAX);
|
||||
let mut flags = FlagsInConfig::default();
|
||||
let mut flags = Flags::default();
|
||||
flags.instance_recv_bps_limit = 1024;
|
||||
flags.foreign_relay_bps_limit = 2048;
|
||||
|
||||
@@ -1315,7 +1315,7 @@ pub(crate) mod tests {
|
||||
fn portable_traffic_limits_default_to_unlimited() {
|
||||
let runtime = PeerRuntimeSnapshot::default().runtime;
|
||||
|
||||
let snapshot = PeerRuntimeSnapshot::new(runtime, FlagsInConfig::default());
|
||||
let snapshot = PeerRuntimeSnapshot::new(runtime, Flags::default());
|
||||
|
||||
assert_eq!(snapshot.traffic_limits(), PeerTrafficLimits::default());
|
||||
}
|
||||
@@ -1550,7 +1550,7 @@ pub(crate) mod tests {
|
||||
local_exit_node_fallback: true,
|
||||
},
|
||||
};
|
||||
let mut flags = FlagsInConfig::default();
|
||||
let mut flags = Flags::default();
|
||||
flags.p2p_only = true;
|
||||
let acl = Acl {
|
||||
acl_v1: Some(easytier_proto::acl::AclV1 {
|
||||
@@ -1626,7 +1626,7 @@ pub(crate) mod tests {
|
||||
live.tcp_nat_type = 4;
|
||||
let context = core_owned_context(
|
||||
runtime,
|
||||
FlagsInConfig::default(),
|
||||
Flags::default(),
|
||||
Some(Arc::new(TestStunInfoSource(live.clone()))),
|
||||
);
|
||||
|
||||
@@ -1636,7 +1636,7 @@ pub(crate) mod tests {
|
||||
|
||||
fn core_owned_context(
|
||||
runtime: PeerRuntimeConfig,
|
||||
flags: FlagsInConfig,
|
||||
flags: Flags,
|
||||
stun_info_source: Option<Arc<dyn PeerStunInfoSource>>,
|
||||
) -> CorePeerContext {
|
||||
core_owned_context_with_acl(runtime, flags, stun_info_source, None)
|
||||
@@ -1644,7 +1644,7 @@ pub(crate) mod tests {
|
||||
|
||||
fn core_owned_context_with_acl(
|
||||
runtime: PeerRuntimeConfig,
|
||||
flags: FlagsInConfig,
|
||||
flags: Flags,
|
||||
stun_info_source: Option<Arc<dyn PeerStunInfoSource>>,
|
||||
acl: Option<&Acl>,
|
||||
) -> CorePeerContext {
|
||||
@@ -1679,7 +1679,7 @@ pub(crate) mod tests {
|
||||
secure_mode: None,
|
||||
host_routing: HostRoutingPolicy::default(),
|
||||
},
|
||||
FlagsInConfig::default(),
|
||||
Flags::default(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::{
|
||||
};
|
||||
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use easytier_proto::common::FlagsInConfig;
|
||||
use easytier_proto::common::Flags;
|
||||
use guarden::defer;
|
||||
use tokio::sync::{
|
||||
Mutex, RwLock, RwLockReadGuard,
|
||||
@@ -84,7 +84,7 @@ fn build_foreign_peer_context(
|
||||
network: &NetworkIdentity,
|
||||
parent_context: &Arc<CorePeerContext>,
|
||||
relay_data: bool,
|
||||
mut flags: FlagsInConfig,
|
||||
mut flags: Flags,
|
||||
) -> Arc<CorePeerContext> {
|
||||
let parent_context_dyn: ArcPeerContext = parent_context.clone();
|
||||
let parent_flags = parent_context_dyn.flags();
|
||||
@@ -372,7 +372,7 @@ impl ForeignNetworkEntry {
|
||||
my_peer_id: PeerId,
|
||||
rpc_registrar: Arc<dyn ForeignNetworkRpcRegistrar>,
|
||||
parent_context: Arc<CorePeerContext>,
|
||||
foreign_context_default_flags: FlagsInConfig,
|
||||
foreign_context_default_flags: Flags,
|
||||
relay_data: bool,
|
||||
peer_session_store: Arc<PeerSessionStore>,
|
||||
pm_packet_sender: PacketRecvChan,
|
||||
@@ -822,7 +822,7 @@ impl ForeignNetworkManagerData {
|
||||
relay_data: bool,
|
||||
rpc_registrar: Arc<dyn ForeignNetworkRpcRegistrar>,
|
||||
parent_context: Arc<CorePeerContext>,
|
||||
foreign_context_default_flags: FlagsInConfig,
|
||||
foreign_context_default_flags: Flags,
|
||||
peer_session_store: Arc<PeerSessionStore>,
|
||||
pm_packet_sender: &PacketRecvChan,
|
||||
) -> Option<(Arc<ForeignNetworkEntry>, bool)> {
|
||||
@@ -874,7 +874,7 @@ enum ForeignNetworkManagerState {
|
||||
pub(crate) struct ForeignNetworkManager {
|
||||
rpc_registrar: Arc<dyn ForeignNetworkRpcRegistrar>,
|
||||
parent_context: Arc<CorePeerContext>,
|
||||
foreign_context_default_flags: FlagsInConfig,
|
||||
foreign_context_default_flags: Flags,
|
||||
peer_session_store: Arc<PeerSessionStore>,
|
||||
packet_sender_to_mgr: PacketRecvChan,
|
||||
|
||||
@@ -912,7 +912,7 @@ impl ForeignNetworkManager {
|
||||
pub fn new(
|
||||
rpc_registrar: Arc<dyn ForeignNetworkRpcRegistrar>,
|
||||
parent_context: Arc<CorePeerContext>,
|
||||
foreign_context_default_flags: FlagsInConfig,
|
||||
foreign_context_default_flags: Flags,
|
||||
peer_session_store: Arc<PeerSessionStore>,
|
||||
packet_sender_to_mgr: PacketRecvChan,
|
||||
global_peer_map: Weak<PeerMap>,
|
||||
@@ -1593,7 +1593,7 @@ mod tests {
|
||||
};
|
||||
|
||||
use dashmap::DashMap;
|
||||
use easytier_proto::common::{FlagsInConfig, PeerFeatureFlag};
|
||||
use easytier_proto::common::{Flags, PeerFeatureFlag};
|
||||
|
||||
use super::{
|
||||
ForeignNetworkEntry, ForeignNetworkManager, ForeignNetworkManagerData,
|
||||
@@ -1638,7 +1638,7 @@ mod tests {
|
||||
1,
|
||||
Arc::new(()),
|
||||
parent,
|
||||
FlagsInConfig::default(),
|
||||
Flags::default(),
|
||||
true,
|
||||
Arc::new(PeerSessionStore::new()),
|
||||
packet_sender,
|
||||
@@ -1825,7 +1825,7 @@ mod tests {
|
||||
|
||||
struct FeatureContext {
|
||||
avoid_relay_data: AtomicBool,
|
||||
flags: FlagsInConfig,
|
||||
flags: Flags,
|
||||
hostname: String,
|
||||
}
|
||||
|
||||
@@ -1833,7 +1833,7 @@ mod tests {
|
||||
fn new(avoid_relay_data: bool) -> Self {
|
||||
Self {
|
||||
avoid_relay_data: AtomicBool::new(avoid_relay_data),
|
||||
flags: FlagsInConfig::default(),
|
||||
flags: Flags::default(),
|
||||
hostname: String::new(),
|
||||
}
|
||||
}
|
||||
@@ -1851,7 +1851,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn flags(&self) -> FlagsInConfig {
|
||||
fn flags(&self) -> Flags {
|
||||
self.flags.clone()
|
||||
}
|
||||
|
||||
@@ -1873,6 +1873,8 @@ mod tests {
|
||||
assert!(check_network_in_relay_whitelist("*", "any-network").is_ok());
|
||||
assert!(check_network_in_relay_whitelist("", "net1").is_err());
|
||||
assert!(check_network_in_relay_whitelist("net1 net2*", "net3").is_err());
|
||||
assert!(check_network_in_relay_whitelist("net1,net2*", "net1").is_ok());
|
||||
assert!(check_network_in_relay_whitelist("net1, net2*", "net2-west").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1943,7 +1945,7 @@ mod tests {
|
||||
network_secret: Some("secret".to_owned()),
|
||||
network_secret_digest: None,
|
||||
};
|
||||
let mut defaults = FlagsInConfig::default();
|
||||
let mut defaults = Flags::default();
|
||||
defaults.mtu = 1400;
|
||||
defaults.relay_network_whitelist = "baseline".to_owned();
|
||||
let foreign = build_foreign_peer_context(&network, &parent, false, defaults);
|
||||
|
||||
@@ -37,7 +37,7 @@ use crate::{
|
||||
CompressorAlgo, PacketType, ZCPacket,
|
||||
compressor::{Compressor as _, DefaultCompressor},
|
||||
},
|
||||
proto::common::{FlagsInConfig, PeerFeatureFlag, StunInfo, Url as ProtoUrl},
|
||||
proto::common::{Flags, PeerFeatureFlag, StunInfo, Url as ProtoUrl},
|
||||
proto::core_peer::peer::{ListPublicIpv6InfoResponse, PeerConnInfo, Route as CoreRoute},
|
||||
tunnel::{
|
||||
Tunnel,
|
||||
@@ -213,14 +213,14 @@ pub struct PortablePeerManagerConfig {
|
||||
///
|
||||
/// This is explicit because those contexts participate in the same
|
||||
/// handshake as the parent but do not inherit all parent policy flags.
|
||||
pub foreign_context_default_flags: FlagsInConfig,
|
||||
pub foreign_context_default_flags: Flags,
|
||||
}
|
||||
|
||||
impl PortablePeerManagerConfig {
|
||||
pub fn new(mut runtime: PeerRuntimeConfig) -> Self {
|
||||
let policy = &runtime.core.peer_policy;
|
||||
let traffic = &runtime.core.traffic;
|
||||
let flags = FlagsInConfig {
|
||||
let flags = Flags {
|
||||
enable_encryption: policy.encryption_required,
|
||||
encryption_algorithm: crate::config::EncryptionAlgorithm::default().to_string(),
|
||||
disable_p2p: !policy.p2p_enabled,
|
||||
@@ -956,7 +956,7 @@ impl PeerManagerCore {
|
||||
is_secure_mode_enabled: bool,
|
||||
data_compress_algo: CompressorAlgo,
|
||||
exit_nodes: Vec<IpAddr>,
|
||||
foreign_context_default_flags: FlagsInConfig,
|
||||
foreign_context_default_flags: Flags,
|
||||
foreign_rpc_registrar: Arc<dyn ForeignNetworkRpcRegistrar>,
|
||||
) -> Self {
|
||||
let stats_manager = core_context.stats_manager();
|
||||
|
||||
@@ -774,13 +774,13 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
peers::context::{ArcPeerContext, NetworkIdentity, PeerContext},
|
||||
proto::common::{FlagsInConfig, SecureModeConfig},
|
||||
proto::common::{Flags, SecureModeConfig},
|
||||
};
|
||||
|
||||
struct RelayTestContext {
|
||||
network_identity: NetworkIdentity,
|
||||
secure_mode: SecureModeConfig,
|
||||
flags: FlagsInConfig,
|
||||
flags: Flags,
|
||||
}
|
||||
|
||||
impl PeerContext for RelayTestContext {
|
||||
@@ -792,7 +792,7 @@ mod tests {
|
||||
Some(self.secure_mode.clone())
|
||||
}
|
||||
|
||||
fn flags(&self) -> FlagsInConfig {
|
||||
fn flags(&self) -> Flags {
|
||||
self.flags.clone()
|
||||
}
|
||||
}
|
||||
@@ -866,7 +866,7 @@ mod tests {
|
||||
local_private_key: Some(BASE64_STANDARD.encode(private.as_bytes())),
|
||||
local_public_key: Some(BASE64_STANDARD.encode(public.as_bytes())),
|
||||
},
|
||||
flags: FlagsInConfig {
|
||||
flags: Flags {
|
||||
encryption_algorithm: "aes-gcm".to_owned(),
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -4886,8 +4886,8 @@ mod tests {
|
||||
CoreNetworkIdentity::default()
|
||||
}
|
||||
|
||||
fn flags(&self) -> crate::proto::common::FlagsInConfig {
|
||||
crate::proto::common::FlagsInConfig {
|
||||
fn flags(&self) -> crate::proto::common::Flags {
|
||||
crate::proto::common::Flags {
|
||||
prefer_peer_relay: self.enabled.load(Ordering::Relaxed),
|
||||
..Default::default()
|
||||
}
|
||||
@@ -5012,7 +5012,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn test_peer_relay_service_impl(my_peer_id: PeerId) -> PeerRouteServiceImpl {
|
||||
let flags = crate::proto::common::FlagsInConfig {
|
||||
let flags = crate::proto::common::Flags {
|
||||
prefer_peer_relay: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use cidr::{Ipv4Inet, Ipv6Inet};
|
||||
use easytier_proto::common::{FlagsInConfig, SecureModeConfig};
|
||||
use easytier_proto::common::{Flags, SecureModeConfig};
|
||||
use hmac::Hmac;
|
||||
use sha2::Sha256;
|
||||
|
||||
@@ -58,7 +58,7 @@ fn ipv6_inet_to_config(value: Ipv6Inet) -> IpPrefix {
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct NoopPeerContext {
|
||||
network_identity: NetworkIdentity,
|
||||
flags: FlagsInConfig,
|
||||
flags: Flags,
|
||||
secure_mode: Option<SecureModeConfig>,
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ impl NoopPeerContext {
|
||||
pub(crate) fn new(network_identity: NetworkIdentity) -> Self {
|
||||
Self {
|
||||
network_identity,
|
||||
flags: FlagsInConfig::default(),
|
||||
flags: Flags::default(),
|
||||
secure_mode: None,
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ impl NoopPeerContext {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_flags(mut self, flags: FlagsInConfig) -> Self {
|
||||
pub(crate) fn with_flags(mut self, flags: Flags) -> Self {
|
||||
self.flags = flags;
|
||||
self
|
||||
}
|
||||
@@ -94,7 +94,7 @@ impl PeerContext for NoopPeerContext {
|
||||
self.network_identity.clone()
|
||||
}
|
||||
|
||||
fn flags(&self) -> FlagsInConfig {
|
||||
fn flags(&self) -> Flags {
|
||||
self.flags.clone()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ pub(crate) fn check_network_in_relay_whitelist(
|
||||
network_name: &str,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if relay_network_whitelist
|
||||
.split(' ')
|
||||
.split(|c: char| c.is_whitespace() || c == ',')
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(wildmatch::WildMatch::new)
|
||||
.any(|whitelist| whitelist.matches(network_name))
|
||||
{
|
||||
|
||||
@@ -277,7 +277,7 @@ fn maybe_offload_aead(
|
||||
pub(crate) fn validate_algorithm(algorithm: &str) -> Result<(), Error> {
|
||||
let parsed = algorithm
|
||||
.parse::<EncryptionAlgorithm>()
|
||||
.map_err(|()| Error::InvalidAlgorithm(algorithm.to_owned()))?;
|
||||
.map_err(|_| Error::InvalidAlgorithm(algorithm.to_owned()))?;
|
||||
if algorithm_is_available(parsed) {
|
||||
Ok(())
|
||||
} else {
|
||||
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
)
|
||||
|
||||
var managementProtoFiles = []string{
|
||||
"annotations.proto",
|
||||
"common.proto",
|
||||
"error.proto",
|
||||
"acl.proto",
|
||||
|
||||
Binary file not shown.
@@ -3,6 +3,6 @@
|
||||
package artifact
|
||||
|
||||
const (
|
||||
Commit = "599e4eacaa9c9a6f84b8d6439418af9d860f9aa3"
|
||||
SHA256 = "f5cc76a30d4c6128f7ce77d494dd4e8130aa7e3ce9d5b9ede7604745064bed8d"
|
||||
Commit = "9a2290f312dbdec6f40e8b13d8a6de5f66e4c570"
|
||||
SHA256 = "044f9ed79079f449741744d9dd0559d6844e1fff2f1a1994710811904f119986"
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.35.1
|
||||
// protoc v7.36.1
|
||||
// source: acl.proto
|
||||
|
||||
package acl
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.35.1
|
||||
// protoc v7.36.1
|
||||
// source: api_config.proto
|
||||
|
||||
package config
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.35.1
|
||||
// protoc v7.36.1
|
||||
// source: api_instance.proto
|
||||
|
||||
package instance
|
||||
@@ -4399,17 +4399,17 @@ var file_api_instance_proto_goTypes = []any{
|
||||
nil, // 66: api.instance.ListForeignNetworkResponse.ForeignNetworksEntry
|
||||
(*ListGlobalForeignNetworkResponse_OneForeignNetwork)(nil), // 67: api.instance.ListGlobalForeignNetworkResponse.OneForeignNetwork
|
||||
(*ListGlobalForeignNetworkResponse_ForeignNetworks)(nil), // 68: api.instance.ListGlobalForeignNetworkResponse.ForeignNetworks
|
||||
nil, // 69: api.instance.ListGlobalForeignNetworkResponse.ForeignNetworksEntry
|
||||
nil, // 70: api.instance.MetricSnapshot.LabelsEntry
|
||||
(*common.UUID)(nil), // 71: common.UUID
|
||||
(*common.TunnelInfo)(nil), // 72: common.TunnelInfo
|
||||
(peer_rpc.SecureAuthLevel)(0), // 73: peer_rpc.SecureAuthLevel
|
||||
(peer_rpc.PeerIdentityType)(0), // 74: peer_rpc.PeerIdentityType
|
||||
(*common.Ipv4Inet)(nil), // 75: common.Ipv4Inet
|
||||
(*common.StunInfo)(nil), // 76: common.StunInfo
|
||||
(*common.PeerFeatureFlag)(nil), // 77: common.PeerFeatureFlag
|
||||
(*common.Ipv6Inet)(nil), // 78: common.Ipv6Inet
|
||||
(*peer_rpc.GetIpListResponse)(nil), // 79: peer_rpc.GetIpListResponse
|
||||
nil, // 69: api.instance.ListGlobalForeignNetworkResponse.ForeignNetworksEntry
|
||||
nil, // 70: api.instance.MetricSnapshot.LabelsEntry
|
||||
(*common.UUID)(nil), // 71: common.UUID
|
||||
(*common.TunnelInfo)(nil), // 72: common.TunnelInfo
|
||||
(peer_rpc.SecureAuthLevel)(0), // 73: peer_rpc.SecureAuthLevel
|
||||
(peer_rpc.PeerIdentityType)(0), // 74: peer_rpc.PeerIdentityType
|
||||
(*common.Ipv4Inet)(nil), // 75: common.Ipv4Inet
|
||||
(*common.StunInfo)(nil), // 76: common.StunInfo
|
||||
(*common.PeerFeatureFlag)(nil), // 77: common.PeerFeatureFlag
|
||||
(*common.Ipv6Inet)(nil), // 78: common.Ipv6Inet
|
||||
(*peer_rpc.GetIpListResponse)(nil), // 79: peer_rpc.GetIpListResponse
|
||||
(*peer_rpc.RouteForeignNetworkSummary)(nil), // 80: peer_rpc.RouteForeignNetworkSummary
|
||||
(*common.Url)(nil), // 81: common.Url
|
||||
(*common.SocketAddr)(nil), // 82: common.SocketAddr
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.35.1
|
||||
// protoc v7.36.1
|
||||
// source: api_manage.proto
|
||||
|
||||
package manage
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.35.1
|
||||
// protoc v7.36.1
|
||||
// source: common.proto
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
_ "github.com/easytier/easytier/easytier-go/proto/easytier"
|
||||
error1 "github.com/easytier/easytier/easytier-go/proto/error"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
@@ -188,7 +189,7 @@ func (SocketType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_common_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
type FlagsInConfig struct {
|
||||
type Flags struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
DefaultProtocol string `protobuf:"bytes,1,opt,name=default_protocol,json=defaultProtocol,proto3" json:"default_protocol,omitempty"`
|
||||
DevName string `protobuf:"bytes,2,opt,name=dev_name,json=devName,proto3" json:"dev_name,omitempty"`
|
||||
@@ -263,20 +264,20 @@ type FlagsInConfig struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) Reset() {
|
||||
*x = FlagsInConfig{}
|
||||
func (x *Flags) Reset() {
|
||||
*x = Flags{}
|
||||
mi := &file_common_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) String() string {
|
||||
func (x *Flags) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*FlagsInConfig) ProtoMessage() {}
|
||||
func (*Flags) ProtoMessage() {}
|
||||
|
||||
func (x *FlagsInConfig) ProtoReflect() protoreflect.Message {
|
||||
func (x *Flags) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_common_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -288,180 +289,180 @@ func (x *FlagsInConfig) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use FlagsInConfig.ProtoReflect.Descriptor instead.
|
||||
func (*FlagsInConfig) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use Flags.ProtoReflect.Descriptor instead.
|
||||
func (*Flags) Descriptor() ([]byte, []int) {
|
||||
return file_common_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDefaultProtocol() string {
|
||||
func (x *Flags) GetDefaultProtocol() string {
|
||||
if x != nil {
|
||||
return x.DefaultProtocol
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDevName() string {
|
||||
func (x *Flags) GetDevName() string {
|
||||
if x != nil {
|
||||
return x.DevName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetEnableEncryption() bool {
|
||||
func (x *Flags) GetEnableEncryption() bool {
|
||||
if x != nil {
|
||||
return x.EnableEncryption
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetEnableIpv6() bool {
|
||||
func (x *Flags) GetEnableIpv6() bool {
|
||||
if x != nil {
|
||||
return x.EnableIpv6
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetMtu() uint32 {
|
||||
func (x *Flags) GetMtu() uint32 {
|
||||
if x != nil {
|
||||
return x.Mtu
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetLatencyFirst() bool {
|
||||
func (x *Flags) GetLatencyFirst() bool {
|
||||
if x != nil {
|
||||
return x.LatencyFirst
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetEnableExitNode() bool {
|
||||
func (x *Flags) GetEnableExitNode() bool {
|
||||
if x != nil {
|
||||
return x.EnableExitNode
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetNoTun() bool {
|
||||
func (x *Flags) GetNoTun() bool {
|
||||
if x != nil {
|
||||
return x.NoTun
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetUseSmoltcp() bool {
|
||||
func (x *Flags) GetUseSmoltcp() bool {
|
||||
if x != nil {
|
||||
return x.UseSmoltcp
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetRelayNetworkWhitelist() string {
|
||||
func (x *Flags) GetRelayNetworkWhitelist() string {
|
||||
if x != nil {
|
||||
return x.RelayNetworkWhitelist
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDisableP2P() bool {
|
||||
func (x *Flags) GetDisableP2P() bool {
|
||||
if x != nil {
|
||||
return x.DisableP2P
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetRelayAllPeerRpc() bool {
|
||||
func (x *Flags) GetRelayAllPeerRpc() bool {
|
||||
if x != nil {
|
||||
return x.RelayAllPeerRpc
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDisableUdpHolePunching() bool {
|
||||
func (x *Flags) GetDisableUdpHolePunching() bool {
|
||||
if x != nil {
|
||||
return x.DisableUdpHolePunching
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetMultiThread() bool {
|
||||
func (x *Flags) GetMultiThread() bool {
|
||||
if x != nil {
|
||||
return x.MultiThread
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDataCompressAlgo() CompressionAlgoPb {
|
||||
func (x *Flags) GetDataCompressAlgo() CompressionAlgoPb {
|
||||
if x != nil {
|
||||
return x.DataCompressAlgo
|
||||
}
|
||||
return CompressionAlgoPb_Invalid
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetBindDevice() bool {
|
||||
func (x *Flags) GetBindDevice() bool {
|
||||
if x != nil {
|
||||
return x.BindDevice
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetEnableKcpProxy() bool {
|
||||
func (x *Flags) GetEnableKcpProxy() bool {
|
||||
if x != nil {
|
||||
return x.EnableKcpProxy
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDisableKcpInput() bool {
|
||||
func (x *Flags) GetDisableKcpInput() bool {
|
||||
if x != nil {
|
||||
return x.DisableKcpInput
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDisableRelayKcp() bool {
|
||||
func (x *Flags) GetDisableRelayKcp() bool {
|
||||
if x != nil {
|
||||
return x.DisableRelayKcp
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetProxyForwardBySystem() bool {
|
||||
func (x *Flags) GetProxyForwardBySystem() bool {
|
||||
if x != nil {
|
||||
return x.ProxyForwardBySystem
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetAcceptDns() bool {
|
||||
func (x *Flags) GetAcceptDns() bool {
|
||||
if x != nil {
|
||||
return x.AcceptDns
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetPrivateMode() bool {
|
||||
func (x *Flags) GetPrivateMode() bool {
|
||||
if x != nil {
|
||||
return x.PrivateMode
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetEnableQuicProxy() bool {
|
||||
func (x *Flags) GetEnableQuicProxy() bool {
|
||||
if x != nil {
|
||||
return x.EnableQuicProxy
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDisableQuicInput() bool {
|
||||
func (x *Flags) GetDisableQuicInput() bool {
|
||||
if x != nil {
|
||||
return x.DisableQuicInput
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDisableRelayQuic() bool {
|
||||
func (x *Flags) GetDisableRelayQuic() bool {
|
||||
if x != nil {
|
||||
return x.DisableRelayQuic
|
||||
}
|
||||
@@ -469,126 +470,126 @@ func (x *FlagsInConfig) GetDisableRelayQuic() bool {
|
||||
}
|
||||
|
||||
// Deprecated: Marked as deprecated in common.proto.
|
||||
func (x *FlagsInConfig) GetQuicListenPort() uint32 {
|
||||
func (x *Flags) GetQuicListenPort() uint32 {
|
||||
if x != nil {
|
||||
return x.QuicListenPort
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetForeignRelayBpsLimit() uint64 {
|
||||
func (x *Flags) GetForeignRelayBpsLimit() uint64 {
|
||||
if x != nil {
|
||||
return x.ForeignRelayBpsLimit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetMultiThreadCount() uint32 {
|
||||
func (x *Flags) GetMultiThreadCount() uint32 {
|
||||
if x != nil {
|
||||
return x.MultiThreadCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetEnableRelayForeignNetworkKcp() bool {
|
||||
func (x *Flags) GetEnableRelayForeignNetworkKcp() bool {
|
||||
if x != nil {
|
||||
return x.EnableRelayForeignNetworkKcp
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetEnableRelayForeignNetworkQuic() bool {
|
||||
func (x *Flags) GetEnableRelayForeignNetworkQuic() bool {
|
||||
if x != nil {
|
||||
return x.EnableRelayForeignNetworkQuic
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetEncryptionAlgorithm() string {
|
||||
func (x *Flags) GetEncryptionAlgorithm() string {
|
||||
if x != nil {
|
||||
return x.EncryptionAlgorithm
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDisableSymHolePunching() bool {
|
||||
func (x *Flags) GetDisableSymHolePunching() bool {
|
||||
if x != nil {
|
||||
return x.DisableSymHolePunching
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetTldDnsZone() string {
|
||||
func (x *Flags) GetTldDnsZone() string {
|
||||
if x != nil {
|
||||
return x.TldDnsZone
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetP2POnly() bool {
|
||||
func (x *Flags) GetP2POnly() bool {
|
||||
if x != nil {
|
||||
return x.P2POnly
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDisableTcpHolePunching() bool {
|
||||
func (x *Flags) GetDisableTcpHolePunching() bool {
|
||||
if x != nil {
|
||||
return x.DisableTcpHolePunching
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetLazyP2P() bool {
|
||||
func (x *Flags) GetLazyP2P() bool {
|
||||
if x != nil {
|
||||
return x.LazyP2P
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetNeedP2P() bool {
|
||||
func (x *Flags) GetNeedP2P() bool {
|
||||
if x != nil {
|
||||
return x.NeedP2P
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetInstanceRecvBpsLimit() uint64 {
|
||||
func (x *Flags) GetInstanceRecvBpsLimit() uint64 {
|
||||
if x != nil {
|
||||
return x.InstanceRecvBpsLimit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDisableUpnp() bool {
|
||||
func (x *Flags) GetDisableUpnp() bool {
|
||||
if x != nil {
|
||||
return x.DisableUpnp
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetDisableRelayData() bool {
|
||||
func (x *Flags) GetDisableRelayData() bool {
|
||||
if x != nil {
|
||||
return x.DisableRelayData
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetEnableUdpBroadcastRelay() bool {
|
||||
func (x *Flags) GetEnableUdpBroadcastRelay() bool {
|
||||
if x != nil {
|
||||
return x.EnableUdpBroadcastRelay
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetSocketMark() uint32 {
|
||||
func (x *Flags) GetSocketMark() uint32 {
|
||||
if x != nil && x.SocketMark != nil {
|
||||
return *x.SocketMark
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FlagsInConfig) GetPreferPeerRelay() bool {
|
||||
func (x *Flags) GetPreferPeerRelay() bool {
|
||||
if x != nil {
|
||||
return x.PreferPeerRelay
|
||||
}
|
||||
@@ -2147,59 +2148,108 @@ var File_common_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_common_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\fcommon.proto\x12\x06common\x1a\verror.proto\"\xf5\x0e\n" +
|
||||
"\rFlagsInConfig\x12)\n" +
|
||||
"\x10default_protocol\x18\x01 \x01(\tR\x0fdefaultProtocol\x12\x19\n" +
|
||||
"\bdev_name\x18\x02 \x01(\tR\adevName\x12+\n" +
|
||||
"\x11enable_encryption\x18\x03 \x01(\bR\x10enableEncryption\x12\x1f\n" +
|
||||
"\venable_ipv6\x18\x04 \x01(\bR\n" +
|
||||
"enableIpv6\x12\x10\n" +
|
||||
"\x03mtu\x18\x05 \x01(\rR\x03mtu\x12#\n" +
|
||||
"\rlatency_first\x18\x06 \x01(\bR\flatencyFirst\x12(\n" +
|
||||
"\x10enable_exit_node\x18\a \x01(\bR\x0eenableExitNode\x12\x15\n" +
|
||||
"\x06no_tun\x18\b \x01(\bR\x05noTun\x12\x1f\n" +
|
||||
"\vuse_smoltcp\x18\t \x01(\bR\n" +
|
||||
"useSmoltcp\x126\n" +
|
||||
"\fcommon.proto\x12\x06common\x1a\x11annotations.proto\x1a\verror.proto\"\x8a\x14\n" +
|
||||
"\x05Flags\x124\n" +
|
||||
"\x10default_protocol\x18\x01 \x01(\tB\t\x8a\xb5\x18\x05\n" +
|
||||
"\x03tcpR\x0fdefaultProtocol\x12#\n" +
|
||||
"\bdev_name\x18\x02 \x01(\tB\b\x8a\xb5\x18\x04\n" +
|
||||
"\x00\x18\x01R\adevName\x12O\n" +
|
||||
"\x11enable_encryption\x18\x03 \x01(\bB\"\x8a\xb5\x18\x1e\n" +
|
||||
"\x04true\x12\x16\n" +
|
||||
"\x12disable_encryption\x10\x01R\x10enableEncryption\x12=\n" +
|
||||
"\venable_ipv6\x18\x04 \x01(\bB\x1c\x8a\xb5\x18\x18\n" +
|
||||
"\x04true\x12\x10\n" +
|
||||
"\fdisable_ipv6\x10\x01R\n" +
|
||||
"enableIpv6\x12\x1e\n" +
|
||||
"\x03mtu\x18\x05 \x01(\rB\f\x8a\xb5\x18\b\n" +
|
||||
"\x041380\x18\x01R\x03mtu\x120\n" +
|
||||
"\rlatency_first\x18\x06 \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\flatencyFirst\x125\n" +
|
||||
"\x10enable_exit_node\x18\a \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x0eenableExitNode\x12\"\n" +
|
||||
"\x06no_tun\x18\b \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x05noTun\x12,\n" +
|
||||
"\vuse_smoltcp\x18\t \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\n" +
|
||||
"useSmoltcp\x12A\n" +
|
||||
"\x17relay_network_whitelist\x18\n" +
|
||||
" \x01(\tR\x15relayNetworkWhitelist\x12\x1f\n" +
|
||||
"\vdisable_p2p\x18\v \x01(\bR\n" +
|
||||
"disableP2p\x12+\n" +
|
||||
"\x12relay_all_peer_rpc\x18\f \x01(\bR\x0frelayAllPeerRpc\x129\n" +
|
||||
"\x19disable_udp_hole_punching\x18\r \x01(\bR\x16disableUdpHolePunching\x12!\n" +
|
||||
"\fmulti_thread\x18\x0f \x01(\bR\vmultiThread\x12G\n" +
|
||||
"\x12data_compress_algo\x18\x10 \x01(\x0e2\x19.common.CompressionAlgoPbR\x10dataCompressAlgo\x12\x1f\n" +
|
||||
"\vbind_device\x18\x11 \x01(\bR\n" +
|
||||
"bindDevice\x12(\n" +
|
||||
"\x10enable_kcp_proxy\x18\x12 \x01(\bR\x0eenableKcpProxy\x12*\n" +
|
||||
"\x11disable_kcp_input\x18\x13 \x01(\bR\x0fdisableKcpInput\x12*\n" +
|
||||
"\x11disable_relay_kcp\x18\x14 \x01(\bR\x0fdisableRelayKcp\x125\n" +
|
||||
"\x17proxy_forward_by_system\x18\x15 \x01(\bR\x14proxyForwardBySystem\x12\x1d\n" +
|
||||
" \x01(\tB\t\x8a\xb5\x18\x05\n" +
|
||||
"\x01*\x18\x01R\x15relayNetworkWhitelist\x12,\n" +
|
||||
"\vdisable_p2p\x18\v \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\n" +
|
||||
"disableP2p\x128\n" +
|
||||
"\x12relay_all_peer_rpc\x18\f \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x0frelayAllPeerRpc\x12F\n" +
|
||||
"\x19disable_udp_hole_punching\x18\r \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x16disableUdpHolePunching\x12-\n" +
|
||||
"\fmulti_thread\x18\x0f \x01(\bB\n" +
|
||||
"\x8a\xb5\x18\x06\n" +
|
||||
"\x04trueR\vmultiThread\x12U\n" +
|
||||
"\x12data_compress_algo\x18\x10 \x01(\x0e2\x19.common.CompressionAlgoPbB\f\x8a\xb5\x18\b\n" +
|
||||
"\x04None\x18\x01R\x10dataCompressAlgo\x12+\n" +
|
||||
"\vbind_device\x18\x11 \x01(\bB\n" +
|
||||
"\x8a\xb5\x18\x06\n" +
|
||||
"\x04trueR\n" +
|
||||
"bindDevice\x125\n" +
|
||||
"\x10enable_kcp_proxy\x18\x12 \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x0eenableKcpProxy\x127\n" +
|
||||
"\x11disable_kcp_input\x18\x13 \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x0fdisableKcpInput\x127\n" +
|
||||
"\x11disable_relay_kcp\x18\x14 \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x0fdisableRelayKcp\x12B\n" +
|
||||
"\x17proxy_forward_by_system\x18\x15 \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x14proxyForwardBySystem\x12>\n" +
|
||||
"\n" +
|
||||
"accept_dns\x18\x16 \x01(\bR\tacceptDns\x12!\n" +
|
||||
"\fprivate_mode\x18\x17 \x01(\bR\vprivateMode\x12*\n" +
|
||||
"\x11enable_quic_proxy\x18\x18 \x01(\bR\x0fenableQuicProxy\x12,\n" +
|
||||
"\x12disable_quic_input\x18\x19 \x01(\bR\x10disableQuicInput\x12,\n" +
|
||||
"\x12disable_relay_quic\x18# \x01(\bR\x10disableRelayQuic\x12,\n" +
|
||||
"\x10quic_listen_port\x18! \x01(\rB\x02\x18\x01R\x0equicListenPort\x125\n" +
|
||||
"\x17foreign_relay_bps_limit\x18\x1a \x01(\x04R\x14foreignRelayBpsLimit\x12,\n" +
|
||||
"\x12multi_thread_count\x18\x1b \x01(\rR\x10multiThreadCount\x12F\n" +
|
||||
" enable_relay_foreign_network_kcp\x18\x1c \x01(\bR\x1cenableRelayForeignNetworkKcp\x12H\n" +
|
||||
"!enable_relay_foreign_network_quic\x18$ \x01(\bR\x1denableRelayForeignNetworkQuic\x121\n" +
|
||||
"\x14encryption_algorithm\x18\x1d \x01(\tR\x13encryptionAlgorithm\x129\n" +
|
||||
"\x19disable_sym_hole_punching\x18\x1e \x01(\bR\x16disableSymHolePunching\x12 \n" +
|
||||
"\ftld_dns_zone\x18\x1f \x01(\tR\n" +
|
||||
"tldDnsZone\x12\x19\n" +
|
||||
"\bp2p_only\x18 \x01(\bR\ap2pOnly\x129\n" +
|
||||
"\x19disable_tcp_hole_punching\x18\" \x01(\bR\x16disableTcpHolePunching\x12\x19\n" +
|
||||
"\blazy_p2p\x18% \x01(\bR\alazyP2p\x12\x19\n" +
|
||||
"\bneed_p2p\x18& \x01(\bR\aneedP2p\x125\n" +
|
||||
"\x17instance_recv_bps_limit\x18' \x01(\x04R\x14instanceRecvBpsLimit\x12!\n" +
|
||||
"\fdisable_upnp\x18( \x01(\bR\vdisableUpnp\x12,\n" +
|
||||
"\x12disable_relay_data\x18) \x01(\bR\x10disableRelayData\x12;\n" +
|
||||
"\x1aenable_udp_broadcast_relay\x18* \x01(\bR\x17enableUdpBroadcastRelay\x12$\n" +
|
||||
"\vsocket_mark\x18+ \x01(\rH\x00R\n" +
|
||||
"socketMark\x88\x01\x01\x12*\n" +
|
||||
"\x11prefer_peer_relay\x18, \x01(\bR\x0fpreferPeerRelayB\x0e\n" +
|
||||
"accept_dns\x18\x16 \x01(\bB\x1f\x8a\xb5\x18\x1b\n" +
|
||||
"\x05false\x12\x12\n" +
|
||||
"\x10enable_magic_dnsR\tacceptDns\x12E\n" +
|
||||
"\fprivate_mode\x18\x17 \x01(\bB\"\x8a\xb5\x18\x1e\n" +
|
||||
"\x05false\x12\x15\n" +
|
||||
"\x13enable_private_modeR\vprivateMode\x127\n" +
|
||||
"\x11enable_quic_proxy\x18\x18 \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x0fenableQuicProxy\x129\n" +
|
||||
"\x12disable_quic_input\x18\x19 \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x10disableQuicInput\x129\n" +
|
||||
"\x12disable_relay_quic\x18# \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x10disableRelayQuic\x12>\n" +
|
||||
"\x10quic_listen_port\x18! \x01(\rB\x14\x8a\xb5\x18\x0e\n" +
|
||||
"\n" +
|
||||
"4294967295 \x01\x18\x01R\x0equicListenPort\x12Q\n" +
|
||||
"\x17foreign_relay_bps_limit\x18\x1a \x01(\x04B\x1a\x8a\xb5\x18\x16\n" +
|
||||
"\x1418446744073709551615R\x14foreignRelayBpsLimit\x125\n" +
|
||||
"\x12multi_thread_count\x18\x1b \x01(\rB\a\x8a\xb5\x18\x03\n" +
|
||||
"\x012R\x10multiThreadCount\x12S\n" +
|
||||
" enable_relay_foreign_network_kcp\x18\x1c \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x1cenableRelayForeignNetworkKcp\x12U\n" +
|
||||
"!enable_relay_foreign_network_quic\x18$ \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x1denableRelayForeignNetworkQuic\x12B\n" +
|
||||
"\x14encryption_algorithm\x18\x1d \x01(\tB\x0f\x8a\xb5\x18\v\n" +
|
||||
"\aaes-gcm\x18\x01R\x13encryptionAlgorithm\x12F\n" +
|
||||
"\x19disable_sym_hole_punching\x18\x1e \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x16disableSymHolePunching\x12/\n" +
|
||||
"\ftld_dns_zone\x18\x1f \x01(\tB\r\x8a\xb5\x18\t\n" +
|
||||
"\aet.net.R\n" +
|
||||
"tldDnsZone\x12&\n" +
|
||||
"\bp2p_only\x18 \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\ap2pOnly\x12F\n" +
|
||||
"\x19disable_tcp_hole_punching\x18\" \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x16disableTcpHolePunching\x12&\n" +
|
||||
"\blazy_p2p\x18% \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\alazyP2p\x12&\n" +
|
||||
"\bneed_p2p\x18& \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\aneedP2p\x12S\n" +
|
||||
"\x17instance_recv_bps_limit\x18' \x01(\x04B\x1c\x8a\xb5\x18\x18\n" +
|
||||
"\x1418446744073709551615\x18\x01R\x14instanceRecvBpsLimit\x12.\n" +
|
||||
"\fdisable_upnp\x18( \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\vdisableUpnp\x129\n" +
|
||||
"\x12disable_relay_data\x18) \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x10disableRelayData\x12H\n" +
|
||||
"\x1aenable_udp_broadcast_relay\x18* \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x17enableUdpBroadcastRelay\x12,\n" +
|
||||
"\vsocket_mark\x18+ \x01(\rB\x06\x8a\xb5\x18\x02\x18\x01H\x00R\n" +
|
||||
"socketMark\x88\x01\x01\x127\n" +
|
||||
"\x11prefer_peer_relay\x18, \x01(\bB\v\x8a\xb5\x18\a\n" +
|
||||
"\x05falseR\x0fpreferPeerRelayB\x0e\n" +
|
||||
"\f_socket_mark\"\x95\x01\n" +
|
||||
"\rRpcDescriptor\x12\x1f\n" +
|
||||
"\vdomain_name\x18\x01 \x01(\tR\n" +
|
||||
@@ -2371,7 +2421,7 @@ var file_common_proto_goTypes = []any{
|
||||
(CompressionAlgoPb)(0), // 0: common.CompressionAlgoPb
|
||||
(NatType)(0), // 1: common.NatType
|
||||
(SocketType)(0), // 2: common.SocketType
|
||||
(*FlagsInConfig)(nil), // 3: common.FlagsInConfig
|
||||
(*Flags)(nil), // 3: common.Flags
|
||||
(*RpcDescriptor)(nil), // 4: common.RpcDescriptor
|
||||
(*RpcRequest)(nil), // 5: common.RpcRequest
|
||||
(*DirectRpcRequest)(nil), // 6: common.DirectRpcRequest
|
||||
@@ -2398,7 +2448,7 @@ var file_common_proto_goTypes = []any{
|
||||
(*error1.Error)(nil), // 27: error.Error
|
||||
}
|
||||
var file_common_proto_depIdxs = []int32{
|
||||
0, // 0: common.FlagsInConfig.data_compress_algo:type_name -> common.CompressionAlgoPb
|
||||
0, // 0: common.Flags.data_compress_algo:type_name -> common.CompressionAlgoPb
|
||||
4, // 1: common.RpcRequest.descriptor:type_name -> common.RpcDescriptor
|
||||
6, // 2: common.HostManagementRequest.rpc:type_name -> common.DirectRpcRequest
|
||||
12, // 3: common.HostManagementRequest.prepared_instance_id:type_name -> common.UUID
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.36.1
|
||||
// source: annotations.proto
|
||||
|
||||
package easytier
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
descriptorpb "google.golang.org/protobuf/types/descriptorpb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// The name the management API spells a flag with, where it differs from the
|
||||
// flag's own, and whether that field is the flag's negation.
|
||||
type ApiSpelling struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Field string `protobuf:"bytes,1,opt,name=field,proto3" json:"field,omitempty"`
|
||||
Negate bool `protobuf:"varint,2,opt,name=negate,proto3" json:"negate,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ApiSpelling) Reset() {
|
||||
*x = ApiSpelling{}
|
||||
mi := &file_annotations_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ApiSpelling) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ApiSpelling) ProtoMessage() {}
|
||||
|
||||
func (x *ApiSpelling) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_annotations_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ApiSpelling.ProtoReflect.Descriptor instead.
|
||||
func (*ApiSpelling) Descriptor() ([]byte, []int) {
|
||||
return file_annotations_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ApiSpelling) GetField() string {
|
||||
if x != nil {
|
||||
return x.Field
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ApiSpelling) GetNegate() bool {
|
||||
if x != nil {
|
||||
return x.Negate
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// What a flag is worth and how the layers around it name it.
|
||||
//
|
||||
// `default` holds the value the core runs for a flag a configuration does not
|
||||
// state, written the way the flag's own type reads; an `optional` flag leaves
|
||||
// it out, which is what "unset" means. A form has a non-boolean control for
|
||||
// `form`; boolean flags need no marker, since the type says so.
|
||||
type FlagMeta struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Default *string `protobuf:"bytes,1,opt,name=default,proto3,oneof" json:"default,omitempty"`
|
||||
Api *ApiSpelling `protobuf:"bytes,2,opt,name=api,proto3" json:"api,omitempty"`
|
||||
Form bool `protobuf:"varint,3,opt,name=form,proto3" json:"form,omitempty"`
|
||||
Deprecated bool `protobuf:"varint,4,opt,name=deprecated,proto3" json:"deprecated,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *FlagMeta) Reset() {
|
||||
*x = FlagMeta{}
|
||||
mi := &file_annotations_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *FlagMeta) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*FlagMeta) ProtoMessage() {}
|
||||
|
||||
func (x *FlagMeta) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_annotations_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use FlagMeta.ProtoReflect.Descriptor instead.
|
||||
func (*FlagMeta) Descriptor() ([]byte, []int) {
|
||||
return file_annotations_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *FlagMeta) GetDefault() string {
|
||||
if x != nil && x.Default != nil {
|
||||
return *x.Default
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *FlagMeta) GetApi() *ApiSpelling {
|
||||
if x != nil {
|
||||
return x.Api
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *FlagMeta) GetForm() bool {
|
||||
if x != nil {
|
||||
return x.Form
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FlagMeta) GetDeprecated() bool {
|
||||
if x != nil {
|
||||
return x.Deprecated
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var file_annotations_proto_extTypes = []protoimpl.ExtensionInfo{
|
||||
{
|
||||
ExtendedType: (*descriptorpb.FieldOptions)(nil),
|
||||
ExtensionType: (*FlagMeta)(nil),
|
||||
Field: 50001,
|
||||
Name: "easytier.flag",
|
||||
Tag: "bytes,50001,opt,name=flag",
|
||||
Filename: "annotations.proto",
|
||||
},
|
||||
}
|
||||
|
||||
// Extension fields to descriptorpb.FieldOptions.
|
||||
var (
|
||||
// optional easytier.FlagMeta flag = 50001;
|
||||
E_Flag = &file_annotations_proto_extTypes[0]
|
||||
)
|
||||
|
||||
var File_annotations_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_annotations_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x11annotations.proto\x12\beasytier\x1a google/protobuf/descriptor.proto\";\n" +
|
||||
"\vApiSpelling\x12\x14\n" +
|
||||
"\x05field\x18\x01 \x01(\tR\x05field\x12\x16\n" +
|
||||
"\x06negate\x18\x02 \x01(\bR\x06negate\"\x92\x01\n" +
|
||||
"\bFlagMeta\x12\x1d\n" +
|
||||
"\adefault\x18\x01 \x01(\tH\x00R\adefault\x88\x01\x01\x12'\n" +
|
||||
"\x03api\x18\x02 \x01(\v2\x15.easytier.ApiSpellingR\x03api\x12\x12\n" +
|
||||
"\x04form\x18\x03 \x01(\bR\x04form\x12\x1e\n" +
|
||||
"\n" +
|
||||
"deprecated\x18\x04 \x01(\bR\n" +
|
||||
"deprecatedB\n" +
|
||||
"\n" +
|
||||
"\b_default:G\n" +
|
||||
"\x04flag\x12\x1d.google.protobuf.FieldOptions\x18ц\x03 \x01(\v2\x12.easytier.FlagMetaR\x04flagb\x06proto3"
|
||||
|
||||
var (
|
||||
file_annotations_proto_rawDescOnce sync.Once
|
||||
file_annotations_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_annotations_proto_rawDescGZIP() []byte {
|
||||
file_annotations_proto_rawDescOnce.Do(func() {
|
||||
file_annotations_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_annotations_proto_rawDesc), len(file_annotations_proto_rawDesc)))
|
||||
})
|
||||
return file_annotations_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_annotations_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_annotations_proto_goTypes = []any{
|
||||
(*ApiSpelling)(nil), // 0: easytier.ApiSpelling
|
||||
(*FlagMeta)(nil), // 1: easytier.FlagMeta
|
||||
(*descriptorpb.FieldOptions)(nil), // 2: google.protobuf.FieldOptions
|
||||
}
|
||||
var file_annotations_proto_depIdxs = []int32{
|
||||
0, // 0: easytier.FlagMeta.api:type_name -> easytier.ApiSpelling
|
||||
2, // 1: easytier.flag:extendee -> google.protobuf.FieldOptions
|
||||
1, // 2: easytier.flag:type_name -> easytier.FlagMeta
|
||||
3, // [3:3] is the sub-list for method output_type
|
||||
3, // [3:3] is the sub-list for method input_type
|
||||
2, // [2:3] is the sub-list for extension type_name
|
||||
1, // [1:2] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_annotations_proto_init() }
|
||||
func file_annotations_proto_init() {
|
||||
if File_annotations_proto != nil {
|
||||
return
|
||||
}
|
||||
file_annotations_proto_msgTypes[1].OneofWrappers = []any{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_annotations_proto_rawDesc), len(file_annotations_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 1,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_annotations_proto_goTypes,
|
||||
DependencyIndexes: file_annotations_proto_depIdxs,
|
||||
MessageInfos: file_annotations_proto_msgTypes,
|
||||
ExtensionInfos: file_annotations_proto_extTypes,
|
||||
}.Build()
|
||||
File_annotations_proto = out.File
|
||||
file_annotations_proto_goTypes = nil
|
||||
file_annotations_proto_depIdxs = nil
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.35.1
|
||||
// protoc v7.36.1
|
||||
// source: error.proto
|
||||
|
||||
package error
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.35.1
|
||||
// protoc v7.36.1
|
||||
// source: peer_rpc.proto
|
||||
|
||||
package peer_rpc
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
package proto
|
||||
|
||||
const (
|
||||
EasyTierCommit = "599e4eacaa9c9a6f84b8d6439418af9d860f9aa3"
|
||||
SchemaSHA256 = "7de60ee229e6ee2f17e3673cff6f9d9d4d4bece13ed74e46b1999277b364db67"
|
||||
EasyTierCommit = "9a2290f312dbdec6f40e8b13d8a6de5f66e4c570"
|
||||
SchemaSHA256 = "c145983fee99e08f6207c425cea0952c1aaf50f9bfea12c834d1ec985e0b39df"
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v7.35.1
|
||||
// protoc v7.36.1
|
||||
// source: web.proto
|
||||
|
||||
package web
|
||||
|
||||
@@ -11,8 +11,8 @@ if [[ ! -f "${proto_root}/api_instance.proto" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$(protoc --version)" != "libprotoc 35.1" ]]; then
|
||||
echo "protoc 35.1 is required" >&2
|
||||
if [[ "$(protoc --version)" != "libprotoc 35.1" && "$(protoc --version)" != "libprotoc 36.1" ]]; then
|
||||
echo "protoc 35.1 or 36.1 is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(protoc-gen-go --version)" != "protoc-gen-go v1.36.11" ]]; then
|
||||
@@ -24,6 +24,7 @@ protoc \
|
||||
-I "${proto_root}" \
|
||||
--go_out="${repository_root}" \
|
||||
--go_opt=module=github.com/easytier/easytier/easytier-go \
|
||||
--go_opt=Mannotations.proto=github.com/easytier/easytier/easytier-go/proto/easytier \
|
||||
--go_opt=Mcommon.proto=github.com/easytier/easytier/easytier-go/proto/common \
|
||||
--go_opt=Merror.proto=github.com/easytier/easytier/easytier-go/proto/error \
|
||||
--go_opt=Macl.proto=github.com/easytier/easytier/easytier-go/proto/acl \
|
||||
@@ -32,6 +33,7 @@ protoc \
|
||||
--go_opt=Mapi_config.proto=github.com/easytier/easytier/easytier-go/proto/api/config \
|
||||
--go_opt=Mapi_manage.proto=github.com/easytier/easytier/easytier-go/proto/api/manage \
|
||||
--go_opt=Mweb.proto=github.com/easytier/easytier/easytier-go/proto/web \
|
||||
"${proto_root}/annotations.proto" \
|
||||
"${proto_root}/common.proto" \
|
||||
"${proto_root}/error.proto" \
|
||||
"${proto_root}/acl.proto" \
|
||||
|
||||
@@ -13,6 +13,7 @@ license-file = "../LICENSE"
|
||||
build = "build/main.rs"
|
||||
|
||||
[dependencies]
|
||||
optionize = { workspace = true, optional = true }
|
||||
anyhow = { workspace = true, optional = true }
|
||||
async-trait = { workspace = true, optional = true }
|
||||
auto_impl = { workspace = true, optional = true }
|
||||
@@ -28,6 +29,7 @@ pbjson = { version = "0.9.0", optional = true }
|
||||
serde = { workspace = true, features = ["derive"], optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
sha2 = { workspace = true, optional = true }
|
||||
strum = { workspace = true, features = ["derive"], optional = true }
|
||||
thiserror = { workspace = true, optional = true }
|
||||
tokio = { workspace = true, features = ["time"], optional = true }
|
||||
url = { workspace = true, features = ["serde"], optional = true }
|
||||
@@ -35,11 +37,16 @@ uuid = { workspace = true, features = ["serde"], optional = true }
|
||||
x25519-dalek = { workspace = true, features = ["static_secrets"], optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow.workspace = true
|
||||
indoc.workspace = true
|
||||
pbjson-build = "0.9.0"
|
||||
proc-macro2 = "1"
|
||||
prost.workspace = true
|
||||
prost-build = "0.14.4"
|
||||
prost-reflect = "0.16.4"
|
||||
prost-types.workspace = true
|
||||
quote = "1"
|
||||
serde_json.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
reqwest = { workspace = true, default-features = true, features = ["blocking"] }
|
||||
@@ -71,6 +78,7 @@ full = [
|
||||
]
|
||||
api = ["core"]
|
||||
core = [
|
||||
"dep:optionize",
|
||||
"dep:anyhow",
|
||||
"dep:async-trait",
|
||||
"dep:auto_impl",
|
||||
@@ -85,6 +93,7 @@ core = [
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"dep:sha2",
|
||||
"dep:strum",
|
||||
"dep:thiserror",
|
||||
"dep:tokio",
|
||||
"dep:url",
|
||||
@@ -114,4 +123,5 @@ ignored = [
|
||||
"auto_impl",
|
||||
"pbjson",
|
||||
"serde",
|
||||
"strum",
|
||||
]
|
||||
@@ -0,0 +1,149 @@
|
||||
//! Reads the `(easytier.flag)` annotations off `common.Flags` and writes them
|
||||
//! out as Rust the crate compiles in.
|
||||
//!
|
||||
//! Each annotation carries what the core runs for a flag a configuration does
|
||||
//! not state, the name the management API spells it with, and whether a config
|
||||
//! form offers it. Nothing here is a second list: the field, its type and its
|
||||
//! presence are the schema's, and an unannotated field fails the build.
|
||||
|
||||
use std::{
|
||||
fmt::Write as _,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, bail, ensure};
|
||||
use prost_reflect::{DescriptorPool, DynamicMessage, Kind, Value};
|
||||
|
||||
pub fn write(descriptor_set: &[u8], out: &Path) -> anyhow::Result<PathBuf> {
|
||||
let pool = DescriptorPool::decode(descriptor_set)?;
|
||||
let message = pool
|
||||
.get_message_by_name("common.Flags")
|
||||
.context("the compiled schema declares no common.Flags")?;
|
||||
let extension = pool
|
||||
.get_extension_by_name("easytier.flag")
|
||||
.context("annotations.proto declares no easytier.flag")?;
|
||||
// A form reads and writes the management API, so a flag it manages has to
|
||||
// be one the API carries.
|
||||
let api = pool
|
||||
.get_message_by_name("api.manage.NetworkConfig")
|
||||
.context("the compiled schema declares no api.manage.NetworkConfig")?;
|
||||
let api_carries = |name: &str| api.get_field_by_name(name).is_some();
|
||||
|
||||
let mut defaults: Vec<String> = Vec::new();
|
||||
let mut form = String::new();
|
||||
|
||||
for field in message.fields() {
|
||||
let name = field.name();
|
||||
let options = field.options();
|
||||
ensure!(
|
||||
options.has_extension(&extension),
|
||||
"common.Flags.{name} carries no (easytier.flag) annotation"
|
||||
);
|
||||
let Value::Message(meta) = options.get_extension(&extension).into_owned() else {
|
||||
bail!("easytier.flag on common.Flags.{name} is not a message");
|
||||
};
|
||||
|
||||
// The value the flag runs with, written the way its type reads; JSON
|
||||
// needs quotes around text, and an optional flag that states nothing
|
||||
// is the one that runs unset. What the value *means* is protobuf's to
|
||||
// say when the crate reads this document back.
|
||||
let value = match text(&meta, "default", name)? {
|
||||
Some(text) => match field.kind() {
|
||||
Kind::String | Kind::Enum(_) => serde_json::to_string(&text)?,
|
||||
_ => text,
|
||||
},
|
||||
None if field.supports_presence() => "null".to_owned(),
|
||||
None => bail!("common.Flags.{name} states no default"),
|
||||
};
|
||||
defaults.push(format!(" {name:?}: {value}"));
|
||||
|
||||
// The name the management API uses, where the flag's own name is not it.
|
||||
let spelling = match meta.get_field_by_name("api").as_deref() {
|
||||
Some(Value::Message(api)) => match api.get_field_by_name("field").as_deref() {
|
||||
Some(Value::String(field)) if !field.is_empty() => Some(field.clone()),
|
||||
_ => None,
|
||||
},
|
||||
Some(_) => bail!("common.Flags.{name}: the api spelling is not a message"),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(spelling) = &spelling {
|
||||
ensure!(
|
||||
api_carries(spelling),
|
||||
"common.Flags.{name} is spelled {spelling} by the management API, \
|
||||
which carries no such field"
|
||||
);
|
||||
}
|
||||
|
||||
let deprecated = is_set(&meta, "deprecated");
|
||||
|
||||
ensure!(
|
||||
deprecated
|
||||
== matches!(
|
||||
options.get_field_by_name("deprecated").as_deref(),
|
||||
Some(Value::Bool(true))
|
||||
),
|
||||
"common.Flags.{name}: the annotation says deprecated = {deprecated}, \
|
||||
but the field says the contrary"
|
||||
);
|
||||
|
||||
// The schema field names a config form owns, used as TOML keys.
|
||||
//
|
||||
// Only what the API carries can be a key here: the merge drops a key
|
||||
// the form produces nothing for, so a flag the API cannot express
|
||||
// would be deleted from a stored configuration on every save.
|
||||
let api_name = spelling.as_deref().unwrap_or(name);
|
||||
if is_set(&meta, "form") {
|
||||
ensure!(
|
||||
api_carries(api_name),
|
||||
"common.Flags.{name} says a form has a control for it, but \
|
||||
api.manage.NetworkConfig carries no {api_name}"
|
||||
);
|
||||
}
|
||||
if !deprecated
|
||||
&& api_carries(api_name)
|
||||
&& (matches!(field.kind(), Kind::Bool) || is_set(&meta, "form"))
|
||||
{
|
||||
writeln!(form, " {name:?},")?;
|
||||
}
|
||||
}
|
||||
|
||||
let defaults = defaults.join(",\n");
|
||||
let generated = format!(
|
||||
"\
|
||||
// @generated by easytier-proto's build script from the (easytier.flag)
|
||||
// annotations on proto/common.proto. Do not edit.
|
||||
|
||||
/// The values a network runs with when a configuration states no flag, as the
|
||||
/// schema declares them in protobuf JSON.
|
||||
pub const DEFAULTS: &str = r##\"{{
|
||||
{defaults}}}\"##;
|
||||
|
||||
/// The schema field names of flags managed by the config form.
|
||||
pub const FORM: &[&str] = &[
|
||||
{form}];
|
||||
"
|
||||
);
|
||||
|
||||
let path = out.join("flags.rs");
|
||||
std::fs::write(&path, generated)?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// A text field of the annotation, absent when the annotation leaves it out.
|
||||
fn text(meta: &DynamicMessage, field: &str, flag: &str) -> anyhow::Result<Option<String>> {
|
||||
if !meta.has_field_by_name(field) {
|
||||
return Ok(None);
|
||||
}
|
||||
match meta.get_field_by_name(field).as_deref() {
|
||||
Some(Value::String(text)) => Ok(Some(text.clone())),
|
||||
Some(other) => bail!("common.Flags.{flag}: {field} is {other:?}, expected text"),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_set(meta: &DynamicMessage, field: &str) -> bool {
|
||||
matches!(
|
||||
meta.get_field_by_name(field).as_deref(),
|
||||
Some(Value::Bool(true))
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod flags;
|
||||
mod rpc;
|
||||
|
||||
use crate::rpc::ServiceGenerator;
|
||||
@@ -81,7 +82,7 @@ fn ensure_protoc_for_windows() {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn main() -> anyhow::Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
ensure_protoc_for_windows();
|
||||
|
||||
@@ -106,7 +107,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -121,7 +121,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.extern_path(".google.protobuf.Value", "::prost_types::Value");
|
||||
}
|
||||
config
|
||||
.file_descriptor_set_path(&descriptor)
|
||||
.file_descriptor_set_path(out.join("descriptors.bin"))
|
||||
.service_generator(Box::new(ServiceGenerator::default()))
|
||||
.btree_map(["."])
|
||||
.skip_debug([
|
||||
@@ -130,16 +130,78 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
".common.UUID",
|
||||
".api.manage.ManagedCredentialConfig",
|
||||
".api.manage.VpnPortalConfig",
|
||||
]);
|
||||
])
|
||||
.type_attribute(
|
||||
".common.CompressionAlgoPb",
|
||||
"#[derive(strum::EnumString, strum::Display)]",
|
||||
)
|
||||
.type_attribute(
|
||||
".common.CompressionAlgoPb",
|
||||
"#[strum(ascii_case_insensitive)]",
|
||||
)
|
||||
.field_attribute(".common.CompressionAlgoPb.Invalid", "#[strum(disabled)]");
|
||||
|
||||
config.compile_protos(&proto_files, &["proto/"])?;
|
||||
let mut descriptor_set = config.load_fds(&proto_files, &["proto/"])?;
|
||||
// What protoc wrote (this set carries every file, imports included), read
|
||||
// before anything else may touch it: the annotations are extension fields,
|
||||
// which a prost-types round-trip drops.
|
||||
let annotated = std::fs::read(out.join("descriptors.bin"))?;
|
||||
|
||||
{
|
||||
let common = descriptor_set
|
||||
.file
|
||||
.iter_mut()
|
||||
.find(|file| file.package.as_deref() == Some("common"))
|
||||
.unwrap();
|
||||
let flags = common
|
||||
.message_type
|
||||
.iter()
|
||||
.find(|message| message.name.as_deref() == Some("Flags"))
|
||||
.unwrap();
|
||||
config
|
||||
.type_attribute(".common.Flags", "#[optionize::optionized]")
|
||||
.type_attribute(".common.Flags", "#[optionize(object = FlagsPatch)]");
|
||||
|
||||
let flattened = flags
|
||||
.field
|
||||
.iter()
|
||||
.filter(|field| field.proto3_optional())
|
||||
.map(|field| field.name().to_owned());
|
||||
for name in flattened {
|
||||
config.field_attribute(format!(".common.Flags.{name}"), "#[optionize(flatten)]");
|
||||
}
|
||||
|
||||
let mut patch = flags.clone();
|
||||
patch.name = Some("FlagsPatch".to_owned());
|
||||
// Proto3 spells `optional` as one synthetic one-of per field.
|
||||
let oneofs = patch
|
||||
.field
|
||||
.iter()
|
||||
.map(|field| prost_types::OneofDescriptorProto {
|
||||
name: Some(format!("_{}", field.name())),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
patch.oneof_decl = oneofs;
|
||||
for (index, field) in patch.field.iter_mut().enumerate() {
|
||||
field.proto3_optional = Some(true);
|
||||
field.label = Some(prost_types::field_descriptor_proto::Label::Optional as i32);
|
||||
field.oneof_index = Some(index as i32);
|
||||
}
|
||||
common.message_type.push(patch);
|
||||
|
||||
config.disable_comments([".common.FlagsPatch"]);
|
||||
}
|
||||
|
||||
let descriptors = prost::Message::encode_to_vec(&descriptor_set);
|
||||
|
||||
config.file_descriptor_set_path(out.join("file_descriptor_set.bin"));
|
||||
config.compile_protos(&proto_files_reflect, &["proto/"])?;
|
||||
config.compile_fds(descriptor_set)?;
|
||||
flags::write(&annotated, &out)?;
|
||||
|
||||
let descriptor = std::fs::read(descriptor)?;
|
||||
pbjson_build::Builder::new()
|
||||
.register_descriptors(&descriptor)?
|
||||
.register_descriptors(&descriptors)?
|
||||
.preserve_proto_field_names()
|
||||
.btree_map(["."])
|
||||
.build(&["."])?;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package easytier;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
// The name the management API spells a flag with, where it differs from the
|
||||
// flag's own, and whether that field is the flag's negation.
|
||||
message ApiSpelling {
|
||||
string field = 1;
|
||||
bool negate = 2;
|
||||
}
|
||||
|
||||
// What a flag is worth and how the layers around it name it.
|
||||
//
|
||||
// `default` holds the value the core runs for a flag a configuration does not
|
||||
// state, written the way the flag's own type reads; an `optional` flag leaves
|
||||
// it out, which is what "unset" means. A form has a non-boolean control for
|
||||
// `form`; boolean flags need no marker, since the type says so.
|
||||
message FlagMeta {
|
||||
optional string default = 1;
|
||||
ApiSpelling api = 2;
|
||||
bool form = 3;
|
||||
bool deprecated = 4;
|
||||
}
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
FlagMeta flag = 50001;
|
||||
}
|
||||
@@ -1,95 +1,97 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "annotations.proto";
|
||||
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;
|
||||
message Flags {
|
||||
string default_protocol = 1 [(easytier.flag) = { default: "tcp" }];
|
||||
string dev_name = 2 [(easytier.flag) = { default: "", form: true }];
|
||||
bool enable_encryption = 3 [(easytier.flag) = { default: "true", api: { field: "disable_encryption", negate: true } }];
|
||||
bool enable_ipv6 = 4 [(easytier.flag) = { default: "true", api: { field: "disable_ipv6", negate: true } }];
|
||||
uint32 mtu = 5 [(easytier.flag) = { default: "1380", form: true }];
|
||||
bool latency_first = 6 [(easytier.flag) = { default: "false" }];
|
||||
bool enable_exit_node = 7 [(easytier.flag) = { default: "false" }];
|
||||
bool no_tun = 8 [(easytier.flag) = { default: "false" }];
|
||||
bool use_smoltcp = 9 [(easytier.flag) = { default: "false" }];
|
||||
string relay_network_whitelist = 10 [(easytier.flag) = { default: "*", form: true }];
|
||||
bool disable_p2p = 11 [(easytier.flag) = { default: "false" }];
|
||||
bool relay_all_peer_rpc = 12 [(easytier.flag) = { default: "false" }];
|
||||
bool disable_udp_hole_punching = 13 [(easytier.flag) = { default: "false" }];
|
||||
// string ipv6_listener = 14; [deprecated = true]; use -l udp://[::]:12345
|
||||
// instead
|
||||
bool multi_thread = 15;
|
||||
CompressionAlgoPb data_compress_algo = 16;
|
||||
bool bind_device = 17;
|
||||
bool multi_thread = 15 [(easytier.flag) = { default: "true" }];
|
||||
CompressionAlgoPb data_compress_algo = 16 [(easytier.flag) = { default: "None", form: true }];
|
||||
bool bind_device = 17 [(easytier.flag) = { default: "true" }];
|
||||
|
||||
// should we convert all tcp streams into kcp streams
|
||||
bool enable_kcp_proxy = 18;
|
||||
bool enable_kcp_proxy = 18 [(easytier.flag) = { default: "false" }];
|
||||
// does this peer allow kcp input
|
||||
bool disable_kcp_input = 19;
|
||||
bool disable_kcp_input = 19 [(easytier.flag) = { default: "false" }];
|
||||
// disable relay local network kcp packets
|
||||
bool disable_relay_kcp = 20;
|
||||
bool proxy_forward_by_system = 21;
|
||||
bool disable_relay_kcp = 20 [(easytier.flag) = { default: "false" }];
|
||||
bool proxy_forward_by_system = 21 [(easytier.flag) = { default: "false" }];
|
||||
|
||||
// enable magic dns or not
|
||||
bool accept_dns = 22;
|
||||
bool accept_dns = 22 [(easytier.flag) = { default: "false", api: { field: "enable_magic_dns" } }];
|
||||
// enable private mode
|
||||
bool private_mode = 23;
|
||||
bool private_mode = 23 [(easytier.flag) = { default: "false", api: { field: "enable_private_mode" } }];
|
||||
|
||||
// should we convert all tcp streams into quic streams
|
||||
bool enable_quic_proxy = 24;
|
||||
bool enable_quic_proxy = 24 [(easytier.flag) = { default: "false" }];
|
||||
// does this peer allow quic input
|
||||
bool disable_quic_input = 25;
|
||||
bool disable_quic_input = 25 [(easytier.flag) = { default: "false" }];
|
||||
// disable relay local network quic packets
|
||||
bool disable_relay_quic = 35;
|
||||
bool disable_relay_quic = 35 [(easytier.flag) = { default: "false" }];
|
||||
|
||||
// quic listen port
|
||||
uint32 quic_listen_port = 33 [deprecated = true];
|
||||
uint32 quic_listen_port = 33 [deprecated = true, (easytier.flag) = { default: "4294967295", deprecated: true }];
|
||||
|
||||
// a global relay limit, only work for foreign network
|
||||
uint64 foreign_relay_bps_limit = 26;
|
||||
uint64 foreign_relay_bps_limit = 26 [(easytier.flag) = { default: "18446744073709551615" }];
|
||||
|
||||
uint32 multi_thread_count = 27;
|
||||
uint32 multi_thread_count = 27 [(easytier.flag) = { default: "2" }];
|
||||
|
||||
// enable relay foreign network kcp packets
|
||||
bool enable_relay_foreign_network_kcp = 28;
|
||||
bool enable_relay_foreign_network_kcp = 28 [(easytier.flag) = { default: "false" }];
|
||||
|
||||
// enable relay foreign network quic packets
|
||||
bool enable_relay_foreign_network_quic = 36;
|
||||
bool enable_relay_foreign_network_quic = 36 [(easytier.flag) = { default: "false" }];
|
||||
|
||||
// encryption algorithm to use, empty string means default (aes-gcm)
|
||||
string encryption_algorithm = 29;
|
||||
string encryption_algorithm = 29 [(easytier.flag) = { default: "aes-gcm", form: true }];
|
||||
|
||||
// disable symmetric nat hole punching, treat symmetric as cone when enabled
|
||||
bool disable_sym_hole_punching = 30;
|
||||
bool disable_sym_hole_punching = 30 [(easytier.flag) = { default: "false" }];
|
||||
|
||||
// tld dns zone for magic dns
|
||||
string tld_dns_zone = 31;
|
||||
string tld_dns_zone = 31 [(easytier.flag) = { default: "et.net." }];
|
||||
|
||||
bool p2p_only = 32;
|
||||
bool p2p_only = 32 [(easytier.flag) = { default: "false" }];
|
||||
|
||||
bool disable_tcp_hole_punching = 34;
|
||||
bool disable_tcp_hole_punching = 34 [(easytier.flag) = { default: "false" }];
|
||||
|
||||
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;
|
||||
bool lazy_p2p = 37 [(easytier.flag) = { default: "false" }];
|
||||
bool need_p2p = 38 [(easytier.flag) = { default: "false" }];
|
||||
uint64 instance_recv_bps_limit = 39 [(easytier.flag) = { default: "18446744073709551615", form: true }];
|
||||
bool disable_upnp = 40 [(easytier.flag) = { default: "false" }];
|
||||
bool disable_relay_data = 41 [(easytier.flag) = { default: "false" }];
|
||||
bool enable_udp_broadcast_relay = 42 [(easytier.flag) = { default: "false" }];
|
||||
|
||||
// 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;
|
||||
optional uint32 socket_mark = 43 [(easytier.flag) = { form: true }];
|
||||
|
||||
// Prefer direct credential peers that already relay to a destination over
|
||||
// advertising another direct edge to the same destination.
|
||||
bool prefer_peer_relay = 44;
|
||||
bool prefer_peer_relay = 44 [(easytier.flag) = { default: "false" }];
|
||||
}
|
||||
|
||||
|
||||
message RpcDescriptor {
|
||||
// allow same service registered multiple times in different domain
|
||||
string domain_name = 1;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use anyhow::Context;
|
||||
use base64::{Engine as _, prelude::BASE64_STANDARD};
|
||||
use optionize::Optionizable as _;
|
||||
use prost::Message as _;
|
||||
use std::time::SystemTime;
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
str::FromStr,
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
const IP_SCHEMES: &[&str] = &["tcp", "udp", "wg", "quic", "ws", "wss", "faketcp"];
|
||||
@@ -11,6 +14,76 @@ 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"));
|
||||
|
||||
impl Flags {
|
||||
/// The flags the message declares, in declaration order, less the ones it
|
||||
/// has deprecated: what a user can still set.
|
||||
///
|
||||
/// Read back from the descriptor set this crate embeds, so a flag added to
|
||||
/// the message shows up here without a second list to keep in step.
|
||||
pub fn flags() -> &'static [prost_types::FieldDescriptorProto] {
|
||||
static FLAGS: OnceLock<Vec<prost_types::FieldDescriptorProto>> = OnceLock::new();
|
||||
FLAGS.get_or_init(|| {
|
||||
prost_types::FileDescriptorSet::decode(crate::DESCRIPTOR_POOL_BYTES)
|
||||
.unwrap()
|
||||
.file
|
||||
.into_iter()
|
||||
.find(|file| file.package() == "common")
|
||||
.unwrap()
|
||||
.message_type
|
||||
.into_iter()
|
||||
.find(|message| message.name() == "Flags")
|
||||
.unwrap()
|
||||
.field
|
||||
.into_iter()
|
||||
.filter(|field| {
|
||||
!field
|
||||
.options
|
||||
.as_ref()
|
||||
.is_some_and(|options| options.deprecated())
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// The values a network runs with when a configuration states no flag.
|
||||
///
|
||||
/// The build script reads them off the `(easytier.flag)` annotations on the
|
||||
/// message, in protobuf JSON, and the config UI reads the same annotations
|
||||
/// from its own bindings, so an unset flag means the same thing on both
|
||||
/// sides.
|
||||
pub fn defaults() -> Self {
|
||||
static DEFAULTS: OnceLock<Flags> = OnceLock::new();
|
||||
DEFAULTS
|
||||
.get_or_init(|| {
|
||||
serde_json::from_str(flags::DEFAULTS)
|
||||
.expect("the build script writes the defaults from the schema")
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Applies `patch` over [`Flags::defaults`]. A field the user left out keeps
|
||||
/// its default, so nothing downstream has to decide what "unset" means.
|
||||
pub fn resolve(patch: FlagsPatch) -> Self {
|
||||
let mut flags = Self::defaults();
|
||||
flags.load(patch);
|
||||
flags
|
||||
}
|
||||
|
||||
/// The schema field names of flags managed by the config form.
|
||||
///
|
||||
/// These are TOML keys. Only flags the management API carries are included:
|
||||
/// a merge drops a key the form produces nothing for.
|
||||
pub fn form() -> &'static [&'static str] {
|
||||
flags::FORM
|
||||
}
|
||||
}
|
||||
|
||||
/// What the `(easytier.flag)` annotations on the message declare, generated by
|
||||
/// this crate's build script.
|
||||
mod flags {
|
||||
include!(concat!(env!("OUT_DIR"), "/flags.rs"));
|
||||
}
|
||||
|
||||
pub trait TimestampExt {
|
||||
fn now() -> Self;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ rust-version.workspace = true
|
||||
description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server."
|
||||
|
||||
[dependencies]
|
||||
optionize.workspace = true
|
||||
easytier = { workspace = true, default-features = true }
|
||||
easytier-core = { workspace = true, default-features = true }
|
||||
tracing.workspace = true
|
||||
|
||||
@@ -15,6 +15,7 @@ const protocWrapper = require.resolve('@protobuf-ts/protoc/protoc.js')
|
||||
const protobufTsPluginRoot = dirname(require.resolve('@protobuf-ts/plugin/package.json'))
|
||||
|
||||
const protoFiles = [
|
||||
'annotations.proto',
|
||||
'common.proto',
|
||||
'acl.proto',
|
||||
'api_instance.proto',
|
||||
|
||||
@@ -6,6 +6,8 @@ import InputGroupAddon from 'primevue/inputgroupaddon'
|
||||
import {
|
||||
addRow,
|
||||
DEFAULT_NETWORK_CONFIG,
|
||||
defaultFlagValue,
|
||||
formFlags,
|
||||
NetworkConfig,
|
||||
normalizeNetworkConfig,
|
||||
removeRow,
|
||||
@@ -137,34 +139,28 @@ interface BoolFlag {
|
||||
}
|
||||
|
||||
const bool_flags: BoolFlag[] = [
|
||||
{ field: 'latency_first', help: 'latency_first_help' },
|
||||
{ field: 'use_smoltcp', help: 'use_smoltcp_help' },
|
||||
{ field: 'disable_ipv6', help: 'disable_ipv6_help' },
|
||||
// Every boolean flag the schema declares, plus the one network option the
|
||||
// form offers as a checkbox.
|
||||
...formFlags(),
|
||||
{ field: 'ipv6_public_addr_auto', help: 'ipv6_public_addr_auto_help' },
|
||||
{ field: 'enable_kcp_proxy', help: 'enable_kcp_proxy_help' },
|
||||
{ field: 'disable_kcp_input', help: 'disable_kcp_input_help' },
|
||||
{ field: 'enable_quic_proxy', help: 'enable_quic_proxy_help' },
|
||||
{ field: 'disable_quic_input', help: 'disable_quic_input_help' },
|
||||
{ field: 'disable_p2p', help: 'disable_p2p_help' },
|
||||
{ field: 'p2p_only', help: 'p2p_only_help' },
|
||||
{ field: 'lazy_p2p', help: 'lazy_p2p_help' },
|
||||
{ field: 'bind_device', help: 'bind_device_help' },
|
||||
{ field: 'no_tun', help: 'no_tun_help' },
|
||||
{ field: 'enable_exit_node', help: 'enable_exit_node_help' },
|
||||
{ field: 'relay_all_peer_rpc', help: 'relay_all_peer_rpc_help' },
|
||||
{ field: 'need_p2p', help: 'need_p2p_help' },
|
||||
{ field: 'multi_thread', help: 'multi_thread_help' },
|
||||
{ field: 'proxy_forward_by_system', help: 'proxy_forward_by_system_help' },
|
||||
{ field: 'disable_encryption', help: 'disable_encryption_help' },
|
||||
{ field: 'disable_tcp_hole_punching', help: 'disable_tcp_hole_punching_help' },
|
||||
{ field: 'disable_udp_hole_punching', help: 'disable_udp_hole_punching_help' },
|
||||
{ field: 'enable_udp_broadcast_relay', help: 'enable_udp_broadcast_relay_help' },
|
||||
{ field: 'disable_upnp', help: 'disable_upnp_help' },
|
||||
{ field: 'disable_sym_hole_punching', help: 'disable_sym_hole_punching_help' },
|
||||
{ field: 'enable_magic_dns', help: 'enable_magic_dns_help' },
|
||||
{ field: 'enable_private_mode', help: 'enable_private_mode_help' },
|
||||
]
|
||||
|
||||
/**
|
||||
* Whether a flag's checkbox is on: what the configuration states, or the value
|
||||
* the network runs with when it states nothing. A flag this config leaves unset
|
||||
* is not off, it is the default, and several default to on.
|
||||
*/
|
||||
function flagChecked(field: keyof NetworkConfig): boolean {
|
||||
const value = curNetwork.value[field]
|
||||
|
||||
return typeof value === 'boolean' ? value : defaultFlagValue(field)
|
||||
}
|
||||
|
||||
/** A checkbox states a flag; leaving it alone leaves the config silent. */
|
||||
function setFlag(field: keyof NetworkConfig, value: boolean) {
|
||||
Object.assign(curNetwork.value, { [field]: value })
|
||||
}
|
||||
|
||||
const portForwardProtocolOptions = ref(["tcp", "udp"]);
|
||||
|
||||
const editingPortForward = ref(false);
|
||||
@@ -394,7 +390,8 @@ function removeVpnPortalClient(index: number) {
|
||||
<div class="flex flex-row flex-wrap">
|
||||
|
||||
<div class="basis-[20rem] flex items-center" v-for="flag in bool_flags">
|
||||
<Checkbox v-model="curNetwork[flag.field]" :input-id="flag.field" :binary="true" />
|
||||
<Checkbox :model-value="flagChecked(flag.field)" :input-id="flag.field" :binary="true"
|
||||
@update:model-value="setFlag(flag.field, $event)" />
|
||||
<label :for="flag.field" class="ml-2"> {{ t(flag.field) }} </label>
|
||||
<span class="pi pi-question-circle ml-2 self-center" v-tooltip="t(flag.help)"></span>
|
||||
</div>
|
||||
|
||||
@@ -800,3 +800,8 @@ acl:
|
||||
name: 名称
|
||||
type: 类型
|
||||
match: 匹配
|
||||
|
||||
disable_relay_data: 不转发数据
|
||||
disable_relay_data_help: 不转发其他节点的数据包,本节点不承载自身以外的流量。
|
||||
prefer_peer_relay: 优先凭证节点转发
|
||||
prefer_peer_relay_help: 优先通过已经转发到目标节点的凭证节点转发,而不是为同一目标再通告一条直连路径。
|
||||
@@ -800,3 +800,8 @@ acl:
|
||||
name: Name
|
||||
type: Type
|
||||
match: Match
|
||||
|
||||
disable_relay_data: Disable Data Relay
|
||||
disable_relay_data_help: Do not relay data packets for other peers, so this node carries no traffic it did not originate.
|
||||
prefer_peer_relay: Prefer Peer Relay
|
||||
prefer_peer_relay_help: Prefer a credential peer that already relays to a destination over advertising another direct edge to the same destination.
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
type VpnPortalClientConfig,
|
||||
type VpnPortalConfig,
|
||||
} from '../generated/proto/api_manage'
|
||||
import { ScalarType } from '@protobuf-ts/runtime'
|
||||
import type { FlagMeta } from '../generated/proto/annotations'
|
||||
import {
|
||||
VpnPortalClientState,
|
||||
VpnPortalInfo as VpnPortalInfoPb,
|
||||
@@ -27,6 +29,7 @@ import {
|
||||
} from '../generated/proto/acl'
|
||||
import {
|
||||
CompressionAlgoPb,
|
||||
Flags as FlagsPb,
|
||||
NatType,
|
||||
type PeerFeatureFlag,
|
||||
type SecureModeConfig,
|
||||
@@ -87,6 +90,68 @@ function emptyAcl(): Acl {
|
||||
}
|
||||
}
|
||||
|
||||
/** A flag of `common.Flags` that carries an annotation. */
|
||||
interface AnnotatedFlag {
|
||||
/** The name the management API uses for the flag. */
|
||||
field: string
|
||||
/** Whether the schema types the flag as a boolean. */
|
||||
boolean: boolean
|
||||
meta: FlagMeta
|
||||
}
|
||||
|
||||
const annotatedFlags: AnnotatedFlag[] = FlagsPb.fields.flatMap((flag) => {
|
||||
const meta = flag.options?.['easytier.flag'] as FlagMeta | undefined
|
||||
if (!meta) return []
|
||||
|
||||
return [{
|
||||
field: meta.api?.field ?? flag.name,
|
||||
boolean: flag.kind === 'scalar' && flag.T === ScalarType.BOOL,
|
||||
meta,
|
||||
}]
|
||||
})
|
||||
|
||||
/** The value a flag's annotation declares, as the flag itself is typed. */
|
||||
function declaredValue(flag: AnnotatedFlag): boolean {
|
||||
const declared = flag.meta.default === 'true'
|
||||
|
||||
return flag.meta.api?.negate ? !declared : declared
|
||||
}
|
||||
|
||||
/**
|
||||
* The value a flag runs with when the configuration does not state one, so a
|
||||
* form shows what an unset flag means instead of showing it as off.
|
||||
*/
|
||||
export function defaultFlagValue(field: keyof NetworkConfig): boolean {
|
||||
const flag = annotatedFlags.find((flag) => flag.field === field)
|
||||
|
||||
return flag ? declaredValue(flag) : false
|
||||
}
|
||||
|
||||
/** The flags the management API carries, so a form's value reaches the backend. */
|
||||
export function apiCarries(field: string): boolean {
|
||||
return NetworkConfigPb.fields.some((declared) => declared.name === field)
|
||||
}
|
||||
|
||||
/**
|
||||
* The flags a config form offers as checkboxes, in schema order: every boolean
|
||||
* flag the message declares that the management API carries, less the deprecated ones.
|
||||
*/
|
||||
export function formFlags(): { field: keyof NetworkConfig; help: string }[] {
|
||||
return annotatedFlags
|
||||
.filter((flag) => flag.boolean && !flag.meta.deprecated && apiCarries(flag.field))
|
||||
.map((flag) => ({ field: flag.field as keyof NetworkConfig, help: `${flag.field}_help` }))
|
||||
}
|
||||
|
||||
/** The value the schema declares for every boolean flag, by form field. */
|
||||
function newFlagValues(): Record<string, boolean> {
|
||||
const values: Record<string, boolean> = {}
|
||||
for (const flag of annotatedFlags) {
|
||||
if (flag.boolean) values[flag.field] = declaredValue(flag)
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
||||
return {
|
||||
...NetworkConfigPb.create(),
|
||||
@@ -113,32 +178,10 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
||||
'udp://0.0.0.0:11010',
|
||||
'wg://0.0.0.0:11011',
|
||||
],
|
||||
latency_first: false,
|
||||
...newFlagValues(),
|
||||
dev_name: '',
|
||||
|
||||
use_smoltcp: false,
|
||||
disable_ipv6: false,
|
||||
ipv6_public_addr_auto: false,
|
||||
enable_kcp_proxy: false,
|
||||
disable_kcp_input: false,
|
||||
enable_quic_proxy: false,
|
||||
disable_quic_input: false,
|
||||
disable_p2p: false,
|
||||
p2p_only: false,
|
||||
lazy_p2p: false,
|
||||
bind_device: true,
|
||||
no_tun: false,
|
||||
enable_exit_node: false,
|
||||
relay_all_peer_rpc: false,
|
||||
need_p2p: false,
|
||||
multi_thread: true,
|
||||
proxy_forward_by_system: false,
|
||||
disable_encryption: false,
|
||||
disable_tcp_hole_punching: false,
|
||||
disable_udp_hole_punching: false,
|
||||
disable_upnp: false,
|
||||
enable_udp_broadcast_relay: false,
|
||||
disable_sym_hole_punching: false,
|
||||
enable_relay_network_whitelist: false,
|
||||
relay_network_whitelist: [],
|
||||
enable_manual_routes: false,
|
||||
@@ -149,8 +192,6 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
|
||||
mtu: null,
|
||||
instance_recv_bps_limit: null,
|
||||
mapped_listeners: [],
|
||||
enable_magic_dns: false,
|
||||
enable_private_mode: false,
|
||||
port_forwards: [],
|
||||
acl: emptyAcl(),
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@ import { mount, type VueWrapper } from '@vue/test-utils'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { defineComponent, h, nextTick, reactive } from 'vue'
|
||||
import Config from '../src/components/Config.vue'
|
||||
import { ScalarType } from '@protobuf-ts/runtime'
|
||||
import type { FlagMeta } from '../src/generated/proto/annotations'
|
||||
import { NetworkConfig as NetworkConfigPb } from '../src/generated/proto/api_manage'
|
||||
import { Flags as FlagsPb } from '../src/generated/proto/common'
|
||||
import {
|
||||
DEFAULT_NETWORK_CONFIG,
|
||||
normalizeNetworkConfig,
|
||||
@@ -9,33 +13,27 @@ import {
|
||||
type NetworkConfig,
|
||||
} from '../src/types/network'
|
||||
|
||||
/** The flags the management API carries, so a form's value reaches the backend. */
|
||||
function apiCarries(field: string): boolean {
|
||||
return NetworkConfigPb.fields.some((declared) => declared.name === field)
|
||||
}
|
||||
|
||||
/**
|
||||
* The checkboxes the form is expected to offer, read off the schema the way a
|
||||
* config form reads it: every boolean flag it declares that the management API
|
||||
* carries, less the deprecated ones, plus the one network option the form adds itself.
|
||||
*/
|
||||
const CONFIG_FLAG_FIELDS = [
|
||||
'latency_first',
|
||||
'use_smoltcp',
|
||||
'disable_ipv6',
|
||||
...FlagsPb.fields.flatMap((flag) => {
|
||||
const meta = flag.options?.['easytier.flag'] as FlagMeta | undefined
|
||||
if (meta?.deprecated) return []
|
||||
if (flag.kind !== 'scalar' || flag.T !== ScalarType.BOOL) return []
|
||||
const field = (meta.api?.field ?? flag.name) as keyof NetworkConfig
|
||||
if (!apiCarries(field)) return []
|
||||
|
||||
return [field]
|
||||
}),
|
||||
'ipv6_public_addr_auto',
|
||||
'enable_kcp_proxy',
|
||||
'disable_kcp_input',
|
||||
'enable_quic_proxy',
|
||||
'disable_quic_input',
|
||||
'disable_p2p',
|
||||
'p2p_only',
|
||||
'lazy_p2p',
|
||||
'bind_device',
|
||||
'no_tun',
|
||||
'enable_exit_node',
|
||||
'relay_all_peer_rpc',
|
||||
'need_p2p',
|
||||
'multi_thread',
|
||||
'proxy_forward_by_system',
|
||||
'disable_encryption',
|
||||
'disable_tcp_hole_punching',
|
||||
'disable_udp_hole_punching',
|
||||
'enable_udp_broadcast_relay',
|
||||
'disable_upnp',
|
||||
'disable_sym_hole_punching',
|
||||
'enable_magic_dns',
|
||||
'enable_private_mode',
|
||||
] as const satisfies readonly (keyof NetworkConfig)[]
|
||||
|
||||
const CONFIG_CHECKBOX_FIELDS = [
|
||||
@@ -532,8 +530,28 @@ describe('Config.vue network config projection', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('shows a flag the configuration does not state as the value the core resolves', async () => {
|
||||
const config = makeConfig()
|
||||
delete config.bind_device
|
||||
delete config.multi_thread
|
||||
|
||||
const { curNetwork, wrapper } = mountConfig(config)
|
||||
await nextTick()
|
||||
|
||||
// Both default to on, so an unset flag must not read as off.
|
||||
expect(input(wrapper, '#bind_device').checked).toBe(true)
|
||||
expect(input(wrapper, '#multi_thread').checked).toBe(true)
|
||||
|
||||
// Toggling states the value, which is how a flag gets turned off.
|
||||
await wrapper.find('#bind_device').setValue(false)
|
||||
expect(curNetwork.bind_device).toBe(false)
|
||||
})
|
||||
|
||||
it('round-trips every visible boolean config control into backend JSON', async () => {
|
||||
const config = makeConfig()
|
||||
// A checkbox for a flag the API has no field for states a value that stops
|
||||
// at the form; the rest have to arrive at the backend.
|
||||
const reachesBackend = CONFIG_UI_BOOLEAN_FIELDS.filter(apiCarries)
|
||||
const originalFlagValues = new Map(
|
||||
CONFIG_UI_BOOLEAN_FIELDS.map((field, index) => {
|
||||
const value = index % 2 === 0
|
||||
@@ -567,6 +585,10 @@ describe('Config.vue network config projection', () => {
|
||||
for (const [field, value] of originalFlagValues) {
|
||||
const expectedValue = !value
|
||||
expect(curNetwork[field], `${field} should update config`).toBe(expectedValue)
|
||||
if (!reachesBackend.includes(field)) {
|
||||
expect(backend[field], `${field} has no api.manage.NetworkConfig field`).toBeUndefined()
|
||||
continue
|
||||
}
|
||||
expect(backend[field], `${field} should be preserved in backend JSON`).toBe(expectedValue)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"skipLibCheck": true,
|
||||
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Bundler",
|
||||
|
||||
@@ -20,10 +20,11 @@ use easytier::{
|
||||
RunNetworkInstanceRequest,
|
||||
},
|
||||
},
|
||||
common::{CompressionAlgoPb, Ipv4Inet as RpcIpv4Inet},
|
||||
common::{CompressionAlgoPb, FlagsPatch, Ipv4Inet as RpcIpv4Inet},
|
||||
rpc_types::controller::BaseController,
|
||||
},
|
||||
};
|
||||
use optionize::Retain;
|
||||
|
||||
use super::session::{SessionConfigClient, SessionRpcClient};
|
||||
|
||||
@@ -53,7 +54,11 @@ fn instance_identifier(inst_id: &str) -> anyhow::Result<InstanceIdentifier> {
|
||||
fn hot_patch_base(config: &NetworkConfig) -> anyhow::Result<NetworkConfig> {
|
||||
let data_compress_algo = normalized_data_compress_algo(config.data_compress_algo);
|
||||
let encryption_algorithm = normalized_encryption_algorithm(config.encryption_algorithm.clone());
|
||||
let mut config = NetworkConfig::new_from_config(config.gen_config()?)?;
|
||||
let config = config.gen_config()?;
|
||||
// Runtime comparison intentionally resolves defaults. Configuration exports
|
||||
// otherwise preserve whether each flag was supplied by the user.
|
||||
config.set_flags(config.get_flags());
|
||||
let mut config = NetworkConfig::new_from_config(config)?;
|
||||
let is_credential_mode = config.network_secret.is_none()
|
||||
&& config
|
||||
.secure_mode
|
||||
@@ -208,14 +213,6 @@ fn normalized_proxy_networks(config: &NetworkConfig) -> anyhow::Result<Vec<Runti
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn normalized_disable_relay_data(config: &NetworkConfig) -> anyhow::Result<bool> {
|
||||
Ok(config.gen_config()?.get_flags().disable_relay_data)
|
||||
}
|
||||
|
||||
fn normalized_prefer_peer_relay(config: &NetworkConfig) -> anyhow::Result<bool> {
|
||||
Ok(config.gen_config()?.get_flags().prefer_peer_relay)
|
||||
}
|
||||
|
||||
fn normalized_vpn_portal(config: &NetworkConfig) -> anyhow::Result<Option<RuntimeVpnPortalConfig>> {
|
||||
Ok(config.gen_config()?.get_vpn_portal_config())
|
||||
}
|
||||
@@ -335,17 +332,16 @@ fn web_source_runtime_patch(
|
||||
diff_proxy_networks(¤t_proxy_networks, &desired_proxy_networks)?;
|
||||
}
|
||||
|
||||
let current_disable_relay_data = normalized_disable_relay_data(current)?;
|
||||
let desired_disable_relay_data = normalized_disable_relay_data(desired)?;
|
||||
if current_disable_relay_data != desired_disable_relay_data {
|
||||
patch.disable_relay_data = Some(desired_disable_relay_data);
|
||||
}
|
||||
|
||||
let current_prefer_peer_relay = normalized_prefer_peer_relay(current)?;
|
||||
let desired_prefer_peer_relay = normalized_prefer_peer_relay(desired)?;
|
||||
if current_prefer_peer_relay != desired_prefer_peer_relay {
|
||||
patch.prefer_peer_relay = Some(desired_prefer_peer_relay);
|
||||
}
|
||||
let current_flags = current.gen_config()?.get_flags();
|
||||
let desired_flags = desired.gen_config()?.get_flags();
|
||||
let mut flags = FlagsPatch {
|
||||
disable_relay_data: Some(desired_flags.disable_relay_data),
|
||||
prefer_peer_relay: Some(desired_flags.prefer_peer_relay),
|
||||
..Default::default()
|
||||
};
|
||||
flags.retain(¤t_flags);
|
||||
patch.disable_relay_data = flags.disable_relay_data;
|
||||
patch.prefer_peer_relay = flags.prefer_peer_relay;
|
||||
|
||||
match (
|
||||
normalized_vpn_portal(current)?,
|
||||
|
||||
@@ -136,6 +136,7 @@ once_cell.workspace = true
|
||||
|
||||
# for rpc
|
||||
prost.workspace = true
|
||||
prost-types.workspace = true
|
||||
anyhow.workspace = true
|
||||
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
|
||||
@@ -207,8 +207,8 @@ core_clap:
|
||||
en: "the url of the ipv6 listener, e.g.: tcp://[::]:11010, if not set, will listen on random udp port"
|
||||
zh-CN: "IPv6 监听器的URL,例如:tcp://[::]:11010,如果未设置,将在随机UDP端口上监听"
|
||||
compression:
|
||||
en: "compression algorithm to use, support none, zstd. default is none"
|
||||
zh-CN: "要使用的压缩算法,支持 none、zstd。默认为 none"
|
||||
en: "compression algorithm to use, support None, Zstd. default is None"
|
||||
zh-CN: "要使用的压缩算法,支持 None、Zstd。默认为 None"
|
||||
mapped_listeners:
|
||||
en: "manually specify the public address of the listener, other nodes can use this address to connect to this node. e.g.: tcp://123.123.123.123:11223, can specify multiple."
|
||||
zh-CN: "手动指定监听器的公网地址,其他节点可以使用该地址连接到本节点。例如:tcp://123.123.123.123:11223,可以指定多个。"
|
||||
@@ -218,6 +218,12 @@ core_clap:
|
||||
socket_mark:
|
||||
en: "Linux only: set SO_MARK (fwmark) on EasyTier's underlay sockets (TCP, UDP, QUIC, WebSocket, WireGuard, and the FakeTCP decoy socket) so the host can policy-route or filter them with 'ip rule fwmark ...', nftables ('meta mark'), or iptables ('-m mark'). Any value is applied verbatim (0 is a valid mark); omit the flag to leave SO_MARK untouched. Requires CAP_NET_ADMIN. Note: FakeTCP payload travels via raw TUN writes which the kernel does not tag — mark those separately on the TUN device if needed."
|
||||
zh-CN: "仅 Linux: 在 EasyTier 的底层套接字 (TCP、UDP、QUIC、WebSocket、WireGuard 以及 FakeTCP 诱饵套接字) 上设置 SO_MARK (fwmark),使主机能用 'ip rule fwmark ...'、nftables ('meta mark') 或 iptables ('-m mark') 策略路由/过滤这些数据包。任何值都会原样应用 (0 也是合法的 mark);不传该参数即保持 SO_MARK 不变。需要 CAP_NET_ADMIN 权限。注意:FakeTCP 的实际载荷通过原始 TUN 写入,内核不会为其打标记;如有需要请在 TUN 设备上单独打标记。"
|
||||
disable_relay_data:
|
||||
en: "if true, do not relay data packets for other peers, so this node carries no traffic it did not originate. default is false"
|
||||
zh-CN: "如果为true,则不转发其他对等节点的数据包,本节点不承载自身以外的流量。默认值为false"
|
||||
prefer_peer_relay:
|
||||
en: "prefer a credential peer that already relays to a destination over advertising another direct edge to the same destination"
|
||||
zh-CN: "优先通过已经转发到目标节点的凭证节点转发,而不是为同一目标再通告一条直连路径"
|
||||
enable_kcp_proxy:
|
||||
en: "proxy tcp streams with kcp, improving the latency and throughput on the network with udp packet loss."
|
||||
zh-CN: "使用 KCP 代理 TCP 流,提高在 UDP 丢包网络上的延迟和吞吐量。"
|
||||
|
||||
@@ -30,12 +30,6 @@ pub fn parse_mapped_listener_urls(
|
||||
.parse_urls(mapped_listeners)
|
||||
}
|
||||
|
||||
pub fn parse_encryption_algorithm(value: &str) -> Result<EncryptionAlgorithm, String> {
|
||||
value
|
||||
.parse()
|
||||
.map_err(|_| format!("'{value}' is not a valid encryption algorithm"))
|
||||
}
|
||||
|
||||
pub fn load_toml_config_from_path(path: &PathBuf) -> Result<TomlConfigLoader, anyhow::Error> {
|
||||
let config = std::fs::read_to_string(path)
|
||||
.with_context(|| format!("failed to read config file: {}", path.display()))?;
|
||||
|
||||
+260
-440
@@ -2,9 +2,9 @@ use crate::{
|
||||
ShellType,
|
||||
common::{
|
||||
config::{
|
||||
ConfigFileControl, ConfigLoader, ConsoleLoggerConfig, EncryptionAlgorithm,
|
||||
FileLoggerConfig, LoggingConfigLoader, NetworkIdentity, PeerConfig, PortForwardConfig,
|
||||
TomlConfigLoader, VpnPortalClientConfig, VpnPortalConfig, add_proxy_network_to_config,
|
||||
ConfigFileControl, ConfigLoader, ConsoleLoggerConfig, FileLoggerConfig,
|
||||
LoggingConfigLoader, NetworkIdentity, PeerConfig, PortForwardConfig, TomlConfigLoader,
|
||||
VpnPortalClientConfig, VpnPortalConfig, add_proxy_network_to_config,
|
||||
load_config_from_file, load_toml_config_from_path,
|
||||
load_toml_config_from_str_with_source, parse_mapped_listener_urls,
|
||||
},
|
||||
@@ -12,17 +12,20 @@ use crate::{
|
||||
log,
|
||||
},
|
||||
instance::factory::native_cli_instance_manager,
|
||||
proto::common::{CompressionAlgoPb, SecureModeConfig},
|
||||
proto::common::{CompressionAlgoPb, Flags, FlagsPatch, SecureModeConfig},
|
||||
rpc_service::ApiRpcServer,
|
||||
utils::panic::setup_panic_handler,
|
||||
web_client,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use cidr::IpCidr;
|
||||
use clap::{CommandFactory, Parser};
|
||||
use easytier_core::config::normalize_secure_mode_config;
|
||||
use clap::{Arg, ArgMatches, CommandFactory, FromArgMatches, Parser};
|
||||
use easytier_core::config::{EncryptionAlgorithm, normalize_secure_mode_config};
|
||||
use guarden::defer;
|
||||
use prost_types::field_descriptor_proto::Type;
|
||||
use rust_i18n::t;
|
||||
use serde_json::{Value, json};
|
||||
use std::str::{FromStr, ParseBoolError};
|
||||
use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
path::PathBuf,
|
||||
@@ -35,6 +38,7 @@ use tokio::io::AsyncReadExt;
|
||||
use crate::tunnel::IpScheme;
|
||||
#[cfg(feature = "jemalloc-prof")]
|
||||
use jemalloc_ctl::{Access as _, AsName as _, epoch, stats};
|
||||
use serde::Serialize;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
windows_service::define_windows_service!(ffi_service_main, win_service_main);
|
||||
@@ -149,6 +153,10 @@ struct Cli {
|
||||
|
||||
#[derive(Parser, Debug, Default, PartialEq, Eq)]
|
||||
struct NetworkOptions {
|
||||
/// Filled in from the flag arguments the schema implies.
|
||||
#[arg(skip)]
|
||||
flags: FlagsPatch,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_NETWORK_NAME",
|
||||
@@ -312,79 +320,6 @@ struct NetworkOptions {
|
||||
)]
|
||||
vpn_portal_client_groups: Vec<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DEFAULT_PROTOCOL",
|
||||
help = t!("core_clap.default_protocol").to_string()
|
||||
)]
|
||||
default_protocol: Option<String>,
|
||||
|
||||
#[arg(
|
||||
short = 'u',
|
||||
long,
|
||||
env = "ET_DISABLE_ENCRYPTION",
|
||||
help = t!("core_clap.disable_encryption").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
disable_encryption: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_ENCRYPTION_ALGORITHM",
|
||||
help = t!("core_clap.encryption_algorithm").to_string(),
|
||||
value_parser = crate::common::config::parse_encryption_algorithm,
|
||||
)]
|
||||
encryption_algorithm: Option<EncryptionAlgorithm>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_MULTI_THREAD",
|
||||
help = t!("core_clap.multi_thread").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
multi_thread: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_MULTI_THREAD_COUNT",
|
||||
help = t!("core_clap.multi_thread_count").to_string(),
|
||||
)]
|
||||
multi_thread_count: Option<u32>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DISABLE_IPV6",
|
||||
help = t!("core_clap.disable_ipv6").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
disable_ipv6: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DEV_NAME",
|
||||
help = t!("core_clap.dev_name").to_string()
|
||||
)]
|
||||
dev_name: Option<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_MTU",
|
||||
help = t!("core_clap.mtu").to_string()
|
||||
)]
|
||||
mtu: Option<u16>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_LATENCY_FIRST",
|
||||
help = t!("core_clap.latency_first").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
latency_first: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_EXIT_NODES",
|
||||
@@ -394,42 +329,6 @@ struct NetworkOptions {
|
||||
)]
|
||||
exit_nodes: Vec<IpAddr>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_ENABLE_EXIT_NODE",
|
||||
help = t!("core_clap.enable_exit_node").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
enable_exit_node: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_PROXY_FORWARD_BY_SYSTEM",
|
||||
help = t!("core_clap.proxy_forward_by_system").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
proxy_forward_by_system: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_NO_TUN",
|
||||
help = t!("core_clap.no_tun").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
no_tun: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_USE_SMOLTCP",
|
||||
help = t!("core_clap.use_smoltcp").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
use_smoltcp: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_MANUAL_ROUTES",
|
||||
@@ -439,108 +338,6 @@ struct NetworkOptions {
|
||||
)]
|
||||
manual_routes: Option<Vec<String>>,
|
||||
|
||||
// if not in relay_network_whitelist:
|
||||
// for foreign virtual network, will refuse the incoming connection
|
||||
// for local virtual network, will refuse to relay tun packets
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_RELAY_NETWORK_WHITELIST",
|
||||
value_delimiter = ',',
|
||||
help = t!("core_clap.relay_network_whitelist").to_string(),
|
||||
num_args = 0..
|
||||
)]
|
||||
relay_network_whitelist: Option<Vec<String>>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_P2P_ONLY",
|
||||
help = t!("core_clap.p2p_only").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
p2p_only: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_LAZY_P2P",
|
||||
help = t!("core_clap.lazy_p2p").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
lazy_p2p: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DISABLE_P2P",
|
||||
help = t!("core_clap.disable_p2p").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
disable_p2p: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DISABLE_UDP_HOLE_PUNCHING",
|
||||
help = t!("core_clap.disable_udp_hole_punching").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
disable_udp_hole_punching: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DISABLE_TCP_HOLE_PUNCHING",
|
||||
help = t!("core_clap.disable_tcp_hole_punching").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
disable_tcp_hole_punching: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DISABLE_SYM_HOLE_PUNCHING",
|
||||
help = t!("core_clap.disable_sym_hole_punching").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
disable_sym_hole_punching: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DISABLE_UPNP",
|
||||
help = t!("core_clap.disable_upnp").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
disable_upnp: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_ENABLE_UDP_BROADCAST_RELAY",
|
||||
help = t!("core_clap.enable_udp_broadcast_relay").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
enable_udp_broadcast_relay: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_RELAY_ALL_PEER_RPC",
|
||||
help = t!("core_clap.relay_all_peer_rpc").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
relay_all_peer_rpc: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_NEED_P2P",
|
||||
help = t!("core_clap.need_p2p").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
need_p2p: Option<bool>,
|
||||
|
||||
#[cfg(feature = "socks5")]
|
||||
#[arg(
|
||||
long,
|
||||
@@ -549,67 +346,6 @@ struct NetworkOptions {
|
||||
)]
|
||||
socks5: Option<u16>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_COMPRESSION",
|
||||
help = t!("core_clap.compression").to_string(),
|
||||
)]
|
||||
compression: Option<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_BIND_DEVICE",
|
||||
help = t!("core_clap.bind_device").to_string()
|
||||
)]
|
||||
bind_device: Option<bool>,
|
||||
|
||||
// SO_MARK (fwmark) is a Linux-family kernel feature. Gate the flag out
|
||||
// entirely on other targets so users on Windows/macOS/BSD don't see a
|
||||
// `--socket-mark` they can't act on.
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_SOCKET_MARK",
|
||||
help = t!("core_clap.socket_mark").to_string()
|
||||
)]
|
||||
socket_mark: Option<u32>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_ENABLE_KCP_PROXY",
|
||||
help = t!("core_clap.enable_kcp_proxy").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
enable_kcp_proxy: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DISABLE_KCP_INPUT",
|
||||
help = t!("core_clap.disable_kcp_input").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
disable_kcp_input: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_ENABLE_QUIC_PROXY",
|
||||
help = t!("core_clap.enable_quic_proxy").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
enable_quic_proxy: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DISABLE_QUIC_INPUT",
|
||||
help = t!("core_clap.disable_quic_input").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
disable_quic_input: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_PORT_FORWARD",
|
||||
@@ -619,40 +355,6 @@ struct NetworkOptions {
|
||||
)]
|
||||
port_forward: Vec<url::Url>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_ACCEPT_DNS",
|
||||
help = t!("core_clap.accept_dns").to_string(),
|
||||
)]
|
||||
accept_dns: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long = "tld-dns-zone",
|
||||
env = "ET_TLD_DNS_ZONE",
|
||||
help = t!("core_clap.tld_dns_zone").to_string())]
|
||||
tld_dns_zone: Option<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_PRIVATE_MODE",
|
||||
help = t!("core_clap.private_mode").to_string(),
|
||||
)]
|
||||
private_mode: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_FOREIGN_RELAY_BPS_LIMIT",
|
||||
help = t!("core_clap.foreign_relay_bps_limit").to_string(),
|
||||
)]
|
||||
foreign_relay_bps_limit: Option<u64>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_INSTANCE_RECV_BPS_LIMIT",
|
||||
help = t!("core_clap.instance_recv_bps_limit").to_string(),
|
||||
)]
|
||||
instance_recv_bps_limit: Option<u64>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
@@ -669,42 +371,6 @@ struct NetworkOptions {
|
||||
)]
|
||||
udp_whitelist: Vec<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DISABLE_RELAY_KCP",
|
||||
help = t!("core_clap.disable_relay_kcp").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
disable_relay_kcp: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_DISABLE_RELAY_QUIC",
|
||||
help = t!("core_clap.disable_relay_quic").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
disable_relay_quic: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_ENABLE_RELAY_FOREIGN_NETWORK_KCP",
|
||||
help = t!("core_clap.enable_relay_foreign_network_kcp").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
enable_relay_foreign_network_kcp: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_ENABLE_RELAY_FOREIGN_NETWORK_QUIC",
|
||||
help = t!("core_clap.enable_relay_foreign_network_quic").to_string(),
|
||||
num_args = 0..=1,
|
||||
default_missing_value = "true"
|
||||
)]
|
||||
enable_relay_foreign_network_quic: Option<bool>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_STUN_SERVERS",
|
||||
@@ -1237,96 +903,12 @@ impl NetworkOptions {
|
||||
cfg.set_secure_mode(Some(normalize_secure_mode_config(c)?));
|
||||
}
|
||||
|
||||
let mut f = cfg.get_flags();
|
||||
if let Some(default_protocol) = &self.default_protocol {
|
||||
f.default_protocol = default_protocol.clone()
|
||||
};
|
||||
if let Some(v) = self.disable_encryption {
|
||||
f.enable_encryption = !v;
|
||||
}
|
||||
if let Some(algorithm) = &self.encryption_algorithm {
|
||||
f.encryption_algorithm = algorithm.to_string();
|
||||
}
|
||||
if let Some(v) = self.disable_ipv6 {
|
||||
f.enable_ipv6 = !v;
|
||||
}
|
||||
f.latency_first = self.latency_first.unwrap_or(f.latency_first);
|
||||
if let Some(dev_name) = &self.dev_name {
|
||||
f.dev_name = dev_name.clone()
|
||||
}
|
||||
if let Some(mtu) = self.mtu {
|
||||
f.mtu = mtu as u32;
|
||||
}
|
||||
f.enable_exit_node = self.enable_exit_node.unwrap_or(f.enable_exit_node);
|
||||
f.proxy_forward_by_system = self
|
||||
.proxy_forward_by_system
|
||||
.unwrap_or(f.proxy_forward_by_system);
|
||||
f.no_tun = self.no_tun.unwrap_or(f.no_tun) || cfg!(not(feature = "tun"));
|
||||
f.use_smoltcp = self.use_smoltcp.unwrap_or(f.use_smoltcp);
|
||||
if let Some(wl) = self.relay_network_whitelist.as_ref() {
|
||||
f.relay_network_whitelist = wl.join(" ");
|
||||
}
|
||||
f.disable_p2p = self.disable_p2p.unwrap_or(f.disable_p2p);
|
||||
f.p2p_only = self.p2p_only.unwrap_or(f.p2p_only);
|
||||
f.lazy_p2p = self.lazy_p2p.unwrap_or(f.lazy_p2p);
|
||||
f.disable_tcp_hole_punching = self
|
||||
.disable_tcp_hole_punching
|
||||
.unwrap_or(f.disable_tcp_hole_punching);
|
||||
f.disable_udp_hole_punching = self
|
||||
.disable_udp_hole_punching
|
||||
.unwrap_or(f.disable_udp_hole_punching);
|
||||
f.relay_all_peer_rpc = self.relay_all_peer_rpc.unwrap_or(f.relay_all_peer_rpc);
|
||||
f.need_p2p = self.need_p2p.unwrap_or(f.need_p2p);
|
||||
f.multi_thread = self.multi_thread.unwrap_or(f.multi_thread);
|
||||
if let Some(compression) = &self.compression {
|
||||
f.data_compress_algo = match compression.as_str() {
|
||||
"none" => CompressionAlgoPb::None,
|
||||
"zstd" => CompressionAlgoPb::Zstd,
|
||||
_ => panic!(
|
||||
"unknown compression algorithm: {}, supported: none, zstd",
|
||||
compression
|
||||
),
|
||||
}
|
||||
.into();
|
||||
}
|
||||
f.bind_device = self.bind_device.unwrap_or(f.bind_device);
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
{
|
||||
f.socket_mark = self.socket_mark.or(f.socket_mark);
|
||||
}
|
||||
f.enable_kcp_proxy = self.enable_kcp_proxy.unwrap_or(f.enable_kcp_proxy);
|
||||
f.disable_kcp_input = self.disable_kcp_input.unwrap_or(f.disable_kcp_input);
|
||||
f.enable_quic_proxy = self.enable_quic_proxy.unwrap_or(f.enable_quic_proxy);
|
||||
f.disable_quic_input = self.disable_quic_input.unwrap_or(f.disable_quic_input);
|
||||
f.accept_dns = self.accept_dns.unwrap_or(f.accept_dns);
|
||||
f.private_mode = self.private_mode.unwrap_or(f.private_mode);
|
||||
f.foreign_relay_bps_limit = self
|
||||
.foreign_relay_bps_limit
|
||||
.unwrap_or(f.foreign_relay_bps_limit);
|
||||
f.instance_recv_bps_limit = self
|
||||
.instance_recv_bps_limit
|
||||
.unwrap_or(f.instance_recv_bps_limit);
|
||||
f.multi_thread_count = self.multi_thread_count.unwrap_or(f.multi_thread_count);
|
||||
f.disable_relay_kcp = self.disable_relay_kcp.unwrap_or(f.disable_relay_kcp);
|
||||
f.disable_relay_quic = self.disable_relay_quic.unwrap_or(f.disable_relay_quic);
|
||||
f.enable_relay_foreign_network_kcp = self
|
||||
.enable_relay_foreign_network_kcp
|
||||
.unwrap_or(f.enable_relay_foreign_network_kcp);
|
||||
f.enable_relay_foreign_network_quic = self
|
||||
.enable_relay_foreign_network_quic
|
||||
.unwrap_or(f.enable_relay_foreign_network_quic);
|
||||
f.disable_sym_hole_punching = self
|
||||
.disable_sym_hole_punching
|
||||
.unwrap_or(f.disable_sym_hole_punching);
|
||||
f.disable_upnp = self.disable_upnp.unwrap_or(f.disable_upnp);
|
||||
f.enable_udp_broadcast_relay = self
|
||||
.enable_udp_broadcast_relay
|
||||
.unwrap_or(f.enable_udp_broadcast_relay);
|
||||
// Configure tld_dns_zone: use provided value if set
|
||||
if let Some(tld_dns_zone) = &self.tld_dns_zone {
|
||||
f.tld_dns_zone = tld_dns_zone.clone();
|
||||
}
|
||||
cfg.set_flags(f);
|
||||
cfg.patch_flags(self.flags.clone());
|
||||
#[cfg(not(feature = "tun"))]
|
||||
cfg.patch_flags(FlagsPatch {
|
||||
no_tun: Some(true),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
if !self.exit_nodes.is_empty() {
|
||||
cfg.set_exit_nodes(self.exit_nodes.clone());
|
||||
@@ -1471,8 +1053,96 @@ fn win_service_event_loop(
|
||||
});
|
||||
}
|
||||
|
||||
fn flags_from(matches: &ArgMatches) -> Result<FlagsPatch, clap::Error> {
|
||||
let given = Flags::flags()
|
||||
.iter()
|
||||
.filter_map(|field| {
|
||||
let name = field.name();
|
||||
let value = if let Ok(Some(values)) = matches.try_get_many::<String>(name) {
|
||||
json!(values.map(|s| s.as_str()).collect::<Vec<_>>().join(" "))
|
||||
} else if let Ok(Some(value)) = matches.try_get_one::<Value>(name) {
|
||||
value.clone()
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some((name.to_owned(), value))
|
||||
})
|
||||
.collect::<serde_json::Map<_, _>>();
|
||||
serde_json::from_value(Value::Object(given)).map_err(|error| {
|
||||
clap::Error::raw(
|
||||
clap::error::ErrorKind::ValueValidation,
|
||||
format!("{error:#}"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn cli_command() -> clap::Command {
|
||||
fn arg(flag: &str, spelling: &str) -> Arg {
|
||||
let name = spelling.to_uppercase();
|
||||
Arg::new(flag.to_owned())
|
||||
.long(spelling.replace('_', "-"))
|
||||
.env(format!("ET_{name}"))
|
||||
.value_name(name)
|
||||
.help(t!(format!("core_clap.{spelling}")).to_string())
|
||||
}
|
||||
|
||||
fn parsed<T: FromStr + Serialize>(value: &str) -> Result<Value, T::Err> {
|
||||
value.parse::<T>().map(|value| json!(value))
|
||||
}
|
||||
|
||||
fn negated(value: &str) -> Result<Value, ParseBoolError> {
|
||||
value.parse::<bool>().map(|value| json!(!value))
|
||||
}
|
||||
|
||||
Cli::command().args(Flags::flags().iter().map(|field| {
|
||||
let name = field.name();
|
||||
match name {
|
||||
// The two the command line states in the negative, which can be given
|
||||
// bare like any other boolean flag.
|
||||
"enable_encryption" => arg(name, "disable_encryption")
|
||||
.short('u')
|
||||
.num_args(0..=1)
|
||||
.default_missing_value("true")
|
||||
.value_parser(negated),
|
||||
"enable_ipv6" => arg(name, "disable_ipv6")
|
||||
.num_args(0..=1)
|
||||
.default_missing_value("true")
|
||||
.value_parser(negated),
|
||||
// The command line names the algorithm instead of spelling the field.
|
||||
// The values are the schema's own, which the patch reads.
|
||||
"data_compress_algo" => {
|
||||
arg(name, "compression").value_parser(parsed::<CompressionAlgoPb>)
|
||||
}
|
||||
"encryption_algorithm" => arg(name, name).value_parser(parsed::<EncryptionAlgorithm>),
|
||||
"mtu" => arg(name, name).value_parser(parsed::<u16>),
|
||||
"relay_network_whitelist" => arg(name, name)
|
||||
.value_delimiter(',')
|
||||
.num_args(0..)
|
||||
.action(clap::ArgAction::Append),
|
||||
_ => {
|
||||
let arg = arg(name, name);
|
||||
match field.r#type() {
|
||||
// A boolean flag can be given bare, and bare means enabled.
|
||||
Type::Bool => arg
|
||||
.num_args(0..=1)
|
||||
.default_missing_value("true")
|
||||
.value_parser(parsed::<bool>),
|
||||
Type::Uint32 => arg.value_parser(parsed::<u32>),
|
||||
Type::Uint64 => arg.value_parser(parsed::<u64>),
|
||||
// An enum flag takes the name the schema spells, and the patch's
|
||||
// own serde rejects anything the enum does not declare.
|
||||
Type::String | Type::Enum => arg.value_parser(parsed::<String>),
|
||||
other => panic!("{name}: no command line for {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_cli() -> Cli {
|
||||
let mut cli = Cli::parse();
|
||||
let matches = cli_command().get_matches();
|
||||
let mut cli = Cli::from_arg_matches(&matches).unwrap_or_else(|error| error.exit());
|
||||
cli.network_options.flags = flags_from(&matches).unwrap_or_else(|error| error.exit());
|
||||
// for --stun-servers="", we want vec![], but clap will give vec![""], hack for that
|
||||
if let Some(stun_servers) = &mut cli.network_options.stun_servers {
|
||||
stun_servers.retain(|s| !s.trim().is_empty());
|
||||
@@ -1778,7 +1448,7 @@ pub async fn main() -> ExitCode {
|
||||
let cli = parse_cli();
|
||||
|
||||
if let Some(shell) = cli.gen_autocomplete {
|
||||
let mut cmd = Cli::command();
|
||||
let mut cmd = cli_command();
|
||||
if let Some(shell) = shell.to_shell() {
|
||||
crate::print_completions(shell, &mut cmd, "easytier-core");
|
||||
} else {
|
||||
@@ -1841,6 +1511,12 @@ async fn validate_config(cli: &Cli) -> anyhow::Result<()> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::proto::common::CompressionAlgoPb;
|
||||
|
||||
/// Parses the flags the way the binary does.
|
||||
fn parse_flags(argv: &[&str]) -> Result<FlagsPatch, clap::Error> {
|
||||
flags_from(&cli_command().try_get_matches_from(argv)?)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_listeners() {
|
||||
@@ -1954,6 +1630,150 @@ enabled = true
|
||||
assert_eq!(cfg.get_hostname(), "override-host");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_flags_from_schema_preserves_presence() {
|
||||
assert_eq!(parse_flags(&["easytier"]).unwrap(), FlagsPatch::default());
|
||||
let patch = parse_flags(&[
|
||||
"easytier",
|
||||
"--disable-encryption",
|
||||
"false",
|
||||
"--latency-first",
|
||||
"false",
|
||||
"--mtu",
|
||||
"0",
|
||||
"--compression",
|
||||
"Zstd",
|
||||
"--socket-mark",
|
||||
"42",
|
||||
"--disable-relay-data",
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(patch.enable_encryption, Some(true));
|
||||
assert_eq!(patch.latency_first, Some(false));
|
||||
assert_eq!(patch.mtu, Some(0));
|
||||
assert_eq!(
|
||||
patch.data_compress_algo,
|
||||
Some(CompressionAlgoPb::Zstd as i32)
|
||||
);
|
||||
assert_eq!(patch.socket_mark, Some(42));
|
||||
assert_eq!(patch.disable_relay_data, Some(true));
|
||||
assert_eq!(patch.enable_ipv6, None);
|
||||
|
||||
// A negated flag can be given bare, and bare means the value it states.
|
||||
let patch = parse_flags(&["easytier", "--disable-ipv6"]).unwrap();
|
||||
assert_eq!(patch.enable_ipv6, Some(false));
|
||||
|
||||
// Compression tests: case-insensitive, accepts None/none/zstd/Zstd/ZSTD, rejects invalid
|
||||
let patch = parse_flags(&["easytier", "--compression", "none"]).unwrap();
|
||||
assert_eq!(
|
||||
patch.data_compress_algo,
|
||||
Some(CompressionAlgoPb::None as i32)
|
||||
);
|
||||
let patch = parse_flags(&["easytier", "--compression", "zstd"]).unwrap();
|
||||
assert_eq!(
|
||||
patch.data_compress_algo,
|
||||
Some(CompressionAlgoPb::Zstd as i32)
|
||||
);
|
||||
let patch = parse_flags(&["easytier", "--compression", "ZSTD"]).unwrap();
|
||||
assert_eq!(
|
||||
patch.data_compress_algo,
|
||||
Some(CompressionAlgoPb::Zstd as i32)
|
||||
);
|
||||
assert!(parse_flags(&["easytier", "--compression", "invalid"]).is_err());
|
||||
assert!(parse_flags(&["easytier", "--compression", "lz4"]).is_err());
|
||||
|
||||
// Encryption algorithm tests: case-insensitive, aliases, rejects invalid
|
||||
let patch = parse_flags(&["easytier", "--encryption-algorithm", "aes-gcm"]).unwrap();
|
||||
assert_eq!(patch.encryption_algorithm, Some("aes-gcm".to_string()));
|
||||
let patch = parse_flags(&["easytier", "--encryption-algorithm", "AES-GCM"]).unwrap();
|
||||
assert_eq!(patch.encryption_algorithm, Some("aes-gcm".to_string()));
|
||||
let patch =
|
||||
parse_flags(&["easytier", "--encryption-algorithm", "openssl-aes-gcm"]).unwrap();
|
||||
assert_eq!(patch.encryption_algorithm, Some("aes-gcm".to_string()));
|
||||
let patch =
|
||||
parse_flags(&["easytier", "--encryption-algorithm", "chacha20-poly1305"]).unwrap();
|
||||
assert_eq!(patch.encryption_algorithm, Some("chacha20".to_string()));
|
||||
let patch = parse_flags(&["easytier", "--encryption-algorithm", "xor"]).unwrap();
|
||||
assert_eq!(patch.encryption_algorithm, Some("xor".to_string()));
|
||||
assert!(parse_flags(&["easytier", "--encryption-algorithm", "rot13"]).is_err());
|
||||
assert!(parse_flags(&["easytier", "--encryption-algorithm", "des"]).is_err());
|
||||
|
||||
// Deprecated flag is not accepted.
|
||||
assert!(parse_flags(&["easytier", "--quic-listen-port", "1234"]).is_err());
|
||||
|
||||
// MTU tests: u16 range validation
|
||||
let patch = parse_flags(&["easytier", "--mtu", "1400"]).unwrap();
|
||||
assert_eq!(patch.mtu, Some(1400));
|
||||
let patch = parse_flags(&["easytier", "--mtu", "65535"]).unwrap();
|
||||
assert_eq!(patch.mtu, Some(65535));
|
||||
assert!(parse_flags(&["easytier", "--mtu", "65536"]).is_err());
|
||||
assert!(parse_flags(&["easytier", "--mtu", "70000"]).is_err());
|
||||
|
||||
// Relay network whitelist tests: comma, multiple values, bare clearing
|
||||
let patch = parse_flags(&["easytier", "--relay-network-whitelist", "net1,net2"]).unwrap();
|
||||
assert_eq!(patch.relay_network_whitelist, Some("net1 net2".to_string()));
|
||||
let patch =
|
||||
parse_flags(&["easytier", "--relay-network-whitelist", "net1", "net2"]).unwrap();
|
||||
assert_eq!(patch.relay_network_whitelist, Some("net1 net2".to_string()));
|
||||
let patch = parse_flags(&[
|
||||
"easytier",
|
||||
"--relay-network-whitelist",
|
||||
"net1",
|
||||
"--relay-network-whitelist",
|
||||
"net2",
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(patch.relay_network_whitelist, Some("net1 net2".to_string()));
|
||||
let patch = parse_flags(&["easytier", "--relay-network-whitelist"]).unwrap();
|
||||
assert_eq!(patch.relay_network_whitelist, Some("".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_network_options_patch_preserves_omitted_flags_and_applies_zero_values() {
|
||||
let cfg = TomlConfigLoader::new_from_str(
|
||||
r#"[flags]
|
||||
default_protocol = "udp"
|
||||
latency_first = true
|
||||
mtu = 1400
|
||||
socket_mark = 42
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
NetworkOptions::default().merge_into(&cfg).unwrap();
|
||||
assert_eq!(cfg.get_flags().socket_mark, Some(42));
|
||||
assert_eq!(cfg.get_flags_patch().enable_encryption, None);
|
||||
assert_eq!(cfg.get_flags_patch().multi_thread, None);
|
||||
|
||||
NetworkOptions {
|
||||
flags: FlagsPatch {
|
||||
latency_first: Some(false),
|
||||
mtu: Some(0),
|
||||
relay_network_whitelist: Some(String::new()),
|
||||
enable_encryption: Some(false),
|
||||
data_compress_algo: Some(CompressionAlgoPb::Zstd.into()),
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
socket_mark: Some(0),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
.merge_into(&cfg)
|
||||
.unwrap();
|
||||
|
||||
let flags = cfg.get_flags();
|
||||
assert_eq!(flags.default_protocol, "udp");
|
||||
assert!(!flags.latency_first);
|
||||
assert_eq!(flags.mtu, 0);
|
||||
assert_eq!(flags.relay_network_whitelist, "");
|
||||
assert!(!flags.enable_encryption);
|
||||
assert_eq!(flags.data_compress_algo, CompressionAlgoPb::Zstd as i32);
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
assert_eq!(flags.socket_mark, Some(0));
|
||||
#[cfg(not(feature = "tun"))]
|
||||
assert!(flags.no_tun);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secure_mode_cli_flag_preserves_config_file_keypair() {
|
||||
use base64::{Engine as _, prelude::BASE64_STANDARD};
|
||||
|
||||
Reference in new issue
Block a user