refactor(web): use generated proto network types (#2373)

* refactor(web): use generated proto network types
* fix(core): preserve dumped config flags
* test(web): cover config flag save paths
* fix(ci): use system protoc before frontend codegen
* fix(ci): serialize frontend-lib builds
This commit is contained in:
KKRainbow
2026-06-27 13:09:28 +08:00
committed by GitHub
parent 034f5066cd
commit f0d00d6161
22 changed files with 5035 additions and 247 deletions
+93 -20
View File
@@ -10,6 +10,7 @@ use ariadne::{CharSet, Config as AriadneConfig, IndexType, Label, Report, Report
use base64::{Engine as _, prelude::BASE64_STANDARD};
use clap::ValueEnum;
use clap::builder::PossibleValue;
use prost_reflect::{DynamicMessage, ReflectMessage, SerializeOptions};
use serde::{Deserialize, Serialize};
use strum::{Display, EnumString, VariantArray};
use tokio::io::AsyncReadExt as _;
@@ -78,6 +79,54 @@ pub fn gen_default_flags() -> Flags {
}
}
fn flags_to_dynamic_message(flags: &Flags) -> DynamicMessage {
let mut message = DynamicMessage::new(flags.descriptor());
message
.transcode_from(flags)
.expect("FlagsInConfig should transcode to DynamicMessage");
message
}
fn flags_to_full_json_map(flags: &DynamicMessage) -> serde_json::Map<String, serde_json::Value> {
let options = SerializeOptions::new()
.use_proto_field_name(true)
.skip_default_fields(false);
match flags
.serialize_with_options(serde_json::value::Serializer, &options)
.expect("FlagsInConfig should serialize to JSON")
{
serde_json::Value::Object(map) => map,
_ => unreachable!("FlagsInConfig should serialize to a JSON object"),
}
}
fn flags_diff_from_default(flags: &Flags) -> serde_json::Map<String, serde_json::Value> {
let default_flags = gen_default_flags();
let default_message = flags_to_dynamic_message(&default_flags);
let current_message = flags_to_dynamic_message(flags);
let default_map = flags_to_full_json_map(&default_message);
let current_map = flags_to_full_json_map(&current_message);
current_message
.descriptor()
.fields()
.filter_map(|field| {
let key = field.name();
let value_changed = default_map.get(key) != current_map.get(key);
let presence_changed =
default_message.has_field(&field) != current_message.has_field(&field);
if value_changed || presence_changed {
current_map
.get(key)
.map(|value| (key.to_string(), value.clone()))
} else {
None
}
})
.collect()
}
fn mapped_listener_allows_implicit_port(url: &url::Url) -> bool {
TunnelScheme::try_from(url)
.ok()
@@ -1093,28 +1142,9 @@ impl ConfigLoader for TomlConfigLoader {
}
fn dump(&self) -> String {
let default_flags_json = serde_json::to_string(&gen_default_flags()).unwrap();
let default_flags_hashmap =
serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(&default_flags_json)
.unwrap();
let cur_flags_json = serde_json::to_string(&self.get_flags()).unwrap();
let cur_flags_hashmap =
serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(&cur_flags_json)
.unwrap();
let mut flag_map: serde_json::Map<String, serde_json::Value> = Default::default();
for (key, value) in default_flags_hashmap {
if let Some(v) = cur_flags_hashmap.get(&key)
&& *v != value
{
flag_map.insert(key, v.clone());
}
}
let mut config = self.config.lock().unwrap().clone();
Self::normalize_config_source(&mut config);
config.flags = Some(flag_map);
config.flags = Some(flags_diff_from_default(&self.get_flags()));
if config.stun_servers == Some(StunInfoCollector::get_default_servers()) {
config.stun_servers = None;
}
@@ -1460,6 +1490,49 @@ socket_mark = 66
assert_eq!(cfg.get_flags().socket_mark, None);
}
#[test]
fn dump_preserves_flags_that_differ_from_easytier_defaults() {
let cfg = TomlConfigLoader::default();
let mut flags = gen_default_flags();
flags.dev_name = "et_test".to_string();
flags.enable_quic_proxy = true;
flags.disable_tcp_hole_punching = true;
flags.disable_sym_hole_punching = true;
flags.multi_thread = false;
flags.bind_device = false;
flags.enable_ipv6 = false;
flags.relay_network_whitelist = "".to_string();
flags.mtu = 0;
flags.socket_mark = Some(0);
cfg.set_flags(flags);
let dumped = cfg.dump();
assert!(dumped.contains("dev_name = \"et_test\""));
assert!(dumped.contains("enable_quic_proxy = true"));
assert!(dumped.contains("disable_tcp_hole_punching = true"));
assert!(dumped.contains("disable_sym_hole_punching = true"));
assert!(dumped.contains("multi_thread = false"));
assert!(dumped.contains("bind_device = false"));
assert!(dumped.contains("enable_ipv6 = false"));
assert!(dumped.contains("relay_network_whitelist = \"\""));
assert!(dumped.contains("mtu = 0"));
assert!(dumped.contains("socket_mark = 0"));
let reloaded = TomlConfigLoader::new_from_str(&dumped).unwrap();
let reloaded_flags = reloaded.get_flags();
assert_eq!(reloaded_flags.dev_name, "et_test");
assert!(reloaded_flags.enable_quic_proxy);
assert!(reloaded_flags.disable_tcp_hole_punching);
assert!(reloaded_flags.disable_sym_hole_punching);
assert!(!reloaded_flags.multi_thread);
assert!(!reloaded_flags.bind_device);
assert!(!reloaded_flags.enable_ipv6);
assert_eq!(reloaded_flags.relay_network_whitelist, "");
assert_eq!(reloaded_flags.mtu, 0);
assert_eq!(reloaded_flags.socket_mark, Some(0));
}
#[test]
fn test_stun_servers_config() {
let config = TomlConfigLoader::default();
+26
View File
@@ -1210,6 +1210,32 @@ mod tests {
Ok(())
}
#[test]
fn network_config_dump_preserves_web_flags() -> Result<(), anyhow::Error> {
let network_config = super::NetworkConfig {
instance_id: Some(uuid::Uuid::new_v4().to_string()),
dhcp: Some(true),
network_name: Some("demo".to_string()),
network_secret: Some("secret".to_string()),
networking_method: Some(crate::proto::api::manage::NetworkingMethod::Manual as i32),
peer_urls: vec!["tcp://1.2.3.4:11010".to_string()],
listener_urls: vec!["tcp://0.0.0.0:11010".to_string()],
dev_name: Some("et_test".to_string()),
enable_quic_proxy: Some(true),
disable_tcp_hole_punching: Some(true),
disable_sym_hole_punching: Some(true),
..Default::default()
};
let dumped = network_config.gen_config()?.dump();
assert!(dumped.contains("dev_name = \"et_test\""));
assert!(dumped.contains("enable_quic_proxy = true"));
assert!(dumped.contains("disable_tcp_hole_punching = true"));
assert!(dumped.contains("disable_sym_hole_punching = true"));
Ok(())
}
#[test]
fn test_network_config_conversion_random() -> Result<(), anyhow::Error> {
let mut rng = rand::thread_rng();