diff --git a/Cargo.lock b/Cargo.lock index 3e77d58c..69e685aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2675,6 +2675,7 @@ dependencies = [ "getrandom 0.2.17", "getrandom 0.3.4", "guarden", + "hickory-proto", "hmac 0.12.1", "http-body-util", "hyper", @@ -5323,6 +5324,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" +[[package]] +name = "lzma-rust2" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b" +dependencies = [ + "sha2 0.11.0", +] + [[package]] name = "lzokay-native" version = "0.2.0" @@ -5333,15 +5343,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "lzma-rust2" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b" -dependencies = [ - "sha2 0.11.0", -] - [[package]] name = "mac_address" version = "1.1.8" diff --git a/docs/magic-dns-records.md b/docs/magic-dns-records.md new file mode 100644 index 00000000..3f2296c1 --- /dev/null +++ b/docs/magic-dns-records.md @@ -0,0 +1,94 @@ +# Local Magic DNS records + +Configure records through a TOML file. Enable Magic DNS as before and add +static records using DNS zone file syntax: + +```toml +[flags] +accept_dns = true + +[dns] +domain = "et.net." + +[[dns.zone]] +origin = "et.net." +ttl = 60 +records = [ + "nas IN A 10.0.0.8", + "nas IN AAAA fd00::8", + "files IN CNAME nas", + 'info IN TXT "local records"', +] + +[[dns.zone]] +origin = "example.test." +records = ["service 120 IN A 10.0.0.9"] +``` + +`domain` under `[dns]` sets the default Magic DNS domain, defaulting to `"et.net."` +when omitted, and supersedes the deprecated `flags.tld_dns_zone`. + +`origin` must be a non-root DNS name. Record names are relative to it; +absolute names must belong to that zone. +`ttl` supplies the default TTL in seconds, or zero when omitted. Individual +records may specify their own TTL. Zone file directives such as `$INCLUDE` are +not accepted. + +Static records come from the local configuration of the EasyTier instance that +wins the local Magic DNS server election. They coexist with automatically +generated node records and survive route updates. Configuration changes take +effect when the instance restarts. If the elected instance has no `[dns]` +configuration, no static records are served, even when another local instance +has some. Records are not distributed to other machines. + +Only the elected instance's configured zones are added to the system resolver +match list, after the default zone. Zones configured by other local instances +are neither served nor used for system name resolution. + +Configured zones are authoritative: a name missing from a configured zone does +not fall back to system DNS. Queries outside those zones retain the existing +system DNS forwarding behavior. This does not change Magic DNS activation, +the fixed DNS address, TUN requirements, or platform DNS setup. Systems without +automatic DNS setup must continue to configure their resolver manually. + +## Migration and configuration definitions + +The file and management API share `core_config.DnsConfigRaw`. `NetworkConfig.dns` +uses field 77. These DNS inputs were not released as a public string contract. +The local `UpdateDnsRecordRequest` is unchanged: static records stay local to the +server instance and are never carried over RPC. + +Optional arrays use reusable messages: `common.RepeatedString` for record strings and +`core_config.RepeatedDnsZoneConfigRaw` for zones. An absent wrapper means the array was +omitted; a present wrapper with no items means an explicit empty array. TOML still +uses `[[dns.zone]]`, `zone = []`, and `records = []`. The collection wrappers use +ordinary Serde with `serde(transparent)`. Both `DnsConfigRaw` and +`DnsZoneConfigRaw` use pbjson and delegate their collection fields to these +transparent wrappers. TOML and DNS management JSON both use `zone`, with no +field renaming: + +```json +{"dns":{"zone":[{"origin":"example.test.","ttl":0,"records":["nas IN A 192.0.2.1"]}]}} +``` + +The protobuf field numbers, field types and wrapper nesting define the binary +contract. The DNS root uses zone at tag 1 and domain at tag 2; each zone uses +origin 1, ttl 2 and records 3; collection wrappers use items at tag 1. A future +configuration generator can rename helper messages while preserving this layout. +Existing TOML and JSON field names remain part of their respective formats. + +`ConfigLoader::get_dns` and `set_dns` now use this Raw message directly. Native +DNS input is parsed directly into runtime records. Defaults, collection unwrapping, +domain normalization and record validation stay in the native DNS parser. Management +projections and browser form merging preserve the Raw collection presence. + +When replacing the legacy DNS runtime, replace the interim +`ConfigLoader::get_dns`/`set_dns` methods and their internal call sites as needed. +dns-policy removes the legacy `magic_dns.proto` along with the old DNS server; +its node RPC and zone distribution protocol replace this local transport. This +interim feature does not extend that transport. + +The existing `accept_dns` switch and this interim runtime's authoritative-zone +behavior remain in use for now. On migration, the full DNS implementation and +the shared definition own activation and fallthrough defaults; these temporary +behaviors are not additional compatibility requirements for that implementation. diff --git a/easytier-core/Cargo.toml b/easytier-core/Cargo.toml index 563a281f..bd6fd10e 100644 --- a/easytier-core/Cargo.toml +++ b/easytier-core/Cargo.toml @@ -34,6 +34,7 @@ cidr = { workspace = true, features = ["serde"] } crossbeam-utils.workspace = true dashmap.workspace = true bon.workspace = true +hickory-proto = { version = "0.25.2", default-features = false } easytier-proto = { workspace = true, features = ["core"] } futures = { workspace = true, features = ["std", "async-await"] } guarden.workspace = true diff --git a/easytier-core/src/config/api.rs b/easytier-core/src/config/api.rs index 3566cfbc..859811c2 100644 --- a/easytier-core/src/config/api.rs +++ b/easytier-core/src/config/api.rs @@ -14,6 +14,7 @@ use super::{ pub fn network_config_from_toml(config: &TomlConfig) -> NetworkConfig { let default_config = TomlConfig::default(); let mut result = NetworkConfig { + dns: config.get_dns(), instance_id: Some(config.get_id().to_string()), dhcp: Some(config.get_dhcp()), dhcp_ipv4_cidr: config.get_dhcp_config().cidr().map(|cidr| cidr.to_string()), diff --git a/easytier-core/src/config/api_input.rs b/easytier-core/src/config/api_input.rs index 5935058b..af8f5308 100644 --- a/easytier-core/src/config/api_input.rs +++ b/easytier-core/src/config/api_input.rs @@ -87,7 +87,10 @@ pub(crate) fn set_network_flags(result: &mut NetworkConfig, flags: FlagsPatch) { 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.tld_dns_zone = flags.tld_dns_zone; + #[allow(deprecated)] + { + result.tld_dns_zone = flags.tld_dns_zone; + } result.mtu = flags.mtu.map(|mtu| mtu as i32); result.data_compress_algo = flags.data_compress_algo; result.encryption_algorithm = flags.encryption_algorithm; @@ -116,6 +119,7 @@ pub trait NetworkConfigExt { ))] const FORM_MANAGED_TOML_FIELDS: &[&str] = &[ "hostname", + "dns", "instance_id", "ipv4", "ipv6_public_addr_provider", @@ -244,6 +248,7 @@ impl NetworkConfigExt for NetworkConfig { #[allow(deprecated)] fn gen_config(&self) -> Result { let cfg = TomlConfigLoader::default(); + cfg.set_dns(self.dns.clone()); cfg.set_id( self.instance_id .clone() @@ -514,6 +519,7 @@ impl NetworkConfigExt for NetworkConfig { } }), accept_dns: self.enable_magic_dns, + #[allow(deprecated)] tld_dns_zone: self.tld_dns_zone.clone(), mtu: self .mtu @@ -559,6 +565,7 @@ impl NetworkConfigExt for NetworkConfig { ..Default::default() }; + result.dns = config.get_dns(); result.instance_id = Some(config.get_id().to_string()); if config.get_hostname() != default_config.get_hostname() { result.hostname = Some(config.get_hostname()); @@ -698,6 +705,121 @@ mod tests { use super::*; use easytier_proto::common::Flags; + #[test] + fn dns_records_survive_toml_and_form_edits() { + for dns in [ + "", + "[dns]\n", + "[dns]\ndomain = 'mesh.test.'\n", + "[[dns.zone]]\norigin = 'example.test.'\nttl = 0\nrecords = ['nas IN A 10.0.0.8', 'nas IN AAAA fd00::8']\n", + ] { + let config = TomlConfigLoader::new_from_str(dns).unwrap(); + let expected = config.get_dns(); + config.set_hostname(Some("changed".to_owned())); + assert_eq!(config.get_dns(), expected); + #[cfg(feature = "config-write")] + assert_eq!( + TomlConfigLoader::new_from_str(&config.dump()) + .unwrap() + .get_dns(), + expected + ); + #[cfg(feature = "browser-config")] + { + let api = NetworkConfig::new_from_config(&config).unwrap(); + let merged = merge_network_config_toml(dns, &api).unwrap(); + let restored = TomlConfigLoader::new_from_str(&merged).unwrap(); + assert_eq!(restored.get_dns(), expected); + assert_eq!(restored.get_hostname(), "changed"); + } + } + } + + #[cfg(feature = "browser-config")] + #[test] + fn dns_raw_presence_survives_management_json_and_file_roundtrips() { + for input in [ + "", + "[dns]\n", + "[dns]\ndomain = 'mesh.test.'\n", + "[dns]\nzone = []\n", + "[[dns.zone]]\norigin = 'example.test.'\n", + "[[dns.zone]]\norigin = 'example.test.'\nttl = 0\nrecords = []\n", + "[[dns.zone]]\norigin = 'example.test.'\nrecords = ['@ IN A 192.0.2.1']\n", + ] { + let config = TomlConfigLoader::new_from_str(input).unwrap(); + let expected = config.get_dns(); + let network = NetworkConfig::new_from_config(&config).unwrap(); + assert_eq!(network.dns, expected, "{input}"); + let json = serde_json::to_value(&network).unwrap(); + if let Some(records) = expected.as_ref().and_then(|dns| dns.zone.as_ref()) { + assert_eq!( + json["dns"]["zone"].as_array().unwrap().len(), + records.items.len() + ); + } + if let Some(domain) = expected.as_ref().and_then(|dns| dns.domain.as_deref()) { + assert_eq!(json["dns"]["domain"].as_str(), Some(domain)); + } + let network: NetworkConfig = serde_json::from_value(json).unwrap(); + let restored = network.gen_config().unwrap(); + assert_eq!(restored.get_dns(), expected, "{input}"); + assert_eq!( + TomlConfigLoader::new_from_str(&restored.dump()) + .unwrap() + .get_dns(), + expected + ); + let merged = merge_network_config_toml(input, &network).unwrap(); + assert_eq!( + TomlConfigLoader::new_from_str(&merged).unwrap().get_dns(), + expected + ); + #[cfg(feature = "web-client")] + assert_eq!( + crate::config::api::network_config_from_toml(&config).dns, + expected + ); + } + let missing = TomlConfigLoader::new_from_str("[dns]\n") + .unwrap() + .get_dns() + .unwrap(); + let empty = TomlConfigLoader::new_from_str("[dns]\nzone = []\n") + .unwrap() + .get_dns() + .unwrap(); + assert!(missing.zone.is_none()); + assert!(empty.zone.unwrap().items.is_empty()); + + let input = "[[dns.zone]]\norigin = 'example.test.'\nrecords = []\n"; + let mut network = + NetworkConfig::new_from_config(TomlConfigLoader::new_from_str(input).unwrap()).unwrap(); + network.dns = None; + let cleared = merge_network_config_toml(input, &network).unwrap(); + assert!( + TomlConfigLoader::new_from_str(&cleared) + .unwrap() + .get_dns() + .is_none() + ); + } + + #[test] + fn dns_file_input_rejects_unknown_fields_and_invalid_shapes() { + for input in [ + "[dns]\nzoen = []\n", + "[[dns.zone]]\norigin = 'example.test.'\nrecrods = []\n", + "[[dns.zone]]\norigin = 'example.test.'\nrecords = {}\n", + "[[dns.zone]]\norigin = 'example.test.'\nttl = -1\n", + ] { + assert!( + TomlConfigLoader::new_from_str(input).is_err(), + "accepted {input}" + ); + } + } + fn api_portal_config() -> manage::VpnPortalConfig { manage::VpnPortalConfig { enabled: None, @@ -931,21 +1053,6 @@ mod tests { ); } - #[test] - fn magic_dns_zone_round_trips_missing_default_and_custom_values() { - for zone in [None, Some("et.net."), Some("nas.internal.")] { - let input = NetworkConfig { - tld_dns_zone: zone.map(str::to_owned), - ..standalone_config() - }; - let config = input.gen_config().unwrap(); - let config = TomlConfigLoader::new_from_str(&config.dump()).unwrap(); - assert_eq!(config.get_flags().tld_dns_zone, zone.unwrap_or("et.net.")); - let output = NetworkConfig::new_from_config(&config).unwrap(); - assert_eq!(output.tld_dns_zone.as_deref(), zone); - } - } - #[test] fn vpn_portal_api_config_round_trips_through_toml_model() { let input = NetworkConfig { @@ -1062,22 +1169,6 @@ mod tests { assert!(config.get_vpn_portal_config().is_none()); } - #[cfg(feature = "browser-config")] - #[test] - fn browser_merge_updates_and_clears_magic_dns_zone() { - let original = "[flags]\ntld_dns_zone = \"old.internal.\"\n"; - for zone in [None, Some("et.net."), Some("nas.internal.")] { - let input = NetworkConfig { - tld_dns_zone: zone.map(str::to_owned), - ..standalone_config() - }; - let merged = merge_network_config_toml(original, &input).unwrap(); - let output = TomlConfigLoader::new_from_str(&merged).unwrap(); - assert_eq!(output.get_flags().tld_dns_zone, zone.unwrap_or("et.net.")); - assert_eq!(output.get_flags_patch().tld_dns_zone.as_deref(), zone); - } - } - #[cfg(feature = "browser-config")] #[test] fn browser_merge_reconciles_renamed_flags() { diff --git a/easytier-core/src/config/toml.rs b/easytier-core/src/config/toml.rs index 436419ef..8e3c71db 100644 --- a/easytier-core/src/config/toml.rs +++ b/easytier-core/src/config/toml.rs @@ -20,6 +20,9 @@ pub const DEFAULT_ET_DNS_ZONE: &str = "et.net."; pub use crate::proto::common::{Flags, FlagsPatch}; +// File and management inputs share these protobuf Raw messages. +pub use crate::proto::core_config::{DnsConfigRaw as DnsConfig, DnsZoneConfigRaw as DnsZoneConfig}; + pub(crate) fn default_instance_name() -> String { "default".to_owned() } @@ -32,6 +35,9 @@ pub trait ConfigLoader: Send + Sync { fn get_hostname(&self) -> String; fn set_hostname(&self, name: Option); + fn get_dns(&self) -> Option; + fn set_dns(&self, dns: Option); + fn get_inst_name(&self) -> String; fn set_inst_name(&self, name: String); @@ -409,6 +415,7 @@ impl std::fmt::Debug for ManagedCredentialConfig { struct Config { netns: Option, hostname: Option, + dns: Option, instance_name: Option, instance_id: Option, ipv4: Option, @@ -672,6 +679,14 @@ impl ConfigLoader for TomlConfig { self.config.lock().unwrap().hostname = name; } + fn get_dns(&self) -> Option { + self.config.lock().unwrap().dns.clone() + } + + fn set_dns(&self, dns: Option) { + self.config.lock().unwrap().dns = dns; + } + fn get_netns(&self) -> Option { self.config.lock().unwrap().netns.clone() } diff --git a/easytier-core/src/gateway/magic_dns/records.rs b/easytier-core/src/gateway/magic_dns/records.rs index 7d449868..4450594b 100644 --- a/easytier-core/src/gateway/magic_dns/records.rs +++ b/easytier-core/src/gateway/magic_dns/records.rs @@ -1,6 +1,7 @@ use std::{collections::BTreeMap, net::Ipv4Addr, sync::Mutex, time::Duration}; use async_trait::async_trait; +use hickory_proto::rr::LowerName; use quanta::Instant; use crate::peers::peer_manager::PeerManagerCore; @@ -15,7 +16,6 @@ pub struct MagicDnsRoute { pub struct MagicDnsRouteSnapshot { pub revision: Instant, pub routes: Vec, - pub zone: String, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -42,22 +42,18 @@ fn magic_dns_route_advertisement( fn magic_dns_route_snapshot( revision: Instant, routes: Vec, - local_identity: (String, Option, String), + local_identity: (String, Option), ) -> MagicDnsRouteSnapshot { let mut routes = routes .into_iter() .map(magic_dns_route_advertisement) .collect::>(); - let (hostname, ipv4_addr, zone) = local_identity; + let (hostname, ipv4_addr) = local_identity; routes.push(MagicDnsRouteAdvertisement { hostname, ipv4_addr, }); - MagicDnsRouteSnapshot { - revision, - routes, - zone, - } + MagicDnsRouteSnapshot { revision, routes } } #[async_trait] @@ -112,12 +108,12 @@ where #[derive(Debug, Clone, PartialEq, Eq)] pub struct MagicDnsRecordSnapshot { - pub zones: BTreeMap>, + pub zones: BTreeMap>, } #[derive(Debug, Default)] pub struct MagicDnsRecordStore { - zones: Mutex>>>, + zones: Mutex>>>, } impl MagicDnsRecordStore { @@ -128,7 +124,7 @@ impl MagicDnsRecordStore { /// authoritative. pub fn replace_client_routes( &self, - zone: String, + zone: LowerName, client: String, routes: Vec, ) -> bool { @@ -153,7 +149,7 @@ impl MagicDnsRecordStore { /// Removes a disconnected client from every zone and returns the zones /// that became empty. - pub fn remove_client(&self, client: &str) -> Vec { + pub fn remove_client(&self, client: &str) -> Vec { let mut zones = self.zones.lock().unwrap(); let mut removed_zones = Vec::new(); zones.retain(|zone, routes_by_client| { @@ -215,14 +211,9 @@ mod tests { let snapshot = magic_dns_route_snapshot( Instant::now(), Vec::new(), - ( - "portable-node".to_owned(), - Some(ipv4_addr), - "et.net.".to_owned(), - ), + ("portable-node".to_owned(), Some(ipv4_addr)), ); - assert_eq!(snapshot.zone, "et.net."); assert_eq!( snapshot.routes, [MagicDnsRouteAdvertisement { @@ -245,7 +236,6 @@ mod tests { hostname: "node-a".to_owned(), ipv4_addr: Some("10.1.0.1/24".parse::().unwrap().into()), }], - zone: "et.net.".to_owned(), } } @@ -351,24 +341,25 @@ mod tests { #[test] fn replaces_routes_for_the_same_client_without_touching_other_clients() { + let zone: LowerName = "et.net.".parse().unwrap(); let store = MagicDnsRecordStore::default(); assert!(!store.replace_client_routes( - "et.net.".to_owned(), + zone.clone(), "tcp://client-a".to_owned(), vec![route("old-a", [10, 0, 0, 1])], )); assert!(!store.replace_client_routes( - "et.net.".to_owned(), + zone.clone(), "tcp://client-b".to_owned(), vec![route("peer-b", [10, 0, 0, 2])], )); assert!(!store.replace_client_routes( - "et.net.".to_owned(), + zone.clone(), "tcp://client-a".to_owned(), vec![route("new-a", [10, 0, 0, 3])], )); - let routes = &store.snapshot().zones["et.net."]; + let routes = &store.snapshot().zones[&zone]; assert_eq!(routes.len(), 2); assert!(routes.iter().any(|route| route.hostname == "new-a")); assert!(routes.iter().any(|route| route.hostname == "peer-b")); @@ -377,53 +368,49 @@ mod tests { #[test] fn empty_update_removes_only_the_target_client_and_reports_empty_zone() { + let zone: LowerName = "et.net.".parse().unwrap(); let store = MagicDnsRecordStore::default(); store.replace_client_routes( - "et.net.".to_owned(), + zone.clone(), "tcp://client-a".to_owned(), vec![route("peer-a", [10, 0, 0, 1])], ); store.replace_client_routes( - "et.net.".to_owned(), + zone.clone(), "tcp://client-b".to_owned(), vec![route("peer-b", [10, 0, 0, 2])], ); assert!(!store.replace_client_routes( - "et.net.".to_owned(), + zone.clone(), "tcp://client-a".to_owned(), Vec::new(), )); - assert!(store.replace_client_routes( - "et.net.".to_owned(), - "tcp://client-b".to_owned(), - Vec::new(), - )); + assert!(store.replace_client_routes(zone, "tcp://client-b".to_owned(), Vec::new())); assert!(store.snapshot().zones.is_empty()); } #[test] fn disconnect_removes_client_from_all_zones() { let store = MagicDnsRecordStore::default(); - for zone in ["a.et.net.", "b.et.net."] { + let zone_a: LowerName = "a.et.net.".parse().unwrap(); + let zone_b: LowerName = "b.et.net.".parse().unwrap(); + for zone in [&zone_a, &zone_b] { store.replace_client_routes( - zone.to_owned(), + zone.clone(), "tcp://client-a".to_owned(), vec![route("peer-a", [10, 0, 0, 1])], ); } store.replace_client_routes( - "b.et.net.".to_owned(), + zone_b.clone(), "tcp://client-b".to_owned(), vec![route("peer-b", [10, 0, 0, 2])], ); - assert_eq!( - store.remove_client("tcp://client-a"), - vec!["a.et.net.".to_owned()] - ); + assert_eq!(store.remove_client("tcp://client-a"), vec![zone_a]); let snapshot = store.snapshot(); assert_eq!(snapshot.zones.len(), 1); - assert_eq!(snapshot.zones["b.et.net."][0].hostname, "peer-b"); + assert_eq!(snapshot.zones[&zone_b][0].hostname, "peer-b"); } } diff --git a/easytier-core/src/peers/peer_manager.rs b/easytier-core/src/peers/peer_manager.rs index bcc6806b..02d4e656 100644 --- a/easytier-core/src/peers/peer_manager.rs +++ b/easytier-core/src/peers/peer_manager.rs @@ -1493,14 +1493,8 @@ impl PeerManagerCore { &self.network_name } - pub(crate) fn dns_route_identity( - &self, - ) -> (String, Option, String) { - ( - self.context.hostname(), - self.context.ipv4().map(Into::into), - self.context.flags().tld_dns_zone, - ) + pub(crate) fn dns_route_identity(&self) -> (String, Option) { + (self.context.hostname(), self.context.ipv4().map(Into::into)) } pub fn p2p_policy_flags(&self) -> P2pPolicyFlags { @@ -4390,7 +4384,6 @@ mod tests { ( "portable-node".to_owned(), Some("10.20.0.91/16".parse::().unwrap().into()), - String::new(), ) ); } diff --git a/easytier-go/proto/api/manage/api_manage.pb.go b/easytier-go/proto/api/manage/api_manage.pb.go index d3d8492d..8639ac56 100644 --- a/easytier-go/proto/api/manage/api_manage.pb.go +++ b/easytier-go/proto/api/manage/api_manage.pb.go @@ -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.2 // source: api_manage.proto package manage @@ -10,6 +10,7 @@ import ( acl "github.com/easytier/easytier/easytier-go/proto/acl" instance "github.com/easytier/easytier/easytier-go/proto/api/instance" common "github.com/easytier/easytier/easytier-go/proto/common" + core_config "github.com/easytier/easytier/easytier-go/proto/core_config" peer_rpc "github.com/easytier/easytier/easytier-go/proto/peer_rpc" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -147,10 +148,12 @@ type NetworkConfig struct { AdvancedSettings *bool `protobuf:"varint,16,opt,name=advanced_settings,json=advancedSettings,proto3,oneof" json:"advanced_settings,omitempty"` ListenerUrls []string `protobuf:"bytes,17,rep,name=listener_urls,json=listenerUrls,proto3" json:"listener_urls,omitempty"` // optional int32 rpc_port = 18; - LatencyFirst *bool `protobuf:"varint,19,opt,name=latency_first,json=latencyFirst,proto3,oneof" json:"latency_first,omitempty"` - DevName *string `protobuf:"bytes,20,opt,name=dev_name,json=devName,proto3,oneof" json:"dev_name,omitempty"` - UseSmoltcp *bool `protobuf:"varint,21,opt,name=use_smoltcp,json=useSmoltcp,proto3,oneof" json:"use_smoltcp,omitempty"` - DisableIpv6 *bool `protobuf:"varint,47,opt,name=disable_ipv6,json=disableIpv6,proto3,oneof" json:"disable_ipv6,omitempty"` + LatencyFirst *bool `protobuf:"varint,19,opt,name=latency_first,json=latencyFirst,proto3,oneof" json:"latency_first,omitempty"` + DevName *string `protobuf:"bytes,20,opt,name=dev_name,json=devName,proto3,oneof" json:"dev_name,omitempty"` + UseSmoltcp *bool `protobuf:"varint,21,opt,name=use_smoltcp,json=useSmoltcp,proto3,oneof" json:"use_smoltcp,omitempty"` + DisableIpv6 *bool `protobuf:"varint,47,opt,name=disable_ipv6,json=disableIpv6,proto3,oneof" json:"disable_ipv6,omitempty"` + // Deprecated: Marked as deprecated in api_manage.proto. + TldDnsZone *string `protobuf:"bytes,73,opt,name=tld_dns_zone,json=tldDnsZone,proto3,oneof" json:"tld_dns_zone,omitempty"` EnableKcpProxy *bool `protobuf:"varint,22,opt,name=enable_kcp_proxy,json=enableKcpProxy,proto3,oneof" json:"enable_kcp_proxy,omitempty"` DisableKcpInput *bool `protobuf:"varint,23,opt,name=disable_kcp_input,json=disableKcpInput,proto3,oneof" json:"disable_kcp_input,omitempty"` DisableP2P *bool `protobuf:"varint,24,opt,name=disable_p2p,json=disableP2p,proto3,oneof" json:"disable_p2p,omitempty"` @@ -201,9 +204,10 @@ type NetworkConfig struct { ManagedCredentials []*ManagedCredentialConfig `protobuf:"bytes,71,rep,name=managed_credentials,json=managedCredentials,proto3" json:"managed_credentials,omitempty"` PreferPeerRelay *bool `protobuf:"varint,72,opt,name=prefer_peer_relay,json=preferPeerRelay,proto3,oneof" json:"prefer_peer_relay,omitempty"` // Bootstrap subnet used when DHCP has not learned a subnet from peers. - DhcpIpv4Cidr *string `protobuf:"bytes,74,opt,name=dhcp_ipv4_cidr,json=dhcpIpv4Cidr,proto3,oneof" json:"dhcp_ipv4_cidr,omitempty"` - Dhcpv6 *bool `protobuf:"varint,75,opt,name=dhcpv6,proto3,oneof" json:"dhcpv6,omitempty"` - DhcpIpv6Cidr *string `protobuf:"bytes,76,opt,name=dhcp_ipv6_cidr,json=dhcpIpv6Cidr,proto3,oneof" json:"dhcp_ipv6_cidr,omitempty"` + DhcpIpv4Cidr *string `protobuf:"bytes,74,opt,name=dhcp_ipv4_cidr,json=dhcpIpv4Cidr,proto3,oneof" json:"dhcp_ipv4_cidr,omitempty"` + Dhcpv6 *bool `protobuf:"varint,75,opt,name=dhcpv6,proto3,oneof" json:"dhcpv6,omitempty"` + DhcpIpv6Cidr *string `protobuf:"bytes,76,opt,name=dhcp_ipv6_cidr,json=dhcpIpv6Cidr,proto3,oneof" json:"dhcp_ipv6_cidr,omitempty"` + Dns *core_config.DnsConfigRaw `protobuf:"bytes,77,opt,name=dns,proto3" json:"dns,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -389,6 +393,14 @@ func (x *NetworkConfig) GetDisableIpv6() bool { return false } +// Deprecated: Marked as deprecated in api_manage.proto. +func (x *NetworkConfig) GetTldDnsZone() string { + if x != nil && x.TldDnsZone != nil { + return *x.TldDnsZone + } + return "" +} + func (x *NetworkConfig) GetEnableKcpProxy() bool { if x != nil && x.EnableKcpProxy != nil { return *x.EnableKcpProxy @@ -747,6 +759,13 @@ func (x *NetworkConfig) GetDhcpIpv6Cidr() string { return "" } +func (x *NetworkConfig) GetDns() *core_config.DnsConfigRaw { + if x != nil { + return x.Dns + } + return nil +} + type ManagedCredentialConfig struct { state protoimpl.MessageState `protogen:"open.v1"` CredentialId string `protobuf:"bytes,1,opt,name=credential_id,json=credentialId,proto3" json:"credential_id,omitempty"` @@ -2211,7 +2230,7 @@ var File_api_manage_proto protoreflect.FileDescriptor const file_api_manage_proto_rawDesc = "" + "\n" + "\x10api_manage.proto\x12\n" + - "api.manage\x1a\fcommon.proto\x1a\x0epeer_rpc.proto\x1a\x12api_instance.proto\x1a\tacl.proto\"\xa4%\n" + + "api.manage\x1a\fcommon.proto\x1a\x0epeer_rpc.proto\x1a\x12api_instance.proto\x1a\tacl.proto\x1a\x11core_config.proto\"\x8d&\n" + "\rNetworkConfig\x12$\n" + "\vinstance_id\x18\x01 \x01(\tH\x00R\n" + "instanceId\x88\x01\x01\x12\x17\n" + @@ -2238,64 +2257,67 @@ const file_api_manage_proto_rawDesc = "" + "\bdev_name\x18\x14 \x01(\tH\x0fR\adevName\x88\x01\x01\x12$\n" + "\vuse_smoltcp\x18\x15 \x01(\bH\x10R\n" + "useSmoltcp\x88\x01\x01\x12&\n" + - "\fdisable_ipv6\x18/ \x01(\bH\x11R\vdisableIpv6\x88\x01\x01\x12-\n" + - "\x10enable_kcp_proxy\x18\x16 \x01(\bH\x12R\x0eenableKcpProxy\x88\x01\x01\x12/\n" + - "\x11disable_kcp_input\x18\x17 \x01(\bH\x13R\x0fdisableKcpInput\x88\x01\x01\x12$\n" + - "\vdisable_p2p\x18\x18 \x01(\bH\x14R\n" + + "\fdisable_ipv6\x18/ \x01(\bH\x11R\vdisableIpv6\x88\x01\x01\x12)\n" + + "\ftld_dns_zone\x18I \x01(\tB\x02\x18\x01H\x12R\n" + + "tldDnsZone\x88\x01\x01\x12-\n" + + "\x10enable_kcp_proxy\x18\x16 \x01(\bH\x13R\x0eenableKcpProxy\x88\x01\x01\x12/\n" + + "\x11disable_kcp_input\x18\x17 \x01(\bH\x14R\x0fdisableKcpInput\x88\x01\x01\x12$\n" + + "\vdisable_p2p\x18\x18 \x01(\bH\x15R\n" + "disableP2p\x88\x01\x01\x12$\n" + - "\vbind_device\x18\x19 \x01(\bH\x15R\n" + + "\vbind_device\x18\x19 \x01(\bH\x16R\n" + "bindDevice\x88\x01\x01\x12\x1a\n" + - "\x06no_tun\x18\x1a \x01(\bH\x16R\x05noTun\x88\x01\x01\x12-\n" + - "\x10enable_exit_node\x18\x1b \x01(\bH\x17R\x0eenableExitNode\x88\x01\x01\x120\n" + - "\x12relay_all_peer_rpc\x18\x1c \x01(\bH\x18R\x0frelayAllPeerRpc\x88\x01\x01\x12&\n" + - "\fmulti_thread\x18\x1d \x01(\bH\x19R\vmultiThread\x88\x01\x01\x12H\n" + - "\x1eenable_relay_network_whitelist\x18\x1e \x01(\bH\x1aR\x1benableRelayNetworkWhitelist\x88\x01\x01\x126\n" + + "\x06no_tun\x18\x1a \x01(\bH\x17R\x05noTun\x88\x01\x01\x12-\n" + + "\x10enable_exit_node\x18\x1b \x01(\bH\x18R\x0eenableExitNode\x88\x01\x01\x120\n" + + "\x12relay_all_peer_rpc\x18\x1c \x01(\bH\x19R\x0frelayAllPeerRpc\x88\x01\x01\x12&\n" + + "\fmulti_thread\x18\x1d \x01(\bH\x1aR\vmultiThread\x88\x01\x01\x12H\n" + + "\x1eenable_relay_network_whitelist\x18\x1e \x01(\bH\x1bR\x1benableRelayNetworkWhitelist\x88\x01\x01\x126\n" + "\x17relay_network_whitelist\x18\x1f \x03(\tR\x15relayNetworkWhitelist\x125\n" + - "\x14enable_manual_routes\x18 \x01(\bH\x1bR\x12enableManualRoutes\x88\x01\x01\x12\x16\n" + + "\x14enable_manual_routes\x18 \x01(\bH\x1cR\x12enableManualRoutes\x88\x01\x01\x12\x16\n" + "\x06routes\x18! \x03(\tR\x06routes\x12\x1d\n" + "\n" + "exit_nodes\x18\" \x03(\tR\texitNodes\x12:\n" + - "\x17proxy_forward_by_system\x18# \x01(\bH\x1cR\x14proxyForwardBySystem\x88\x01\x01\x122\n" + - "\x12disable_encryption\x18$ \x01(\bH\x1dR\x11disableEncryption\x88\x01\x01\x12(\n" + - "\renable_socks5\x18% \x01(\bH\x1eR\fenableSocks5\x88\x01\x01\x12$\n" + - "\vsocks5_port\x18& \x01(\x05H\x1fR\n" + + "\x17proxy_forward_by_system\x18# \x01(\bH\x1dR\x14proxyForwardBySystem\x88\x01\x01\x122\n" + + "\x12disable_encryption\x18$ \x01(\bH\x1eR\x11disableEncryption\x88\x01\x01\x12(\n" + + "\renable_socks5\x18% \x01(\bH\x1fR\fenableSocks5\x88\x01\x01\x12$\n" + + "\vsocks5_port\x18& \x01(\x05H R\n" + "socks5Port\x88\x01\x01\x12>\n" + - "\x19disable_udp_hole_punching\x18' \x01(\bH R\x16disableUdpHolePunching\x88\x01\x01\x12\x15\n" + - "\x03mtu\x18( \x01(\x05H!R\x03mtu\x88\x01\x01\x12)\n" + + "\x19disable_udp_hole_punching\x18' \x01(\bH!R\x16disableUdpHolePunching\x88\x01\x01\x12\x15\n" + + "\x03mtu\x18( \x01(\x05H\"R\x03mtu\x88\x01\x01\x12)\n" + "\x10mapped_listeners\x18) \x03(\tR\x0fmappedListeners\x12-\n" + - "\x10enable_magic_dns\x18* \x01(\bH\"R\x0eenableMagicDns\x88\x01\x01\x123\n" + - "\x13enable_private_mode\x18+ \x01(\bH#R\x11enablePrivateMode\x88\x01\x01\x12/\n" + - "\x11enable_quic_proxy\x18- \x01(\bH$R\x0fenableQuicProxy\x88\x01\x01\x121\n" + - "\x12disable_quic_input\x18. \x01(\bH%R\x10disableQuicInput\x88\x01\x01\x121\n" + - "\x10quic_listen_port\x182 \x01(\x05B\x02\x18\x01H&R\x0equicListenPort\x88\x01\x01\x12B\n" + + "\x10enable_magic_dns\x18* \x01(\bH#R\x0eenableMagicDns\x88\x01\x01\x123\n" + + "\x13enable_private_mode\x18+ \x01(\bH$R\x11enablePrivateMode\x88\x01\x01\x12/\n" + + "\x11enable_quic_proxy\x18- \x01(\bH%R\x0fenableQuicProxy\x88\x01\x01\x121\n" + + "\x12disable_quic_input\x18. \x01(\bH&R\x10disableQuicInput\x88\x01\x01\x121\n" + + "\x10quic_listen_port\x182 \x01(\x05B\x02\x18\x01H'R\x0equicListenPort\x88\x01\x01\x12B\n" + "\rport_forwards\x180 \x03(\v2\x1d.api.manage.PortForwardConfigR\fportForwards\x12>\n" + - "\x19disable_sym_hole_punching\x181 \x01(\bH'R\x16disableSymHolePunching\x88\x01\x01\x12\x1e\n" + - "\bp2p_only\x183 \x01(\bH(R\ap2pOnly\x88\x01\x01\x12L\n" + - "\x12data_compress_algo\x184 \x01(\x0e2\x19.common.CompressionAlgoPbH)R\x10dataCompressAlgo\x88\x01\x01\x126\n" + - "\x14encryption_algorithm\x185 \x01(\tH*R\x13encryptionAlgorithm\x88\x01\x01\x12>\n" + - "\x19disable_tcp_hole_punching\x186 \x01(\bH+R\x16disableTcpHolePunching\x88\x01\x01\x129\n" + + "\x19disable_sym_hole_punching\x181 \x01(\bH(R\x16disableSymHolePunching\x88\x01\x01\x12\x1e\n" + + "\bp2p_only\x183 \x01(\bH)R\ap2pOnly\x88\x01\x01\x12L\n" + + "\x12data_compress_algo\x184 \x01(\x0e2\x19.common.CompressionAlgoPbH*R\x10dataCompressAlgo\x88\x01\x01\x126\n" + + "\x14encryption_algorithm\x185 \x01(\tH+R\x13encryptionAlgorithm\x88\x01\x01\x12>\n" + + "\x19disable_tcp_hole_punching\x186 \x01(\bH,R\x16disableTcpHolePunching\x88\x01\x01\x129\n" + "\vsecure_mode\x187 \x01(\v2\x18.common.SecureModeConfigR\n" + "secureMode\x12\x1f\n" + - "\x03acl\x188 \x01(\v2\b.acl.AclH,R\x03acl\x88\x01\x01\x12,\n" + - "\x0fcredential_file\x189 \x01(\tH-R\x0ecredentialFile\x88\x01\x01\x12\x1e\n" + - "\blazy_p2p\x18: \x01(\bH.R\alazyP2p\x88\x01\x01\x12\x1e\n" + - "\bneed_p2p\x18; \x01(\bH/R\aneedP2p\x88\x01\x01\x12:\n" + - "\x17instance_recv_bps_limit\x18< \x01(\x04H0R\x14instanceRecvBpsLimit\x88\x01\x01\x12&\n" + - "\fdisable_upnp\x18= \x01(\bH1R\vdisableUpnp\x88\x01\x01\x12>\n" + - "\x19ipv6_public_addr_provider\x18> \x01(\bH2R\x16ipv6PublicAddrProvider\x88\x01\x01\x126\n" + - "\x15ipv6_public_addr_auto\x18? \x01(\bH3R\x12ipv6PublicAddrAuto\x88\x01\x01\x12:\n" + - "\x17ipv6_public_addr_prefix\x18@ \x01(\tH4R\x14ipv6PublicAddrPrefix\x88\x01\x01\x121\n" + - "\x12disable_relay_data\x18A \x01(\bH5R\x10disableRelayData\x88\x01\x01\x12@\n" + - "\x1aenable_udp_broadcast_relay\x18B \x01(\bH6R\x17enableUdpBroadcastRelay\x88\x01\x01\x12$\n" + - "\vsocket_mark\x18C \x01(\rH7R\n" + + "\x03acl\x188 \x01(\v2\b.acl.AclH-R\x03acl\x88\x01\x01\x12,\n" + + "\x0fcredential_file\x189 \x01(\tH.R\x0ecredentialFile\x88\x01\x01\x12\x1e\n" + + "\blazy_p2p\x18: \x01(\bH/R\alazyP2p\x88\x01\x01\x12\x1e\n" + + "\bneed_p2p\x18; \x01(\bH0R\aneedP2p\x88\x01\x01\x12:\n" + + "\x17instance_recv_bps_limit\x18< \x01(\x04H1R\x14instanceRecvBpsLimit\x88\x01\x01\x12&\n" + + "\fdisable_upnp\x18= \x01(\bH2R\vdisableUpnp\x88\x01\x01\x12>\n" + + "\x19ipv6_public_addr_provider\x18> \x01(\bH3R\x16ipv6PublicAddrProvider\x88\x01\x01\x126\n" + + "\x15ipv6_public_addr_auto\x18? \x01(\bH4R\x12ipv6PublicAddrAuto\x88\x01\x01\x12:\n" + + "\x17ipv6_public_addr_prefix\x18@ \x01(\tH5R\x14ipv6PublicAddrPrefix\x88\x01\x01\x121\n" + + "\x12disable_relay_data\x18A \x01(\bH6R\x10disableRelayData\x88\x01\x01\x12@\n" + + "\x1aenable_udp_broadcast_relay\x18B \x01(\bH7R\x17enableUdpBroadcastRelay\x88\x01\x01\x12$\n" + + "\vsocket_mark\x18C \x01(\rH8R\n" + "socketMark\x88\x01\x01\x123\n" + "\x05peers\x18D \x03(\v2\x1d.api.manage.NetworkPeerConfigR\x05peers\x12L\n" + - "\x11vpn_portal_config\x18E \x01(\v2\x1b.api.manage.VpnPortalConfigH8R\x0fvpnPortalConfig\x88\x01\x01\x12T\n" + + "\x11vpn_portal_config\x18E \x01(\v2\x1b.api.manage.VpnPortalConfigH9R\x0fvpnPortalConfig\x88\x01\x01\x12T\n" + "\x13managed_credentials\x18G \x03(\v2#.api.manage.ManagedCredentialConfigR\x12managedCredentials\x12/\n" + - "\x11prefer_peer_relay\x18H \x01(\bH9R\x0fpreferPeerRelay\x88\x01\x01\x12)\n" + - "\x0edhcp_ipv4_cidr\x18J \x01(\tH:R\fdhcpIpv4Cidr\x88\x01\x01\x12\x1b\n" + - "\x06dhcpv6\x18K \x01(\bH;R\x06dhcpv6\x88\x01\x01\x12)\n" + - "\x0edhcp_ipv6_cidr\x18L \x01(\tH api.manage.NetworkingMethod @@ -2553,60 +2577,61 @@ var file_api_manage_proto_depIdxs = []int32{ 7, // 5: api.manage.NetworkConfig.peers:type_name -> api.manage.NetworkPeerConfig 6, // 6: api.manage.NetworkConfig.vpn_portal_config:type_name -> api.manage.VpnPortalConfig 3, // 7: api.manage.NetworkConfig.managed_credentials:type_name -> api.manage.ManagedCredentialConfig - 3, // 8: api.manage.ManagedCredentialSet.entries:type_name -> api.manage.ManagedCredentialConfig - 5, // 9: api.manage.VpnPortalConfig.clients:type_name -> api.manage.VpnPortalClientConfig - 33, // 10: api.manage.MyNodeInfo.virtual_ipv4:type_name -> common.Ipv4Inet - 34, // 11: api.manage.MyNodeInfo.ips:type_name -> peer_rpc.GetIpListResponse - 35, // 12: api.manage.MyNodeInfo.stun_info:type_name -> common.StunInfo - 36, // 13: api.manage.MyNodeInfo.listeners:type_name -> common.Url - 37, // 14: api.manage.MyNodeInfo.virtual_ipv6:type_name -> common.Ipv6Inet - 9, // 15: api.manage.NetworkInstanceRunningInfo.my_node_info:type_name -> api.manage.MyNodeInfo - 38, // 16: api.manage.NetworkInstanceRunningInfo.routes:type_name -> api.instance.Route - 39, // 17: api.manage.NetworkInstanceRunningInfo.peers:type_name -> api.instance.PeerInfo - 40, // 18: api.manage.NetworkInstanceRunningInfo.peer_route_pairs:type_name -> api.instance.PeerRoutePair - 41, // 19: api.manage.NetworkInstanceRunningInfo.foreign_network_summary:type_name -> peer_rpc.RouteForeignNetworkSummary - 29, // 20: api.manage.NetworkInstanceRunningInfoMap.map:type_name -> api.manage.NetworkInstanceRunningInfoMap.MapEntry - 42, // 21: api.manage.NetworkMeta.inst_id:type_name -> common.UUID - 1, // 22: api.manage.NetworkMeta.source:type_name -> api.manage.ConfigSource - 2, // 23: api.manage.ValidateConfigRequest.config:type_name -> api.manage.NetworkConfig - 42, // 24: api.manage.RunNetworkInstanceRequest.inst_id:type_name -> common.UUID - 2, // 25: api.manage.RunNetworkInstanceRequest.config:type_name -> api.manage.NetworkConfig - 1, // 26: api.manage.RunNetworkInstanceRequest.source:type_name -> api.manage.ConfigSource - 42, // 27: api.manage.RunNetworkInstanceResponse.inst_id:type_name -> common.UUID - 42, // 28: api.manage.RetainNetworkInstanceRequest.inst_ids:type_name -> common.UUID - 42, // 29: api.manage.RetainNetworkInstanceResponse.remain_inst_ids:type_name -> common.UUID - 42, // 30: api.manage.CollectNetworkInfoRequest.inst_ids:type_name -> common.UUID - 11, // 31: api.manage.CollectNetworkInfoResponse.info:type_name -> api.manage.NetworkInstanceRunningInfoMap - 42, // 32: api.manage.ListNetworkInstanceResponse.inst_ids:type_name -> common.UUID - 42, // 33: api.manage.DeleteNetworkInstanceRequest.inst_ids:type_name -> common.UUID - 42, // 34: api.manage.DeleteNetworkInstanceResponse.remain_inst_ids:type_name -> common.UUID - 42, // 35: api.manage.GetNetworkInstanceConfigRequest.inst_id:type_name -> common.UUID - 2, // 36: api.manage.GetNetworkInstanceConfigResponse.config:type_name -> api.manage.NetworkConfig - 1, // 37: api.manage.GetNetworkInstanceConfigResponse.source:type_name -> api.manage.ConfigSource - 42, // 38: api.manage.ListNetworkInstanceMetaRequest.inst_ids:type_name -> common.UUID - 12, // 39: api.manage.ListNetworkInstanceMetaResponse.metas:type_name -> api.manage.NetworkMeta - 10, // 40: api.manage.NetworkInstanceRunningInfoMap.MapEntry.value:type_name -> api.manage.NetworkInstanceRunningInfo - 13, // 41: api.manage.WebClientService.ValidateConfig:input_type -> api.manage.ValidateConfigRequest - 15, // 42: api.manage.WebClientService.RunNetworkInstance:input_type -> api.manage.RunNetworkInstanceRequest - 17, // 43: api.manage.WebClientService.RetainNetworkInstance:input_type -> api.manage.RetainNetworkInstanceRequest - 19, // 44: api.manage.WebClientService.CollectNetworkInfo:input_type -> api.manage.CollectNetworkInfoRequest - 21, // 45: api.manage.WebClientService.ListNetworkInstance:input_type -> api.manage.ListNetworkInstanceRequest - 23, // 46: api.manage.WebClientService.DeleteNetworkInstance:input_type -> api.manage.DeleteNetworkInstanceRequest - 25, // 47: api.manage.WebClientService.GetNetworkInstanceConfig:input_type -> api.manage.GetNetworkInstanceConfigRequest - 27, // 48: api.manage.WebClientService.ListNetworkInstanceMeta:input_type -> api.manage.ListNetworkInstanceMetaRequest - 14, // 49: api.manage.WebClientService.ValidateConfig:output_type -> api.manage.ValidateConfigResponse - 16, // 50: api.manage.WebClientService.RunNetworkInstance:output_type -> api.manage.RunNetworkInstanceResponse - 18, // 51: api.manage.WebClientService.RetainNetworkInstance:output_type -> api.manage.RetainNetworkInstanceResponse - 20, // 52: api.manage.WebClientService.CollectNetworkInfo:output_type -> api.manage.CollectNetworkInfoResponse - 22, // 53: api.manage.WebClientService.ListNetworkInstance:output_type -> api.manage.ListNetworkInstanceResponse - 24, // 54: api.manage.WebClientService.DeleteNetworkInstance:output_type -> api.manage.DeleteNetworkInstanceResponse - 26, // 55: api.manage.WebClientService.GetNetworkInstanceConfig:output_type -> api.manage.GetNetworkInstanceConfigResponse - 28, // 56: api.manage.WebClientService.ListNetworkInstanceMeta:output_type -> api.manage.ListNetworkInstanceMetaResponse - 49, // [49:57] is the sub-list for method output_type - 41, // [41:49] is the sub-list for method input_type - 41, // [41:41] is the sub-list for extension type_name - 41, // [41:41] is the sub-list for extension extendee - 0, // [0:41] is the sub-list for field type_name + 33, // 8: api.manage.NetworkConfig.dns:type_name -> core_config.DnsConfigRaw + 3, // 9: api.manage.ManagedCredentialSet.entries:type_name -> api.manage.ManagedCredentialConfig + 5, // 10: api.manage.VpnPortalConfig.clients:type_name -> api.manage.VpnPortalClientConfig + 34, // 11: api.manage.MyNodeInfo.virtual_ipv4:type_name -> common.Ipv4Inet + 35, // 12: api.manage.MyNodeInfo.ips:type_name -> peer_rpc.GetIpListResponse + 36, // 13: api.manage.MyNodeInfo.stun_info:type_name -> common.StunInfo + 37, // 14: api.manage.MyNodeInfo.listeners:type_name -> common.Url + 38, // 15: api.manage.MyNodeInfo.virtual_ipv6:type_name -> common.Ipv6Inet + 9, // 16: api.manage.NetworkInstanceRunningInfo.my_node_info:type_name -> api.manage.MyNodeInfo + 39, // 17: api.manage.NetworkInstanceRunningInfo.routes:type_name -> api.instance.Route + 40, // 18: api.manage.NetworkInstanceRunningInfo.peers:type_name -> api.instance.PeerInfo + 41, // 19: api.manage.NetworkInstanceRunningInfo.peer_route_pairs:type_name -> api.instance.PeerRoutePair + 42, // 20: api.manage.NetworkInstanceRunningInfo.foreign_network_summary:type_name -> peer_rpc.RouteForeignNetworkSummary + 29, // 21: api.manage.NetworkInstanceRunningInfoMap.map:type_name -> api.manage.NetworkInstanceRunningInfoMap.MapEntry + 43, // 22: api.manage.NetworkMeta.inst_id:type_name -> common.UUID + 1, // 23: api.manage.NetworkMeta.source:type_name -> api.manage.ConfigSource + 2, // 24: api.manage.ValidateConfigRequest.config:type_name -> api.manage.NetworkConfig + 43, // 25: api.manage.RunNetworkInstanceRequest.inst_id:type_name -> common.UUID + 2, // 26: api.manage.RunNetworkInstanceRequest.config:type_name -> api.manage.NetworkConfig + 1, // 27: api.manage.RunNetworkInstanceRequest.source:type_name -> api.manage.ConfigSource + 43, // 28: api.manage.RunNetworkInstanceResponse.inst_id:type_name -> common.UUID + 43, // 29: api.manage.RetainNetworkInstanceRequest.inst_ids:type_name -> common.UUID + 43, // 30: api.manage.RetainNetworkInstanceResponse.remain_inst_ids:type_name -> common.UUID + 43, // 31: api.manage.CollectNetworkInfoRequest.inst_ids:type_name -> common.UUID + 11, // 32: api.manage.CollectNetworkInfoResponse.info:type_name -> api.manage.NetworkInstanceRunningInfoMap + 43, // 33: api.manage.ListNetworkInstanceResponse.inst_ids:type_name -> common.UUID + 43, // 34: api.manage.DeleteNetworkInstanceRequest.inst_ids:type_name -> common.UUID + 43, // 35: api.manage.DeleteNetworkInstanceResponse.remain_inst_ids:type_name -> common.UUID + 43, // 36: api.manage.GetNetworkInstanceConfigRequest.inst_id:type_name -> common.UUID + 2, // 37: api.manage.GetNetworkInstanceConfigResponse.config:type_name -> api.manage.NetworkConfig + 1, // 38: api.manage.GetNetworkInstanceConfigResponse.source:type_name -> api.manage.ConfigSource + 43, // 39: api.manage.ListNetworkInstanceMetaRequest.inst_ids:type_name -> common.UUID + 12, // 40: api.manage.ListNetworkInstanceMetaResponse.metas:type_name -> api.manage.NetworkMeta + 10, // 41: api.manage.NetworkInstanceRunningInfoMap.MapEntry.value:type_name -> api.manage.NetworkInstanceRunningInfo + 13, // 42: api.manage.WebClientService.ValidateConfig:input_type -> api.manage.ValidateConfigRequest + 15, // 43: api.manage.WebClientService.RunNetworkInstance:input_type -> api.manage.RunNetworkInstanceRequest + 17, // 44: api.manage.WebClientService.RetainNetworkInstance:input_type -> api.manage.RetainNetworkInstanceRequest + 19, // 45: api.manage.WebClientService.CollectNetworkInfo:input_type -> api.manage.CollectNetworkInfoRequest + 21, // 46: api.manage.WebClientService.ListNetworkInstance:input_type -> api.manage.ListNetworkInstanceRequest + 23, // 47: api.manage.WebClientService.DeleteNetworkInstance:input_type -> api.manage.DeleteNetworkInstanceRequest + 25, // 48: api.manage.WebClientService.GetNetworkInstanceConfig:input_type -> api.manage.GetNetworkInstanceConfigRequest + 27, // 49: api.manage.WebClientService.ListNetworkInstanceMeta:input_type -> api.manage.ListNetworkInstanceMetaRequest + 14, // 50: api.manage.WebClientService.ValidateConfig:output_type -> api.manage.ValidateConfigResponse + 16, // 51: api.manage.WebClientService.RunNetworkInstance:output_type -> api.manage.RunNetworkInstanceResponse + 18, // 52: api.manage.WebClientService.RetainNetworkInstance:output_type -> api.manage.RetainNetworkInstanceResponse + 20, // 53: api.manage.WebClientService.CollectNetworkInfo:output_type -> api.manage.CollectNetworkInfoResponse + 22, // 54: api.manage.WebClientService.ListNetworkInstance:output_type -> api.manage.ListNetworkInstanceResponse + 24, // 55: api.manage.WebClientService.DeleteNetworkInstance:output_type -> api.manage.DeleteNetworkInstanceResponse + 26, // 56: api.manage.WebClientService.GetNetworkInstanceConfig:output_type -> api.manage.GetNetworkInstanceConfigResponse + 28, // 57: api.manage.WebClientService.ListNetworkInstanceMeta:output_type -> api.manage.ListNetworkInstanceMetaResponse + 50, // [50:58] is the sub-list for method output_type + 42, // [42:50] is the sub-list for method input_type + 42, // [42:42] is the sub-list for extension type_name + 42, // [42:42] is the sub-list for extension extendee + 0, // [0:42] is the sub-list for field type_name } func init() { file_api_manage_proto_init() } diff --git a/easytier-go/proto/common/common.pb.go b/easytier-go/proto/common/common.pb.go index 30120e5a..eea6b4a0 100644 --- a/easytier-go/proto/common/common.pb.go +++ b/easytier-go/proto/common/common.pb.go @@ -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.2 // source: common.proto package common @@ -242,6 +242,8 @@ type Flags struct { // disable symmetric nat hole punching, treat symmetric as cone when enabled DisableSymHolePunching bool `protobuf:"varint,30,opt,name=disable_sym_hole_punching,json=disableSymHolePunching,proto3" json:"disable_sym_hole_punching,omitempty"` // tld dns zone for magic dns + // + // Deprecated: Marked as deprecated in common.proto. TldDnsZone string `protobuf:"bytes,31,opt,name=tld_dns_zone,json=tldDnsZone,proto3" json:"tld_dns_zone,omitempty"` P2POnly bool `protobuf:"varint,32,opt,name=p2p_only,json=p2pOnly,proto3" json:"p2p_only,omitempty"` DisableTcpHolePunching bool `protobuf:"varint,34,opt,name=disable_tcp_hole_punching,json=disableTcpHolePunching,proto3" json:"disable_tcp_hole_punching,omitempty"` @@ -519,6 +521,7 @@ func (x *Flags) GetDisableSymHolePunching() bool { return false } +// Deprecated: Marked as deprecated in common.proto. func (x *Flags) GetTldDnsZone() string { if x != nil { return x.TldDnsZone @@ -2144,11 +2147,57 @@ func (x *SecureModeConfig) GetLocalPublicKey() string { return "" } +// Collection wrappers preserve omitted versus explicitly empty input. +// The items field number and wire type are part of the configuration contract. +type RepeatedString struct { + state protoimpl.MessageState `protogen:"open.v1"` + Items []string `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RepeatedString) Reset() { + *x = RepeatedString{} + mi := &file_common_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RepeatedString) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RepeatedString) ProtoMessage() {} + +func (x *RepeatedString) ProtoReflect() protoreflect.Message { + mi := &file_common_proto_msgTypes[24] + 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 RepeatedString.ProtoReflect.Descriptor instead. +func (*RepeatedString) Descriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{24} +} + +func (x *RepeatedString) GetItems() []string { + if x != nil { + return x.Items + } + return nil +} + var File_common_proto protoreflect.FileDescriptor const file_common_proto_rawDesc = "" + "\n" + - "\fcommon.proto\x12\x06common\x1a\x11annotations.proto\x1a\verror.proto\"\x8a\x14\n" + + "\fcommon.proto\x12\x06common\x1a\x11annotations.proto\x1a\verror.proto\"\x87\x14\n" + "\x05Flags\x124\n" + "\x10default_protocol\x18\x01 \x01(\tB\t\x8a\xb5\x18\x05\n" + "\x03tcpR\x0fdefaultProtocol\x12#\n" + @@ -2226,9 +2275,10 @@ const file_common_proto_rawDesc = "" + "\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" + + "\x05falseR\x16disableSymHolePunching\x12,\n" + + "\ftld_dns_zone\x18\x1f \x01(\tB\n" + + "\x8a\xb5\x18\x04\n" + + "\x00 \x01\x18\x01R\n" + "tldDnsZone\x12&\n" + "\bp2p_only\x18 \x01(\bB\v\x8a\xb5\x18\a\n" + "\x05falseR\ap2pOnly\x12F\n" + @@ -2381,7 +2431,9 @@ const file_common_proto_rawDesc = "" + "\x11local_private_key\x18\x02 \x01(\tH\x00R\x0flocalPrivateKey\x88\x01\x01\x12-\n" + "\x10local_public_key\x18\x03 \x01(\tH\x01R\x0elocalPublicKey\x88\x01\x01B\x14\n" + "\x12_local_private_keyB\x13\n" + - "\x11_local_public_key*4\n" + + "\x11_local_public_key\"&\n" + + "\x0eRepeatedString\x12\x14\n" + + "\x05items\x18\x01 \x03(\tR\x05items*4\n" + "\x11CompressionAlgoPb\x12\v\n" + "\aInvalid\x10\x00\x12\b\n" + "\x04None\x10\x01\x12\b\n" + @@ -2416,7 +2468,7 @@ func file_common_proto_rawDescGZIP() []byte { } var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 24) +var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 25) var file_common_proto_goTypes = []any{ (CompressionAlgoPb)(0), // 0: common.CompressionAlgoPb (NatType)(0), // 1: common.NatType @@ -2445,14 +2497,15 @@ var file_common_proto_goTypes = []any{ (*PortForwardConfigPb)(nil), // 24: common.PortForwardConfigPb (*ProxyDstInfo)(nil), // 25: common.ProxyDstInfo (*SecureModeConfig)(nil), // 26: common.SecureModeConfig - (*error1.Error)(nil), // 27: error.Error + (*RepeatedString)(nil), // 27: common.RepeatedString + (*error1.Error)(nil), // 28: error.Error } var file_common_proto_depIdxs = []int32{ 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 - 27, // 4: common.RpcResponse.error:type_name -> error.Error + 28, // 4: common.RpcResponse.error:type_name -> error.Error 0, // 5: common.RpcCompressionInfo.algo:type_name -> common.CompressionAlgoPb 0, // 6: common.RpcCompressionInfo.accepted_algo:type_name -> common.CompressionAlgoPb 4, // 7: common.RpcPacket.descriptor:type_name -> common.RpcDescriptor @@ -2508,7 +2561,7 @@ func file_common_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)), NumEnums: 3, - NumMessages: 24, + NumMessages: 25, NumExtensions: 0, NumServices: 0, }, diff --git a/easytier-go/proto/core_config/core_config.pb.go b/easytier-go/proto/core_config/core_config.pb.go new file mode 100644 index 00000000..10f3019c --- /dev/null +++ b/easytier-go/proto/core_config/core_config.pb.go @@ -0,0 +1,845 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v7.36.2 +// source: core_config.proto + +package core_config + +import ( + common "github.com/easytier/easytier/easytier-go/proto/common" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + 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) +) + +type CoreConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Node *NodeConfig `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + Routes *RouteConfig `protobuf:"bytes,2,opt,name=routes,proto3" json:"routes,omitempty"` + PeerPolicy *PeerPolicyConfig `protobuf:"bytes,3,opt,name=peer_policy,json=peerPolicy,proto3" json:"peer_policy,omitempty"` + Traffic *TrafficConfig `protobuf:"bytes,4,opt,name=traffic,proto3" json:"traffic,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CoreConfig) Reset() { + *x = CoreConfig{} + mi := &file_core_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CoreConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CoreConfig) ProtoMessage() {} + +func (x *CoreConfig) ProtoReflect() protoreflect.Message { + mi := &file_core_config_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 CoreConfig.ProtoReflect.Descriptor instead. +func (*CoreConfig) Descriptor() ([]byte, []int) { + return file_core_config_proto_rawDescGZIP(), []int{0} +} + +func (x *CoreConfig) GetNode() *NodeConfig { + if x != nil { + return x.Node + } + return nil +} + +func (x *CoreConfig) GetRoutes() *RouteConfig { + if x != nil { + return x.Routes + } + return nil +} + +func (x *CoreConfig) GetPeerPolicy() *PeerPolicyConfig { + if x != nil { + return x.PeerPolicy + } + return nil +} + +func (x *CoreConfig) GetTraffic() *TrafficConfig { + if x != nil { + return x.Traffic + } + return nil +} + +type NodeConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + PeerId *uint32 `protobuf:"varint,1,opt,name=peer_id,json=peerId,proto3,oneof" json:"peer_id,omitempty"` + InstanceId *common.UUID `protobuf:"bytes,2,opt,name=instance_id,json=instanceId,proto3,oneof" json:"instance_id,omitempty"` + Hostname *string `protobuf:"bytes,3,opt,name=hostname,proto3,oneof" json:"hostname,omitempty"` + NetworkName string `protobuf:"bytes,4,opt,name=network_name,json=networkName,proto3" json:"network_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NodeConfig) Reset() { + *x = NodeConfig{} + mi := &file_core_config_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NodeConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NodeConfig) ProtoMessage() {} + +func (x *NodeConfig) ProtoReflect() protoreflect.Message { + mi := &file_core_config_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 NodeConfig.ProtoReflect.Descriptor instead. +func (*NodeConfig) Descriptor() ([]byte, []int) { + return file_core_config_proto_rawDescGZIP(), []int{1} +} + +func (x *NodeConfig) GetPeerId() uint32 { + if x != nil && x.PeerId != nil { + return *x.PeerId + } + return 0 +} + +func (x *NodeConfig) GetInstanceId() *common.UUID { + if x != nil { + return x.InstanceId + } + return nil +} + +func (x *NodeConfig) GetHostname() string { + if x != nil && x.Hostname != nil { + return *x.Hostname + } + return "" +} + +func (x *NodeConfig) GetNetworkName() string { + if x != nil { + return x.NetworkName + } + return "" +} + +type RouteConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ipv4 *IpPrefix `protobuf:"bytes,1,opt,name=ipv4,proto3,oneof" json:"ipv4,omitempty"` + Ipv6 *IpPrefix `protobuf:"bytes,2,opt,name=ipv6,proto3,oneof" json:"ipv6,omitempty"` + AdvertisedRoutes []*IpPrefix `protobuf:"bytes,3,rep,name=advertised_routes,json=advertisedRoutes,proto3" json:"advertised_routes,omitempty"` + ProxyNetworks []*ProxyNetworkConfig `protobuf:"bytes,4,rep,name=proxy_networks,json=proxyNetworks,proto3" json:"proxy_networks,omitempty"` + ForeignNetworks []*ForeignNetworkConfig `protobuf:"bytes,5,rep,name=foreign_networks,json=foreignNetworks,proto3" json:"foreign_networks,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RouteConfig) Reset() { + *x = RouteConfig{} + mi := &file_core_config_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RouteConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RouteConfig) ProtoMessage() {} + +func (x *RouteConfig) ProtoReflect() protoreflect.Message { + mi := &file_core_config_proto_msgTypes[2] + 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 RouteConfig.ProtoReflect.Descriptor instead. +func (*RouteConfig) Descriptor() ([]byte, []int) { + return file_core_config_proto_rawDescGZIP(), []int{2} +} + +func (x *RouteConfig) GetIpv4() *IpPrefix { + if x != nil { + return x.Ipv4 + } + return nil +} + +func (x *RouteConfig) GetIpv6() *IpPrefix { + if x != nil { + return x.Ipv6 + } + return nil +} + +func (x *RouteConfig) GetAdvertisedRoutes() []*IpPrefix { + if x != nil { + return x.AdvertisedRoutes + } + return nil +} + +func (x *RouteConfig) GetProxyNetworks() []*ProxyNetworkConfig { + if x != nil { + return x.ProxyNetworks + } + return nil +} + +func (x *RouteConfig) GetForeignNetworks() []*ForeignNetworkConfig { + if x != nil { + return x.ForeignNetworks + } + return nil +} + +type IpPrefix struct { + state protoimpl.MessageState `protogen:"open.v1"` + Address *common.IpAddr `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + PrefixLen uint32 `protobuf:"varint,2,opt,name=prefix_len,json=prefixLen,proto3" json:"prefix_len,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IpPrefix) Reset() { + *x = IpPrefix{} + mi := &file_core_config_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IpPrefix) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IpPrefix) ProtoMessage() {} + +func (x *IpPrefix) ProtoReflect() protoreflect.Message { + mi := &file_core_config_proto_msgTypes[3] + 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 IpPrefix.ProtoReflect.Descriptor instead. +func (*IpPrefix) Descriptor() ([]byte, []int) { + return file_core_config_proto_rawDescGZIP(), []int{3} +} + +func (x *IpPrefix) GetAddress() *common.IpAddr { + if x != nil { + return x.Address + } + return nil +} + +func (x *IpPrefix) GetPrefixLen() uint32 { + if x != nil { + return x.PrefixLen + } + return 0 +} + +type ProxyNetworkConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Real *IpPrefix `protobuf:"bytes,1,opt,name=real,proto3" json:"real,omitempty"` + Mapped *IpPrefix `protobuf:"bytes,2,opt,name=mapped,proto3,oneof" json:"mapped,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProxyNetworkConfig) Reset() { + *x = ProxyNetworkConfig{} + mi := &file_core_config_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProxyNetworkConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProxyNetworkConfig) ProtoMessage() {} + +func (x *ProxyNetworkConfig) ProtoReflect() protoreflect.Message { + mi := &file_core_config_proto_msgTypes[4] + 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 ProxyNetworkConfig.ProtoReflect.Descriptor instead. +func (*ProxyNetworkConfig) Descriptor() ([]byte, []int) { + return file_core_config_proto_rawDescGZIP(), []int{4} +} + +func (x *ProxyNetworkConfig) GetReal() *IpPrefix { + if x != nil { + return x.Real + } + return nil +} + +func (x *ProxyNetworkConfig) GetMapped() *IpPrefix { + if x != nil { + return x.Mapped + } + return nil +} + +type ForeignNetworkConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Cidrs []*IpPrefix `protobuf:"bytes,2,rep,name=cidrs,proto3" json:"cidrs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ForeignNetworkConfig) Reset() { + *x = ForeignNetworkConfig{} + mi := &file_core_config_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ForeignNetworkConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ForeignNetworkConfig) ProtoMessage() {} + +func (x *ForeignNetworkConfig) ProtoReflect() protoreflect.Message { + mi := &file_core_config_proto_msgTypes[5] + 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 ForeignNetworkConfig.ProtoReflect.Descriptor instead. +func (*ForeignNetworkConfig) Descriptor() ([]byte, []int) { + return file_core_config_proto_rawDescGZIP(), []int{5} +} + +func (x *ForeignNetworkConfig) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ForeignNetworkConfig) GetCidrs() []*IpPrefix { + if x != nil { + return x.Cidrs + } + return nil +} + +type PeerPolicyConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + P2PEnabled *bool `protobuf:"varint,1,opt,name=p2p_enabled,json=p2pEnabled,proto3,oneof" json:"p2p_enabled,omitempty"` + RelayPeerRpc *bool `protobuf:"varint,2,opt,name=relay_peer_rpc,json=relayPeerRpc,proto3,oneof" json:"relay_peer_rpc,omitempty"` + RelayData *bool `protobuf:"varint,3,opt,name=relay_data,json=relayData,proto3,oneof" json:"relay_data,omitempty"` + LatencyFirst *bool `protobuf:"varint,4,opt,name=latency_first,json=latencyFirst,proto3,oneof" json:"latency_first,omitempty"` + EncryptionRequired *bool `protobuf:"varint,5,opt,name=encryption_required,json=encryptionRequired,proto3,oneof" json:"encryption_required,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PeerPolicyConfig) Reset() { + *x = PeerPolicyConfig{} + mi := &file_core_config_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PeerPolicyConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeerPolicyConfig) ProtoMessage() {} + +func (x *PeerPolicyConfig) ProtoReflect() protoreflect.Message { + mi := &file_core_config_proto_msgTypes[6] + 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 PeerPolicyConfig.ProtoReflect.Descriptor instead. +func (*PeerPolicyConfig) Descriptor() ([]byte, []int) { + return file_core_config_proto_rawDescGZIP(), []int{6} +} + +func (x *PeerPolicyConfig) GetP2PEnabled() bool { + if x != nil && x.P2PEnabled != nil { + return *x.P2PEnabled + } + return false +} + +func (x *PeerPolicyConfig) GetRelayPeerRpc() bool { + if x != nil && x.RelayPeerRpc != nil { + return *x.RelayPeerRpc + } + return false +} + +func (x *PeerPolicyConfig) GetRelayData() bool { + if x != nil && x.RelayData != nil { + return *x.RelayData + } + return false +} + +func (x *PeerPolicyConfig) GetLatencyFirst() bool { + if x != nil && x.LatencyFirst != nil { + return *x.LatencyFirst + } + return false +} + +func (x *PeerPolicyConfig) GetEncryptionRequired() bool { + if x != nil && x.EncryptionRequired != nil { + return *x.EncryptionRequired + } + return false +} + +type TrafficConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mtu *uint32 `protobuf:"varint,1,opt,name=mtu,proto3,oneof" json:"mtu,omitempty"` + InstanceRecvBpsLimit *uint64 `protobuf:"varint,2,opt,name=instance_recv_bps_limit,json=instanceRecvBpsLimit,proto3,oneof" json:"instance_recv_bps_limit,omitempty"` + ForeignRelayBpsLimit *uint64 `protobuf:"varint,3,opt,name=foreign_relay_bps_limit,json=foreignRelayBpsLimit,proto3,oneof" json:"foreign_relay_bps_limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TrafficConfig) Reset() { + *x = TrafficConfig{} + mi := &file_core_config_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TrafficConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TrafficConfig) ProtoMessage() {} + +func (x *TrafficConfig) ProtoReflect() protoreflect.Message { + mi := &file_core_config_proto_msgTypes[7] + 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 TrafficConfig.ProtoReflect.Descriptor instead. +func (*TrafficConfig) Descriptor() ([]byte, []int) { + return file_core_config_proto_rawDescGZIP(), []int{7} +} + +func (x *TrafficConfig) GetMtu() uint32 { + if x != nil && x.Mtu != nil { + return *x.Mtu + } + return 0 +} + +func (x *TrafficConfig) GetInstanceRecvBpsLimit() uint64 { + if x != nil && x.InstanceRecvBpsLimit != nil { + return *x.InstanceRecvBpsLimit + } + return 0 +} + +func (x *TrafficConfig) GetForeignRelayBpsLimit() uint64 { + if x != nil && x.ForeignRelayBpsLimit != nil { + return *x.ForeignRelayBpsLimit + } + return 0 +} + +// Raw file/management input. Domain parsing and defaults belong to Raw -> Parsed. +// Future generators may rename these messages, preserving field numbers, types +// and collection wrapper nesting. +type DnsConfigRaw struct { + state protoimpl.MessageState `protogen:"open.v1"` + Zone *RepeatedDnsZoneConfigRaw `protobuf:"bytes,1,opt,name=zone,proto3" json:"zone,omitempty"` + Domain *string `protobuf:"bytes,2,opt,name=domain,proto3,oneof" json:"domain,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DnsConfigRaw) Reset() { + *x = DnsConfigRaw{} + mi := &file_core_config_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DnsConfigRaw) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DnsConfigRaw) ProtoMessage() {} + +func (x *DnsConfigRaw) ProtoReflect() protoreflect.Message { + mi := &file_core_config_proto_msgTypes[8] + 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 DnsConfigRaw.ProtoReflect.Descriptor instead. +func (*DnsConfigRaw) Descriptor() ([]byte, []int) { + return file_core_config_proto_rawDescGZIP(), []int{8} +} + +func (x *DnsConfigRaw) GetZone() *RepeatedDnsZoneConfigRaw { + if x != nil { + return x.Zone + } + return nil +} + +func (x *DnsConfigRaw) GetDomain() string { + if x != nil && x.Domain != nil { + return *x.Domain + } + return "" +} + +type RepeatedDnsZoneConfigRaw struct { + state protoimpl.MessageState `protogen:"open.v1"` + Items []*DnsZoneConfigRaw `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RepeatedDnsZoneConfigRaw) Reset() { + *x = RepeatedDnsZoneConfigRaw{} + mi := &file_core_config_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RepeatedDnsZoneConfigRaw) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RepeatedDnsZoneConfigRaw) ProtoMessage() {} + +func (x *RepeatedDnsZoneConfigRaw) ProtoReflect() protoreflect.Message { + mi := &file_core_config_proto_msgTypes[9] + 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 RepeatedDnsZoneConfigRaw.ProtoReflect.Descriptor instead. +func (*RepeatedDnsZoneConfigRaw) Descriptor() ([]byte, []int) { + return file_core_config_proto_rawDescGZIP(), []int{9} +} + +func (x *RepeatedDnsZoneConfigRaw) GetItems() []*DnsZoneConfigRaw { + if x != nil { + return x.Items + } + return nil +} + +type DnsZoneConfigRaw struct { + state protoimpl.MessageState `protogen:"open.v1"` + Origin string `protobuf:"bytes,1,opt,name=origin,proto3" json:"origin,omitempty"` + Ttl *uint32 `protobuf:"varint,2,opt,name=ttl,proto3,oneof" json:"ttl,omitempty"` + Records *common.RepeatedString `protobuf:"bytes,3,opt,name=records,proto3" json:"records,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DnsZoneConfigRaw) Reset() { + *x = DnsZoneConfigRaw{} + mi := &file_core_config_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DnsZoneConfigRaw) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DnsZoneConfigRaw) ProtoMessage() {} + +func (x *DnsZoneConfigRaw) ProtoReflect() protoreflect.Message { + mi := &file_core_config_proto_msgTypes[10] + 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 DnsZoneConfigRaw.ProtoReflect.Descriptor instead. +func (*DnsZoneConfigRaw) Descriptor() ([]byte, []int) { + return file_core_config_proto_rawDescGZIP(), []int{10} +} + +func (x *DnsZoneConfigRaw) GetOrigin() string { + if x != nil { + return x.Origin + } + return "" +} + +func (x *DnsZoneConfigRaw) GetTtl() uint32 { + if x != nil && x.Ttl != nil { + return *x.Ttl + } + return 0 +} + +func (x *DnsZoneConfigRaw) GetRecords() *common.RepeatedString { + if x != nil { + return x.Records + } + return nil +} + +var File_core_config_proto protoreflect.FileDescriptor + +const file_core_config_proto_rawDesc = "" + + "\n" + + "\x11core_config.proto\x12\vcore_config\x1a\fcommon.proto\"\xe1\x01\n" + + "\n" + + "CoreConfig\x12+\n" + + "\x04node\x18\x01 \x01(\v2\x17.core_config.NodeConfigR\x04node\x120\n" + + "\x06routes\x18\x02 \x01(\v2\x18.core_config.RouteConfigR\x06routes\x12>\n" + + "\vpeer_policy\x18\x03 \x01(\v2\x1d.core_config.PeerPolicyConfigR\n" + + "peerPolicy\x124\n" + + "\atraffic\x18\x04 \x01(\v2\x1a.core_config.TrafficConfigR\atraffic\"\xcb\x01\n" + + "\n" + + "NodeConfig\x12\x1c\n" + + "\apeer_id\x18\x01 \x01(\rH\x00R\x06peerId\x88\x01\x01\x122\n" + + "\vinstance_id\x18\x02 \x01(\v2\f.common.UUIDH\x01R\n" + + "instanceId\x88\x01\x01\x12\x1f\n" + + "\bhostname\x18\x03 \x01(\tH\x02R\bhostname\x88\x01\x01\x12!\n" + + "\fnetwork_name\x18\x04 \x01(\tR\vnetworkNameB\n" + + "\n" + + "\b_peer_idB\x0e\n" + + "\f_instance_idB\v\n" + + "\t_hostname\"\xd9\x02\n" + + "\vRouteConfig\x12.\n" + + "\x04ipv4\x18\x01 \x01(\v2\x15.core_config.IpPrefixH\x00R\x04ipv4\x88\x01\x01\x12.\n" + + "\x04ipv6\x18\x02 \x01(\v2\x15.core_config.IpPrefixH\x01R\x04ipv6\x88\x01\x01\x12B\n" + + "\x11advertised_routes\x18\x03 \x03(\v2\x15.core_config.IpPrefixR\x10advertisedRoutes\x12F\n" + + "\x0eproxy_networks\x18\x04 \x03(\v2\x1f.core_config.ProxyNetworkConfigR\rproxyNetworks\x12L\n" + + "\x10foreign_networks\x18\x05 \x03(\v2!.core_config.ForeignNetworkConfigR\x0fforeignNetworksB\a\n" + + "\x05_ipv4B\a\n" + + "\x05_ipv6\"S\n" + + "\bIpPrefix\x12(\n" + + "\aaddress\x18\x01 \x01(\v2\x0e.common.IpAddrR\aaddress\x12\x1d\n" + + "\n" + + "prefix_len\x18\x02 \x01(\rR\tprefixLen\"~\n" + + "\x12ProxyNetworkConfig\x12)\n" + + "\x04real\x18\x01 \x01(\v2\x15.core_config.IpPrefixR\x04real\x122\n" + + "\x06mapped\x18\x02 \x01(\v2\x15.core_config.IpPrefixH\x00R\x06mapped\x88\x01\x01B\t\n" + + "\a_mapped\"W\n" + + "\x14ForeignNetworkConfig\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12+\n" + + "\x05cidrs\x18\x02 \x03(\v2\x15.core_config.IpPrefixR\x05cidrs\"\xc3\x02\n" + + "\x10PeerPolicyConfig\x12$\n" + + "\vp2p_enabled\x18\x01 \x01(\bH\x00R\n" + + "p2pEnabled\x88\x01\x01\x12)\n" + + "\x0erelay_peer_rpc\x18\x02 \x01(\bH\x01R\frelayPeerRpc\x88\x01\x01\x12\"\n" + + "\n" + + "relay_data\x18\x03 \x01(\bH\x02R\trelayData\x88\x01\x01\x12(\n" + + "\rlatency_first\x18\x04 \x01(\bH\x03R\flatencyFirst\x88\x01\x01\x124\n" + + "\x13encryption_required\x18\x05 \x01(\bH\x04R\x12encryptionRequired\x88\x01\x01B\x0e\n" + + "\f_p2p_enabledB\x11\n" + + "\x0f_relay_peer_rpcB\r\n" + + "\v_relay_dataB\x10\n" + + "\x0e_latency_firstB\x16\n" + + "\x14_encryption_required\"\xde\x01\n" + + "\rTrafficConfig\x12\x15\n" + + "\x03mtu\x18\x01 \x01(\rH\x00R\x03mtu\x88\x01\x01\x12:\n" + + "\x17instance_recv_bps_limit\x18\x02 \x01(\x04H\x01R\x14instanceRecvBpsLimit\x88\x01\x01\x12:\n" + + "\x17foreign_relay_bps_limit\x18\x03 \x01(\x04H\x02R\x14foreignRelayBpsLimit\x88\x01\x01B\x06\n" + + "\x04_mtuB\x1a\n" + + "\x18_instance_recv_bps_limitB\x1a\n" + + "\x18_foreign_relay_bps_limit\"q\n" + + "\fDnsConfigRaw\x129\n" + + "\x04zone\x18\x01 \x01(\v2%.core_config.RepeatedDnsZoneConfigRawR\x04zone\x12\x1b\n" + + "\x06domain\x18\x02 \x01(\tH\x00R\x06domain\x88\x01\x01B\t\n" + + "\a_domain\"O\n" + + "\x18RepeatedDnsZoneConfigRaw\x123\n" + + "\x05items\x18\x01 \x03(\v2\x1d.core_config.DnsZoneConfigRawR\x05items\"{\n" + + "\x10DnsZoneConfigRaw\x12\x16\n" + + "\x06origin\x18\x01 \x01(\tR\x06origin\x12\x15\n" + + "\x03ttl\x18\x02 \x01(\rH\x00R\x03ttl\x88\x01\x01\x120\n" + + "\arecords\x18\x03 \x01(\v2\x16.common.RepeatedStringR\arecordsB\x06\n" + + "\x04_ttlb\x06proto3" + +var ( + file_core_config_proto_rawDescOnce sync.Once + file_core_config_proto_rawDescData []byte +) + +func file_core_config_proto_rawDescGZIP() []byte { + file_core_config_proto_rawDescOnce.Do(func() { + file_core_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_core_config_proto_rawDesc), len(file_core_config_proto_rawDesc))) + }) + return file_core_config_proto_rawDescData +} + +var file_core_config_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_core_config_proto_goTypes = []any{ + (*CoreConfig)(nil), // 0: core_config.CoreConfig + (*NodeConfig)(nil), // 1: core_config.NodeConfig + (*RouteConfig)(nil), // 2: core_config.RouteConfig + (*IpPrefix)(nil), // 3: core_config.IpPrefix + (*ProxyNetworkConfig)(nil), // 4: core_config.ProxyNetworkConfig + (*ForeignNetworkConfig)(nil), // 5: core_config.ForeignNetworkConfig + (*PeerPolicyConfig)(nil), // 6: core_config.PeerPolicyConfig + (*TrafficConfig)(nil), // 7: core_config.TrafficConfig + (*DnsConfigRaw)(nil), // 8: core_config.DnsConfigRaw + (*RepeatedDnsZoneConfigRaw)(nil), // 9: core_config.RepeatedDnsZoneConfigRaw + (*DnsZoneConfigRaw)(nil), // 10: core_config.DnsZoneConfigRaw + (*common.UUID)(nil), // 11: common.UUID + (*common.IpAddr)(nil), // 12: common.IpAddr + (*common.RepeatedString)(nil), // 13: common.RepeatedString +} +var file_core_config_proto_depIdxs = []int32{ + 1, // 0: core_config.CoreConfig.node:type_name -> core_config.NodeConfig + 2, // 1: core_config.CoreConfig.routes:type_name -> core_config.RouteConfig + 6, // 2: core_config.CoreConfig.peer_policy:type_name -> core_config.PeerPolicyConfig + 7, // 3: core_config.CoreConfig.traffic:type_name -> core_config.TrafficConfig + 11, // 4: core_config.NodeConfig.instance_id:type_name -> common.UUID + 3, // 5: core_config.RouteConfig.ipv4:type_name -> core_config.IpPrefix + 3, // 6: core_config.RouteConfig.ipv6:type_name -> core_config.IpPrefix + 3, // 7: core_config.RouteConfig.advertised_routes:type_name -> core_config.IpPrefix + 4, // 8: core_config.RouteConfig.proxy_networks:type_name -> core_config.ProxyNetworkConfig + 5, // 9: core_config.RouteConfig.foreign_networks:type_name -> core_config.ForeignNetworkConfig + 12, // 10: core_config.IpPrefix.address:type_name -> common.IpAddr + 3, // 11: core_config.ProxyNetworkConfig.real:type_name -> core_config.IpPrefix + 3, // 12: core_config.ProxyNetworkConfig.mapped:type_name -> core_config.IpPrefix + 3, // 13: core_config.ForeignNetworkConfig.cidrs:type_name -> core_config.IpPrefix + 9, // 14: core_config.DnsConfigRaw.zone:type_name -> core_config.RepeatedDnsZoneConfigRaw + 10, // 15: core_config.RepeatedDnsZoneConfigRaw.items:type_name -> core_config.DnsZoneConfigRaw + 13, // 16: core_config.DnsZoneConfigRaw.records:type_name -> common.RepeatedString + 17, // [17:17] is the sub-list for method output_type + 17, // [17:17] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name +} + +func init() { file_core_config_proto_init() } +func file_core_config_proto_init() { + if File_core_config_proto != nil { + return + } + file_core_config_proto_msgTypes[1].OneofWrappers = []any{} + file_core_config_proto_msgTypes[2].OneofWrappers = []any{} + file_core_config_proto_msgTypes[4].OneofWrappers = []any{} + file_core_config_proto_msgTypes[6].OneofWrappers = []any{} + file_core_config_proto_msgTypes[7].OneofWrappers = []any{} + file_core_config_proto_msgTypes[8].OneofWrappers = []any{} + file_core_config_proto_msgTypes[10].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_core_config_proto_rawDesc), len(file_core_config_proto_rawDesc)), + NumEnums: 0, + NumMessages: 11, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_core_config_proto_goTypes, + DependencyIndexes: file_core_config_proto_depIdxs, + MessageInfos: file_core_config_proto_msgTypes, + }.Build() + File_core_config_proto = out.File + file_core_config_proto_goTypes = nil + file_core_config_proto_depIdxs = nil +} diff --git a/easytier-go/script/generate-proto.sh b/easytier-go/script/generate-proto.sh index c98ca74f..dda2f821 100755 --- a/easytier-go/script/generate-proto.sh +++ b/easytier-go/script/generate-proto.sh @@ -11,8 +11,8 @@ if [[ ! -f "${proto_root}/api_instance.proto" ]]; then exit 1 fi -if [[ "$(protoc --version)" != "libprotoc 35.1" && "$(protoc --version)" != "libprotoc 36.1" ]]; then - echo "protoc 35.1 or 36.1 is required" >&2 +if [[ "$(protoc --version)" != "libprotoc 35.1" && "$(protoc --version)" != "libprotoc 36.1" && "$(protoc --version)" != "libprotoc 36.2" ]]; then + echo "protoc 35.1, 36.1 or 36.2 is required" >&2 exit 1 fi if [[ "$(protoc-gen-go --version)" != "protoc-gen-go v1.36.11" ]]; then @@ -25,6 +25,7 @@ protoc \ --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=Mcore_config.proto=github.com/easytier/easytier/easytier-go/proto/core_config \ --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 \ @@ -35,6 +36,7 @@ protoc \ --go_opt=Mweb.proto=github.com/easytier/easytier/easytier-go/proto/web \ "${proto_root}/annotations.proto" \ "${proto_root}/common.proto" \ + "${proto_root}/core_config.proto" \ "${proto_root}/error.proto" \ "${proto_root}/acl.proto" \ "${proto_root}/peer_rpc.proto" \ diff --git a/easytier-proto/build/main.rs b/easytier-proto/build/main.rs index 9ed1e94b..871640fd 100644 --- a/easytier-proto/build/main.rs +++ b/easytier-proto/build/main.rs @@ -178,6 +178,16 @@ fn main() -> anyhow::Result<()> { ) .field_attribute(".common.CompressionAlgoPb.Invalid", "#[strum(disabled)]"); + let repeated_types = [ + ".common.RepeatedString", + ".core_config.RepeatedDnsZoneConfigRaw", + ]; + for ty in repeated_types { + config + .type_attribute(ty, "#[derive(serde::Serialize, serde::Deserialize)]") + .type_attribute(ty, "#[serde(transparent)]"); + } + 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, @@ -240,6 +250,7 @@ fn main() -> anyhow::Result<()> { pbjson_build::Builder::new() .register_descriptors(&descriptors)? .preserve_proto_field_names() + .exclude(repeated_types) .btree_map(["."]) .build(&["."])?; diff --git a/easytier-proto/proto/api_manage.proto b/easytier-proto/proto/api_manage.proto index 00e9afad..6e691a33 100644 --- a/easytier-proto/proto/api_manage.proto +++ b/easytier-proto/proto/api_manage.proto @@ -4,6 +4,7 @@ import "common.proto"; import "peer_rpc.proto"; import "api_instance.proto"; import "acl.proto"; +import "core_config.proto"; package api.manage; @@ -50,7 +51,7 @@ message NetworkConfig { optional bool use_smoltcp = 21; optional bool disable_ipv6 = 47; - optional string tld_dns_zone = 73; + optional string tld_dns_zone = 73 [deprecated = true]; optional bool enable_kcp_proxy = 22; optional bool disable_kcp_input = 23; optional bool disable_p2p = 24; @@ -111,6 +112,7 @@ message NetworkConfig { optional string dhcp_ipv4_cidr = 74; optional bool dhcpv6 = 75; optional string dhcp_ipv6_cidr = 76; + core_config.DnsConfigRaw dns = 77; } message ManagedCredentialConfig { diff --git a/easytier-proto/proto/common.proto b/easytier-proto/proto/common.proto index 3a1ae9c7..dfcb4c0c 100644 --- a/easytier-proto/proto/common.proto +++ b/easytier-proto/proto/common.proto @@ -65,8 +65,8 @@ message Flags { // disable symmetric nat hole punching, treat symmetric as cone when enabled bool disable_sym_hole_punching = 30 [(easytier.flag) = { default: "false" }]; - // tld dns zone for magic dns - string tld_dns_zone = 31 [(easytier.flag) = { default: "et.net.", form: true }]; + // tld dns zone for magic dns + string tld_dns_zone = 31 [deprecated = true, (easytier.flag) = { default: "", deprecated: true }]; bool p2p_only = 32 [(easytier.flag) = { default: "false" }]; @@ -284,3 +284,7 @@ message SecureModeConfig { // base64(X25519 public key), required if local_private_key is set optional string local_public_key = 3; } + +// Collection wrappers preserve omitted versus explicitly empty input. +// The items field number and wire type are part of the configuration contract. +message RepeatedString { repeated string items = 1; } diff --git a/easytier-proto/proto/core_config.proto b/easytier-proto/proto/core_config.proto index 14d70936..f58b70c8 100644 --- a/easytier-proto/proto/core_config.proto +++ b/easytier-proto/proto/core_config.proto @@ -54,3 +54,19 @@ message TrafficConfig { optional uint64 instance_recv_bps_limit = 2; optional uint64 foreign_relay_bps_limit = 3; } + +// Raw file/management input. Domain parsing and defaults belong to Raw -> Parsed. +// Future generators may rename these messages, preserving field numbers, types +// and collection wrapper nesting. +message DnsConfigRaw { + RepeatedDnsZoneConfigRaw zone = 1; + optional string domain = 2; +} + +message RepeatedDnsZoneConfigRaw { repeated DnsZoneConfigRaw items = 1; } + +message DnsZoneConfigRaw { + string origin = 1; + optional uint32 ttl = 2; + common.RepeatedString records = 3; +} diff --git a/easytier-proto/src/common.rs b/easytier-proto/src/common.rs index 1db0d3e2..8ff08c06 100644 --- a/easytier-proto/src/common.rs +++ b/easytier-proto/src/common.rs @@ -595,6 +595,12 @@ impl SecureModeConfig { } } +impl From> for RepeatedString { + fn from(items: Vec) -> Self { + Self { items } + } +} + #[cfg(test)] mod tests { use super::{TunnelInfo, Url, normalize_tunnel_url}; diff --git a/easytier-proto/src/core_config.rs b/easytier-proto/src/core_config.rs index 1076a787..52219299 100644 --- a/easytier-proto/src/core_config.rs +++ b/easytier-proto/src/core_config.rs @@ -1,3 +1,8 @@ include!(concat!(env!("OUT_DIR"), "/core_config.rs")); -#[cfg(feature = "json-rpc")] include!(concat!(env!("OUT_DIR"), "/core_config.serde.rs")); + +impl From> for RepeatedDnsZoneConfigRaw { + fn from(items: Vec) -> Self { + Self { items } + } +} diff --git a/easytier-web/frontend-lib/scripts/codegen-proto.mjs b/easytier-web/frontend-lib/scripts/codegen-proto.mjs index cce8ebce..0baa334c 100644 --- a/easytier-web/frontend-lib/scripts/codegen-proto.mjs +++ b/easytier-web/frontend-lib/scripts/codegen-proto.mjs @@ -17,6 +17,7 @@ const protobufTsPluginRoot = dirname(require.resolve('@protobuf-ts/plugin/packag const protoFiles = [ 'annotations.proto', 'common.proto', + 'core_config.proto', 'acl.proto', 'api_instance.proto', 'api_manage.proto', diff --git a/easytier-web/frontend-lib/scripts/test-network-config.mjs b/easytier-web/frontend-lib/scripts/test-network-config.mjs index 71622462..f92e8319 100644 --- a/easytier-web/frontend-lib/scripts/test-network-config.mjs +++ b/easytier-web/frontend-lib/scripts/test-network-config.mjs @@ -61,11 +61,12 @@ const BOOLEAN_CONFIG_FIELDS = [ 'disable_tcp_hole_punching', ] -const LEGACY_VPN_PORTAL_FIELDS = [ +const LEGACY_NETWORK_CONFIG_FIELDS = [ 'enable_vpn_portal', 'vpn_portal_listen_port', 'vpn_portal_client_network_addr', 'vpn_portal_client_network_len', + 'tld_dns_zone', ] function readGeneratedNetworkConfigFields() { @@ -168,7 +169,11 @@ function allFieldFixture() { mtu: 1280, mapped_listeners: ['tcp://127.0.0.1:13010'], enable_magic_dns: true, - tld_dns_zone: 'nas.internal.', + dns: { zone: [{ + origin: 'example.test.', + ttl: 0, + records: ['nas IN A 192.0.2.1'], + }] }, enable_private_mode: true, enable_quic_proxy: true, disable_quic_input: true, @@ -268,7 +273,7 @@ function allFieldFixture() { function assertFixtureCoversGeneratedFields() { const generatedFields = readGeneratedNetworkConfigFields() - .filter((field) => !LEGACY_VPN_PORTAL_FIELDS.includes(field)) + .filter((field) => !LEGACY_NETWORK_CONFIG_FIELDS.includes(field)) const fixtureFields = new Set(Object.keys(allFieldFixture())) const missing = generatedFields.filter((field) => !fixtureFields.has(field)) @@ -289,7 +294,7 @@ function assertFullFieldRoundTrip() { expectNoCamelCaseKeys(backend) for (const field of readGeneratedNetworkConfigFields() - .filter((field) => !LEGACY_VPN_PORTAL_FIELDS.includes(field))) { + .filter((field) => !LEGACY_NETWORK_CONFIG_FIELDS.includes(field))) { assert.ok(field in backend, `backend JSON should include fixture field ${field}`) } @@ -606,3 +611,27 @@ for (const test of tests) { test() console.log(`ok ${test.name}`) } + +// A collection wrapper, including an empty one, must survive form normalization. +for (const dns of [ + undefined, + {}, + { domain: 'mesh.test.' }, + { zone: [] }, + { domain: 'mesh.test.', zone: [{ origin: 'example.test.' }] }, + { zone: [{ origin: 'example.test.', ttl: 0, records: [] }] }, +]) { + const normalized = normalizeNetworkConfig({ ...DEFAULT_NETWORK_CONFIG(), dns }); + const backend = toBackendNetworkConfig(normalized); + const restored = normalizeNetworkConfig(backend); + assert.deepEqual(restored.dns, normalized.dns); + assert.equal(restored.dns !== undefined, dns !== undefined); + assert.equal(restored.dns?.domain, dns?.domain); + assert.equal(restored.dns?.zone !== undefined, dns?.zone !== undefined); + const before = dns?.zone?.[0]; + const after = restored.dns?.zone?.[0]; + if (before) { + assert.equal(after.records !== undefined, before.records !== undefined); + assert.equal(after.ttl, before.ttl); + } +} diff --git a/easytier-web/frontend-lib/src/components/Config.vue b/easytier-web/frontend-lib/src/components/Config.vue index ab21a562..ea89fef8 100644 --- a/easytier-web/frontend-lib/src/components/Config.vue +++ b/easytier-web/frontend-lib/src/components/Config.vue @@ -387,18 +387,6 @@ function removeVpnPortalClient(index: number) { -
-
-
- - -
- -
-
-
diff --git a/easytier-web/frontend-lib/src/locales/cn.yaml b/easytier-web/frontend-lib/src/locales/cn.yaml index 351fb6b3..8f86539b 100644 --- a/easytier-web/frontend-lib/src/locales/cn.yaml +++ b/easytier-web/frontend-lib/src/locales/cn.yaml @@ -255,11 +255,6 @@ disable_sym_hole_punching_help: 禁用对称NAT的打洞(生日攻击),将 enable_magic_dns: 启用魔法DNS enable_magic_dns_help: | 启用魔法DNS,允许通过EasyTier的DNS服务器访问其他节点的虚拟IPv4地址, 如 node1.et.net。 -tld_dns_zone: DNS域名后缀 -tld_dns_zone_help: | - 自定义魔法DNS的顶级域名(TLD)后缀。默认为 "et.net."。 - 如需使用其他域名后缀,可改为 "et.internal." 或 "easytier.lan" 等。 -tld_dns_zone_placeholder: 例如 et.net. enable_private_mode: 启用私有模式 enable_private_mode_help: | 启用私有模式,则不允许使用了与本网络不相同的网络名称和密码的节点通过本节点进行握手或中转。 diff --git a/easytier-web/frontend-lib/src/locales/en.yaml b/easytier-web/frontend-lib/src/locales/en.yaml index f37f5285..2cdd5b56 100644 --- a/easytier-web/frontend-lib/src/locales/en.yaml +++ b/easytier-web/frontend-lib/src/locales/en.yaml @@ -254,11 +254,6 @@ disable_sym_hole_punching_help: Disable special hole punching handling for symme enable_magic_dns: Enable Magic DNS enable_magic_dns_help: | Enable magic dns, all nodes in the network can access each other by domain name, e.g.: node1.et.net. -tld_dns_zone: DNS Zone TLD -tld_dns_zone_help: | - Custom top-level domain (TLD) zone for Magic DNS hostnames. Default is "et.net.". - Change this if you need a different domain suffix, e.g. "et.internal." or "easytier.lan". -tld_dns_zone_placeholder: e.g. et.net. enable_private_mode: Enable Private Mode enable_private_mode_help: | Enable private mode, nodes with different network names or passwords from this network are not allowed to perform handshake or relay through this node. diff --git a/easytier-web/frontend-lib/src/types/network.ts b/easytier-web/frontend-lib/src/types/network.ts index cec8fadd..86d51def 100644 --- a/easytier-web/frontend-lib/src/types/network.ts +++ b/easytier-web/frontend-lib/src/types/network.ts @@ -55,14 +55,24 @@ export const PEER_PROTOCOLS: { [proto: string]: number } = { srv: 0, } +// Configuration JSON uses transparent repeated wrappers, as Rust Serde does. +// Derive the value types from the protocol schema rather than copying fields. +type ConfigJson = T extends { items: Array } + ? ConfigJson[] + : T extends object ? { [Key in keyof T]: ConfigJson } : T + +export type DnsConfig = ConfigJson> + export type NetworkConfig = Omit< ProtoNetworkConfig, | 'instance_id' | 'instance_recv_bps_limit' | 'mtu' | 'networking_method' + | 'dns' > & { instance_id: string + dns?: DnsConfig mtu: number | null instance_recv_bps_limit: number | string | null networking_method: NetworkingMethod | string @@ -170,6 +180,7 @@ function newFlagValues(): Record { export function DEFAULT_NETWORK_CONFIG(): NetworkConfig { return { ...NetworkConfigPb.create(), + dns: undefined, instance_id: uuidv4(), @@ -207,7 +218,6 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig { mtu: null, instance_recv_bps_limit: null, mapped_listeners: [], - tld_dns_zone: 'et.net.', port_forwards: [], acl: emptyAcl(), } @@ -360,9 +370,11 @@ function applyNetworkingMethod( } export function normalizeNetworkConfig(config: NetworkConfig): NetworkConfig { - const normalized = NetworkConfigPb.fromJson(prepareNetworkConfigForProtoJson(config) as any, { + const { dns, ...protoConfig } = prepareNetworkConfigForProtoJson(config) + const normalized = NetworkConfigPb.fromJson(protoConfig as any, { ignoreUnknownFields: true, }) as unknown as NetworkConfig + normalized.dns = dns applyNetworkingMethod(normalized, { fillPeerUrlsFromPeers: true }) normalized.mtu = normalizeNumberForInput(normalized.mtu) @@ -397,7 +409,8 @@ export function normalizeNetworkConfig(config: NetworkConfig): NetworkConfig { } export function toBackendNetworkConfig(config: NetworkConfig): NetworkConfig { - const backend = NetworkConfigPb.fromJson(prepareNetworkConfigForProtoJson(config) as any, { + const { dns, ...protoConfig } = prepareNetworkConfigForProtoJson(config) + const backend = NetworkConfigPb.fromJson(protoConfig as any, { ignoreUnknownFields: true, }) @@ -417,9 +430,11 @@ export function toBackendNetworkConfig(config: NetworkConfig): NetworkConfig { backend.secure_mode = { enabled: true, local_private_key: credentialSecret } } - return NetworkConfigPb.toJson(backend, { + const result = NetworkConfigPb.toJson(backend, { useProtoFieldName: true, }) as unknown as NetworkConfig + if (dns !== undefined) result.dns = dns + return result } export function normalizeVpnPortalInfo(info: unknown): VpnPortalInfo { diff --git a/easytier-web/frontend-lib/tests/config-ui.spec.ts b/easytier-web/frontend-lib/tests/config-ui.spec.ts index d936c35d..7318af4f 100644 --- a/easytier-web/frontend-lib/tests/config-ui.spec.ts +++ b/easytier-web/frontend-lib/tests/config-ui.spec.ts @@ -416,7 +416,6 @@ describe('Config.vue network config projection', () => { expect(input(wrapper, '#no_tun').checked).toBe(true) expect(input(wrapper, '#hostname').value).toBe('host-a') - expect(input(wrapper, '#tld_dns_zone').value).toBe('et.net.') expect(input(wrapper, '#subnet-proxy').value).toBe('10.10.0.0/16,172.16.1.0/24') expect(input(wrapper, '#vpn_portal_wireguard_listen').value).toBe('0.0.0.0:22023') expect(input(wrapper, '#dev_name').value).toBe('tun-test') @@ -447,7 +446,6 @@ describe('Config.vue network config projection', () => { await wrapper.find('#no_tun').setValue(false) await wrapper.find('#disable_ipv6').setValue(false) await setInput(wrapper, '#hostname', 'host-edited') - await setInput(wrapper, '#tld_dns_zone', 'nas.internal.') await setInput(wrapper, '#subnet-proxy', '10.7.0.0/16,172.17.0.0/16') await setInput(wrapper, '#vpn_portal_wireguard_listen', '[::]:23000') await setInput(wrapper, 'input[data-add-label="add_listener_url"]', 'tcp://0.0.0.0:13010') @@ -476,7 +474,6 @@ describe('Config.vue network config projection', () => { no_tun: false, disable_ipv6: false, hostname: 'host-edited', - tld_dns_zone: 'nas.internal.', proxy_cidrs: ['10.7.0.0/16', '172.17.0.0/16'], vpn_portal_config: { wireguard_listen: '[::]:23000', @@ -512,7 +509,6 @@ describe('Config.vue network config projection', () => { network_secret: 'secret-edited', peer_urls: ['tcp://peer-x:11010', 'udp://peer-y:11010'], listener_urls: ['tcp://0.0.0.0:13010'], - tld_dns_zone: 'nas.internal.', mtu: 1260, instance_recv_bps_limit: '9007199254740993', vpn_portal_config: { @@ -532,9 +528,6 @@ describe('Config.vue network config projection', () => { dst_port: 9090, }], }) - - await setInput(wrapper, '#tld_dns_zone', '') - expect(toBackendNetworkConfig(curNetwork).tld_dns_zone).toBeUndefined() }) it('shows a flag the configuration does not state as the value the core resolves', async () => { diff --git a/easytier-web/src/client_manager/mod.rs b/easytier-web/src/client_manager/mod.rs index 2422ba75..6c7c4c3e 100644 --- a/easytier-web/src/client_manager/mod.rs +++ b/easytier-web/src/client_manager/mod.rs @@ -1086,7 +1086,6 @@ mod tests { assert_eq!(updated.disable_upnp, Some(true)); assert_eq!(updated.disable_relay_data, Some(true)); assert_eq!(updated.enable_magic_dns, Some(true)); - assert_eq!(updated.tld_dns_zone.as_deref(), Some("nas.internal.")); assert_eq!(updated.enable_private_mode, Some(true)); assert_eq!(updated.mtu, Some(1360)); assert_eq!( @@ -1113,7 +1112,6 @@ mod tests { "relay_all_peer_rpc": false, "multi_thread": false, "disable_relay_data": false, - "tld_dns_zone": "initial.internal.", "mtu": 1380 }) } @@ -1164,7 +1162,6 @@ mod tests { disable_upnp: Some(true), disable_relay_data: Some(true), enable_magic_dns: Some(true), - tld_dns_zone: Some("nas.internal.".to_owned()), enable_private_mode: Some(true), mtu: Some(1360), data_compress_algo: Some(CompressionAlgoPb::Zstd as i32), @@ -1654,11 +1651,10 @@ mod tests { .await .unwrap(); - let initial = wait_for_runtime_config(&core_manager, instance_id, |config| { + wait_for_runtime_config(&core_manager, instance_id, |config| { config.network_name.as_deref() == Some("managed-initial") }) .await; - assert_eq!(initial.tld_dns_zone.as_deref(), Some("initial.internal.")); wait_for_applied_revision(&mgr, user_id, machine_id, "rev-initial").await; // Runtime-only mutations do not change SQLite. Invalidate the Session @@ -1775,7 +1771,6 @@ mod tests { Some("managed-redelivered") ); assert_eq!(redelivered.enable_kcp_proxy, Some(true)); - assert_eq!(redelivered.tld_dns_zone, None); assert_eq!(redelivered.instance_recv_bps_limit, Some(654321)); assert_eq!( core_manager.config_source(instance_id), @@ -1811,7 +1806,6 @@ mod tests { Some("managed-redelivered") ); assert_eq!(replayed.enable_kcp_proxy, Some(true)); - assert_eq!(replayed.tld_dns_zone, None); assert_eq!(replayed.instance_recv_bps_limit, Some(654321)); webhook_server.abort(); diff --git a/easytier-web/src/client_manager/runtime_reconcile.rs b/easytier-web/src/client_manager/runtime_reconcile.rs index fd1baeb1..0873050f 100644 --- a/easytier-web/src/client_manager/runtime_reconcile.rs +++ b/easytier-web/src/client_manager/runtime_reconcile.rs @@ -730,26 +730,6 @@ mod tests { .collect() } - #[test] - fn magic_dns_zone_change_requires_recreate() { - let current = config_with_port_forwards(Vec::new()); - let mut desired = current.clone(); - desired.tld_dns_zone = Some("et.net.".to_owned()); - let action = - prepare_web_source_runtime_reconcile_from_current(¤t, desired.clone(), false) - .unwrap(); - assert!(matches!(action, RuntimeReconcileAction::Unchanged(_))); - - desired.tld_dns_zone = Some("nas.internal.".to_owned()); - let action = - prepare_web_source_runtime_reconcile_from_current(¤t, desired, false).unwrap(); - let RuntimeReconcileAction::Run { config, overwrite } = action else { - panic!("changing the DNS zone must recreate the instance"); - }; - assert!(overwrite); - assert_eq!(config.tld_dns_zone.as_deref(), Some("nas.internal.")); - } - #[test] fn vpn_portal_client_changes_produce_hot_patches() { let current = config_with_vpn_portal( diff --git a/easytier/locales/app.yml b/easytier/locales/app.yml index d168138c..02da9ac0 100644 --- a/easytier/locales/app.yml +++ b/easytier/locales/app.yml @@ -248,9 +248,6 @@ core_clap: accept_dns: en: "if true, enable magic dns. with magic dns, you can access other nodes with a domain name, e.g.: .et.net. magic dns will modify your system dns settings, enable it carefully." zh-CN: "如果为true,则启用魔法DNS。使用魔法DNS,您可以使用域名访问其他节点,例如:.et.net。魔法DNS将修改您的系统DNS设置,请谨慎启用。" - tld_dns_zone: - en: "specify the top-level domain zone for magic DNS. if not provided, defaults to the value from dns_server module (et.net.). only used when accept_dns is true." - zh-CN: "指定魔法DNS的顶级域名区域。如果未提供,默认使用dns_server模块中的值(et.net.)。仅在accept_dns为true时使用。" private_mode: en: "if true, foreign networks are only allowed when this node can verify they use the same network secret, or when a foreign credential node is already trusted via admin-issued credential propagation; different or missing secrets are otherwise rejected." zh-CN: "如果为true,则仅允许两类 foreign network 接入:本节点能验证其使用相同 network secret 的节点,或已通过 foreign network 管理节点传播而被信任的 credential 节点;否则 secret 不同或缺失时会被拒绝。" diff --git a/easytier/src/core.rs b/easytier/src/core.rs index 8e465f72..daf679ca 100644 --- a/easytier/src/core.rs +++ b/easytier/src/core.rs @@ -453,6 +453,9 @@ struct NetworkOptions { help = t!("core_clap.credential_file").to_string() )] credential_file: Option, + + #[arg(long, env = "ET_TLD_DNS_ZONE", hide = true)] + tld_dns_zone: Option, } #[derive(Parser, Debug)] @@ -945,7 +948,12 @@ impl NetworkOptions { cfg.set_secure_mode(Some(normalize_secure_mode_config(c)?)); } - cfg.patch_flags(self.flags.clone()); + let mut flags = self.flags.clone(); + #[allow(deprecated)] + if flags.tld_dns_zone.is_none() { + flags.tld_dns_zone = self.tld_dns_zone.clone(); + } + cfg.patch_flags(flags); #[cfg(not(feature = "tun"))] cfg.patch_flags(FlagsPatch { no_tun: Some(true), @@ -1949,6 +1957,26 @@ enabled = true // Deprecated flag is not accepted. assert!(parse_flags(&["easytier", "--quic-listen-port", "1234"]).is_err()); + assert!( + !cli_command() + .render_help() + .to_string() + .contains("tld-dns-zone") + ); + assert!( + !cli_command() + .render_long_help() + .to_string() + .contains("tld-dns-zone") + ); + let legacy_cli = + Cli::try_parse_from(["easytier-core", "--tld-dns-zone", "custom.local."]).unwrap(); + let cfg = TomlConfigLoader::default(); + legacy_cli.network_options.merge_into(&cfg).unwrap(); + #[allow(deprecated)] + { + assert_eq!(cfg.get_flags().tld_dns_zone, "custom.local."); + } // MTU tests: u16 range validation let patch = parse_flags(&["easytier", "--mtu", "1400"]).unwrap(); diff --git a/easytier/src/instance/dns_server/client_instance.rs b/easytier/src/instance/dns_server/client_instance.rs index a9c7c11c..df17b80d 100644 --- a/easytier/src/instance/dns_server/client_instance.rs +++ b/easytier/src/instance/dns_server/client_instance.rs @@ -4,6 +4,7 @@ use easytier_core::gateway::magic_dns::{ MagicDnsRoutePublisher, MagicDnsRouteSnapshot, run_magic_dns_route_publisher, }; use easytier_core::instance::CorePacketPlane; +use hickory_proto::rr::LowerName; use tokio::task::JoinSet; use crate::proto::{ @@ -22,11 +23,13 @@ pub struct MagicDnsClientInstance { rpc_client: RuntimeRpcClient, rpc_stub: Option + Send>>, route_source: Arc, + zone: LowerName, tasks: JoinSet<()>, } struct RpcMagicDnsRoutePublisher { rpc_stub: Box + Send>, + zone: LowerName, } #[async_trait::async_trait] @@ -56,7 +59,7 @@ impl MagicDnsRoutePublisher for RpcMagicDnsRoutePublisher { ..Default::default() }) .collect(), - zone: snapshot.zone.clone(), + zone: self.zone.to_string(), }; tracing::debug!( "MagicDnsClientInstance::update_dns_task: update dns records: {:?}", @@ -70,7 +73,10 @@ impl MagicDnsRoutePublisher for RpcMagicDnsRoutePublisher { } impl MagicDnsClientInstance { - pub(crate) async fn new(route_source: Arc) -> Result { + pub(crate) async fn new( + route_source: Arc, + zone: LowerName, + ) -> Result { let mut rpc_client = runtime_rpc_client(MAGIC_DNS_INSTANCE_ADDR.parse().unwrap()); let rpc_stub = rpc_client .scoped_client::>("".to_string()) @@ -79,6 +85,7 @@ impl MagicDnsClientInstance { rpc_client, rpc_stub: Some(rpc_stub), route_source, + zone, tasks: JoinSet::new(), }) } @@ -86,8 +93,9 @@ impl MagicDnsClientInstance { async fn update_dns_task( route_source: Arc, rpc_stub: Box + Send>, + zone: LowerName, ) -> Result<(), anyhow::Error> { - let mut publisher = RpcMagicDnsRoutePublisher { rpc_stub }; + let mut publisher = RpcMagicDnsRoutePublisher { rpc_stub, zone }; run_magic_dns_route_publisher( route_source.as_ref(), &mut publisher, @@ -99,8 +107,9 @@ impl MagicDnsClientInstance { pub async fn run_and_wait(&mut self) { let rpc_stub = self.rpc_stub.take().unwrap(); let route_source = self.route_source.clone(); + let zone = self.zone.clone(); self.tasks.spawn(async move { - let ret = Self::update_dns_task(route_source, rpc_stub).await; + let ret = Self::update_dns_task(route_source, rpc_stub, zone).await; if let Err(e) = ret { tracing::error!("MagicDnsServerInstanceData::run_and_wait: {:?}", e); } diff --git a/easytier/src/instance/dns_server/mod.rs b/easytier/src/instance/dns_server/mod.rs index 4719a1d0..688d6b76 100644 --- a/easytier/src/instance/dns_server/mod.rs +++ b/easytier/src/instance/dns_server/mod.rs @@ -3,6 +3,8 @@ pub(crate) mod config; #[cfg(all(feature = "magic-dns", feature = "tun"))] pub(crate) mod server; +#[cfg(all(feature = "magic-dns", feature = "tun"))] +pub(crate) mod zone; #[cfg(all(feature = "magic-dns", feature = "tun"))] pub mod client_instance; diff --git a/easytier/src/instance/dns_server/runner.rs b/easytier/src/instance/dns_server/runner.rs index 8e79806f..0ecbad31 100644 --- a/easytier/src/instance/dns_server/runner.rs +++ b/easytier/src/instance/dns_server/runner.rs @@ -143,7 +143,8 @@ impl DnsRunner { tokio::select! { _ = cancel_token.cancelled() => Ok(()), ret = async { - let client = MagicDnsClientInstance::new(self.packet_plane.clone()).await?; + let zone = super::zone::dns_domain(&self.global_ctx.config)?; + let client = MagicDnsClientInstance::new(self.packet_plane.clone(), zone).await?; self.client = Some(client); self.client.as_mut().unwrap().run_and_wait().await; Err(anyhow::anyhow!("Client instance exit")) diff --git a/easytier/src/instance/dns_server/server.rs b/easytier/src/instance/dns_server/server.rs index e9cd5ad3..f5ce0dcd 100644 --- a/easytier/src/instance/dns_server/server.rs +++ b/easytier/src/instance/dns_server/server.rs @@ -60,8 +60,8 @@ impl RequestHandler for CatalogRequestHandler { } } -pub fn build_authority(domain: &str, records: &[Record]) -> Result { - let zone = rr::Name::from_str(domain)?; +pub fn build_authority(origin: &rr::LowerName, records: &[Record]) -> Result { + let zone = rr::Name::from(origin); let mut authority = InMemoryAuthority::empty(zone, ZoneType::Primary, false); for record in records.iter() { let r = record.try_into()?; @@ -79,8 +79,9 @@ impl Server { let mut catalog = Catalog::new(); for (domain, records) in config.zones().iter() { let zone = rr::Name::from_str(domain.as_str())?; - let authroty = build_authority(domain, records)?; - catalog.upsert(zone.clone().into(), vec![Arc::new(authroty)]); + let lower = zone.clone().into(); + let authroty = build_authority(&lower, records)?; + catalog.upsert(lower, vec![Arc::new(authroty)]); } // use forwarder authority for the root zone diff --git a/easytier/src/instance/dns_server/server_instance.rs b/easytier/src/instance/dns_server/server_instance.rs index 2908f222..d6bc3c4c 100644 --- a/easytier/src/instance/dns_server/server_instance.rs +++ b/easytier/src/instance/dns_server/server_instance.rs @@ -11,6 +11,7 @@ use super::{ config::{GeneralConfigBuilder, RunConfigBuilder}, server::Server, system_config::{OSConfig, SystemConfig}, + zone::{self, ZoneGroup}, }; use crate::{ common::{ @@ -48,16 +49,26 @@ use hickory_proto::serialize::binary::{BinDecodable, BinEncoder}; use hickory_server::authority::{MessageRequest, MessageResponse}; use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo}; use std::sync::Mutex; -use std::{collections::BTreeMap, io, net::Ipv4Addr, str::FromStr, sync::Arc, time::Duration}; +use std::{ + collections::{BTreeMap, BTreeSet}, + io, + net::Ipv4Addr, + sync::Arc, + time::Duration, +}; pub(super) struct MagicDnsServerInstanceData { - dns_server: Server, + pub(super) dns_server: Server, tun_dev: Option, net_ns: NetNS, fake_ip: Ipv4Addr, manage_fake_ip_route: bool, route_store: MagicDnsRecordStore, record_apply: tokio::sync::Mutex<()>, + default_zone: LowerName, + /// Static records from this instance's local configuration, grouped once + /// at startup and merged into every refresh of the matching zone. + configured: BTreeMap, system_config: Option>, } @@ -66,7 +77,7 @@ impl MagicDnsServerInstanceData { pub async fn update_dns_records<'a, T: Iterator>( &self, routes: T, - zone: &str, + zone: &LowerName, ) -> Result<(), anyhow::Error> { let mut records: Vec = vec![]; for route in routes { @@ -105,14 +116,13 @@ impl MagicDnsServerInstanceData { .build()?; records.push(soa_record); - let authority = build_authority(zone, &records)?; + let mut authority = build_authority(zone, &records)?; + if let Some(group) = self.configured.get(zone) { + group.extend_authority(&mut authority); + } self.dns_server - .upsert( - LowerName::from_str(zone) - .with_context(|| "Invalid zone name, expect format like \"et.net.\"")?, - Arc::new(authority), - ) + .upsert(zone.clone(), Arc::new(authority)) .await; tracing::debug!("Updated DNS records for zone {}: {:?}", zone, records); @@ -120,16 +130,34 @@ impl MagicDnsServerInstanceData { Ok(()) } - pub async fn update(&self) { + pub async fn update(&self) -> Result<(), anyhow::Error> { let snapshot = self.route_store.snapshot(); - for (zone, routes) in &snapshot.zones { - if let Err(e) = self.update_dns_records(routes.iter(), zone).await { + // Route zones from connected clients, plus the default zone and any + // statically configured origins, must all stay authoritative. + let mut zones: BTreeSet = snapshot.zones.keys().cloned().collect(); + zones.insert(self.default_zone.clone()); + zones.extend(self.configured.keys().cloned()); + let mut first_err = None; + for zone in zones { + let routes = snapshot.zones.get(&zone).into_iter().flatten(); + if let Err(e) = self.update_dns_records(routes, &zone).await { tracing::error!("Failed to update DNS records for zone {}: {:?}", zone, e); + if first_err.is_none() { + first_err = Some(e); + } } } + first_err.map_or(Ok(()), Err) } - async fn keep_zone_authoritative(&self, zone: &str) { + /// Initializes the default zone and all configured static zones before + /// queries are accepted. + pub async fn init_zones(&self) -> Result<(), anyhow::Error> { + let _apply = self.record_apply.lock().await; + self.update().await + } + + async fn keep_zone_authoritative(&self, zone: &LowerName) { if let Err(e) = self .update_dns_records(std::iter::empty::<&MagicDnsRoute>(), zone) .await @@ -142,12 +170,23 @@ impl MagicDnsServerInstanceData { } } - fn do_system_config(&self, zone: &str) -> Result<(), anyhow::Error> { + fn do_system_config(&self) -> Result<(), anyhow::Error> { if let Some(c) = &self.system_config { + // The default zone goes first: Windows uses this list as the + // adapter's search list, so short names must prefer the default + // suffix. + let default_zone = self.default_zone.to_string(); + let mut domains = vec![default_zone.clone()]; + domains.extend( + self.configured + .keys() + .filter(|&origin| origin != &self.default_zone) + .map(ToString::to_string), + ); c.set_dns(&OSConfig { nameservers: vec![self.fake_ip.to_string()], - search_domains: vec![zone.to_string()], - match_domains: vec![zone.to_string()], + search_domains: vec![default_zone], + match_domains: domains, })?; } Ok(()) @@ -184,8 +223,8 @@ impl MagicDnsServerRpc for MagicDnsServerInstanceData { let Some(remote_addr) = &tunnel_info.remote_addr else { return Err(anyhow::anyhow!("No remote addr").into()); }; + let zone = zone::parse_origin(&input.zone)?; let _apply = self.record_apply.lock().await; - let zone = input.zone.clone(); let remote_addr: url::Url = remote_addr.clone().into(); let routes = input .routes @@ -203,7 +242,7 @@ impl MagicDnsServerRpc for MagicDnsServerInstanceData { self.keep_zone_authoritative(&zone).await; } - self.update().await; + let _ = self.update().await; Ok(Default::default()) } @@ -224,7 +263,7 @@ impl MagicDnsServerRpc for MagicDnsServerInstanceData { })), }); } - ret.insert(zone, dns_records); + ret.insert(zone.to_string(), dns_records); } Ok(GetDnsRecordResponse { records: ret }) } @@ -328,7 +367,7 @@ impl RpcServerHook for MagicDnsServerInstanceData { for zone in self.route_store.remove_client(remote_addr.as_ref()) { self.keep_zone_authoritative(&zone).await; } - self.update().await; + let _ = self.update().await; } } @@ -421,6 +460,13 @@ impl MagicDnsServerInstance { fake_ip: Ipv4Addr, manage_fake_ip_route: bool, ) -> Result { + // Validate the local DNS configuration before attempting the election: + // an invalid configuration must not win the listener and then fail or + // tear down the shared server. + let default_zone = zone::dns_domain(&global_ctx.config)?; + let configured = zone::configured_zones(global_ctx.config.get_dns().as_ref()) + .context("Invalid local DNS configuration")?; + let tcp_listener = runtime_rpc_listener(MAGIC_DNS_INSTANCE_SOCKET_ADDR.parse()?); let mut rpc_server = StandAloneServer::new(tcp_listener); rpc_server.serve().await?; @@ -452,6 +498,8 @@ impl MagicDnsServerInstance { manage_fake_ip_route, route_store: MagicDnsRecordStore::default(), record_apply: tokio::sync::Mutex::new(()), + default_zone: default_zone.clone(), + configured, system_config: get_system_config(tun_dev.as_deref())?, }); @@ -460,16 +508,12 @@ impl MagicDnsServerInstance { .register(MagicDnsServerRpcServer::new_arc(data.clone()), ""); rpc_server.set_hook(data.clone()); - // Use configured tld_dns_zone or fall back to DEFAULT_ET_DNS_ZONE if empty - let flags = global_ctx.get_flags(); - let tld_dns_zone_clone = flags.tld_dns_zone.clone(); - - data.update_dns_records(std::iter::empty(), &tld_dns_zone_clone) + data.init_zones() .await - .context("Failed to initialize DNS zone")?; + .context("Failed to initialize DNS zones")?; let data_clone = data.clone(); - tokio::task::spawn_blocking(move || data_clone.do_system_config(&tld_dns_zone_clone)) + tokio::task::spawn_blocking(move || data_clone.do_system_config()) .await .context("Failed to configure system")??; @@ -512,3 +556,399 @@ impl Drop for MagicDnsServerInstance { println!("MagicDnsServerInstance dropped"); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::proto::api::instance::Route; + use easytier_core::config::toml::{DnsConfig, DnsZoneConfig}; + use hickory_proto::{ + op::{Message, Query}, + rr::{RData, RecordType}, + }; + use std::str::FromStr; + + type DomainConfigRecord = (Vec, Vec); + type DomainConfigSpyCalls = Arc>>; + + struct SystemConfigSpy(DomainConfigSpyCalls); + + impl SystemConfig for SystemConfigSpy { + fn set_dns(&self, config: &OSConfig) -> io::Result<()> { + self.0 + .lock() + .unwrap() + .push((config.search_domains.clone(), config.match_domains.clone())); + Ok(()) + } + + fn close(&self) -> io::Result<()> { + Ok(()) + } + } + + fn config(zones: &[(&str, &[&str])]) -> DnsConfig { + DnsConfig { + domain: None, + zone: Some( + zones + .iter() + .map(|(origin, records)| DnsZoneConfig { + origin: (*origin).to_owned(), + ttl: Some(60), + records: Some( + records + .iter() + .map(|record| (*record).to_owned()) + .collect::>() + .into(), + ), + }) + .collect::>() + .into(), + ), + } + } + + async fn data_with_default_zone( + default_zone: &str, + dns: Option<&DnsConfig>, + ) -> MagicDnsServerInstanceData { + let data = MagicDnsServerInstanceData { + dns_server: Server::new( + RunConfigBuilder::default() + .general(GeneralConfigBuilder::default().build().unwrap()) + .build() + .unwrap(), + ), + tun_dev: None, + net_ns: NetNS::new(None), + fake_ip: "100.100.100.101".parse().unwrap(), + manage_fake_ip_route: false, + route_store: MagicDnsRecordStore::default(), + record_apply: tokio::sync::Mutex::new(()), + default_zone: zone::parse_origin(default_zone).unwrap(), + configured: zone::configured_zones(dns).unwrap(), + system_config: None, + }; + data.init_zones().await.unwrap(); + data + } + + async fn data_with(dns: Option<&DnsConfig>) -> MagicDnsServerInstanceData { + data_with_default_zone("et.net.", dns).await + } + + fn controller(port: u16) -> BaseController { + let mut ctrl = BaseController::default(); + ctrl.set_tunnel_info(Some(TunnelInfo { + remote_addr: Some( + url::Url::parse(&format!("tcp://127.0.0.1:{port}")) + .unwrap() + .into(), + ), + ..Default::default() + })); + ctrl + } + + fn request(ip: &str) -> UpdateDnsRecordRequest { + UpdateDnsRecordRequest { + zone: "et.net.".to_owned(), + routes: vec![Route { + hostname: "node".to_owned(), + ipv4_addr: Some(Ipv4Inet::from_str(ip).unwrap().into()), + ..Default::default() + }], + } + } + + async fn query( + data: &MagicDnsServerInstanceData, + name: &str, + record_type: RecordType, + ) -> Message { + let mut request = Message::new(); + request.add_query(Query::query(name.parse().unwrap(), record_type)); + let response = data + .resolve_query_inner(MagicDnsQuery { + source: "127.0.0.1:5353".parse().unwrap(), + payload: request.to_vec().unwrap(), + }) + .await + .unwrap(); + Message::from_vec(&response).unwrap() + } + + async fn a(data: &MagicDnsServerInstanceData, name: &str) -> Vec { + let mut answers = query(data, name, RecordType::A) + .await + .answers() + .iter() + .filter_map(|record| match record.data() { + RData::A(ip) => Some(ip.0), + _ => None, + }) + .collect::>(); + answers.sort(); + answers + } + + fn route(hostname: &str, addr: [u8; 4]) -> MagicDnsRoute { + MagicDnsRoute { + hostname: hostname.to_owned(), + ipv4_addr: Some(addr.into()), + } + } + + #[test] + fn replaces_routes_for_the_same_client_without_touching_other_clients() { + let store = MagicDnsRecordStore::default(); + let zone = zone::parse_origin("et.net.").unwrap(); + assert!(!store.replace_client_routes( + zone.clone(), + "tcp://client-a".to_owned(), + vec![route("old-a", [10, 0, 0, 1])], + )); + assert!(!store.replace_client_routes( + zone.clone(), + "tcp://client-b".to_owned(), + vec![route("peer-b", [10, 0, 0, 2])], + )); + assert!(!store.replace_client_routes( + zone.clone(), + "tcp://client-a".to_owned(), + vec![route("new-a", [10, 0, 0, 3])], + )); + + let routes = &store.snapshot().zones[&zone]; + assert_eq!(routes.len(), 2); + assert!(routes.iter().any(|route| route.hostname == "new-a")); + assert!(routes.iter().any(|route| route.hostname == "peer-b")); + assert!(!routes.iter().any(|route| route.hostname == "old-a")); + } + + #[test] + fn empty_update_removes_only_the_target_client_and_reports_empty_zone() { + let store = MagicDnsRecordStore::default(); + let zone = zone::parse_origin("et.net.").unwrap(); + store.replace_client_routes( + zone.clone(), + "tcp://client-a".to_owned(), + vec![route("peer-a", [10, 0, 0, 1])], + ); + store.replace_client_routes( + zone.clone(), + "tcp://client-b".to_owned(), + vec![route("peer-b", [10, 0, 0, 2])], + ); + + assert!(!store.replace_client_routes( + zone.clone(), + "tcp://client-a".to_owned(), + Vec::new(), + )); + assert!(store.replace_client_routes(zone, "tcp://client-b".to_owned(), Vec::new(),)); + assert!(store.snapshot().zones.is_empty()); + } + + #[test] + fn disconnect_removes_client_from_all_zones() { + let store = MagicDnsRecordStore::default(); + for zone in ["a.et.net.", "b.et.net."] { + store.replace_client_routes( + zone::parse_origin(zone).unwrap(), + "tcp://client-a".to_owned(), + vec![route("peer-a", [10, 0, 0, 1])], + ); + } + store.replace_client_routes( + zone::parse_origin("b.et.net.").unwrap(), + "tcp://client-b".to_owned(), + vec![route("peer-b", [10, 0, 0, 2])], + ); + + assert_eq!( + store.remove_client("tcp://client-a"), + vec![zone::parse_origin("a.et.net.").unwrap()] + ); + let snapshot = store.snapshot(); + assert_eq!(snapshot.zones.len(), 1); + assert_eq!( + snapshot.zones[&zone::parse_origin("b.et.net.").unwrap()][0].hostname, + "peer-b" + ); + } + + #[tokio::test] + async fn custom_records_survive_route_refresh_and_support_common_types() { + let dns = config(&[( + "ET.NET", + &[ + "nas IN A 10.0.0.8", + "nas IN AAAA fd00::8", + "alias IN CNAME nas", + "info IN TXT \"local record\"", + ], + )]); + let data = data_with(Some(&dns)).await; + for ip in ["10.0.0.1/24", "10.0.0.2/24"] { + data.update_dns_record(controller(54001), request(ip)) + .await + .unwrap(); + assert_eq!( + a(&data, "nas.et.net.").await, + vec![Ipv4Addr::new(10, 0, 0, 8)] + ); + assert!(query(&data, "alias.et.net.", RecordType::A).await.answers().iter() + .any(|record| matches!(record.data(), RData::CNAME(name) if name.0.to_string() == "nas.et.net."))); + assert_eq!( + a(&data, "node.et.net.").await, + vec![Ipv4Inet::from_str(ip).unwrap().address()] + ); + } + assert!(matches!( + query(&data, "nas.et.net.", RecordType::AAAA) + .await + .answers()[0] + .data(), + RData::AAAA(_) + )); + assert!(matches!( + query(&data, "info.et.net.", RecordType::TXT) + .await + .answers()[0] + .data(), + RData::TXT(_) + )); + assert_eq!( + query(&data, "nas.et.net.", RecordType::A).await.answers()[0].ttl(), + 60 + ); + // Disconnecting a client removes its node records, not the server's + // statically configured ones. + data.on_client_disconnected(controller(54001).get_tunnel_info().cloned()) + .await; + assert!( + query(&data, "node.et.net.", RecordType::A) + .await + .answers() + .is_empty() + ); + assert_eq!( + a(&data, "nas.et.net.").await, + vec![Ipv4Addr::new(10, 0, 0, 8)] + ); + assert!( + data.dns_server + .read_catalog() + .await + .contains(&"et.net.".parse().unwrap()) + ); + } + + #[tokio::test] + async fn configured_zones_with_distinct_origins_are_served() { + let dns = config(&[("et.net.", &[]), ("example.test.", &["svc IN A 10.0.0.9"])]); + let data = data_with(Some(&dns)).await; + assert_eq!( + a(&data, "svc.example.test.").await, + vec![Ipv4Addr::new(10, 0, 0, 9)] + ); + // Refreshing routes on the default zone must not drop static zones. + data.update_dns_record(controller(54001), request("10.0.0.1/24")) + .await + .unwrap(); + assert_eq!( + a(&data, "svc.example.test.").await, + vec![Ipv4Addr::new(10, 0, 0, 9)] + ); + assert_eq!( + a(&data, "node.et.net.").await, + vec![Ipv4Addr::new(10, 0, 0, 1)] + ); + } + + #[tokio::test] + async fn uppercase_zone_and_route_updates_preserve_node_records() { + let data = data_with_default_zone("ET.NET.", None).await; + for zone in ["ET.NET.", "et.net"] { + let req = UpdateDnsRecordRequest { + zone: zone.to_owned(), + routes: vec![Route { + hostname: "node".to_owned(), + ipv4_addr: Some(Ipv4Inet::from_str("10.0.0.1/24").unwrap().into()), + ..Default::default() + }], + }; + data.update_dns_record(controller(54001), req) + .await + .unwrap(); + assert_eq!( + a(&data, "node.et.net.").await, + vec![Ipv4Addr::new(10, 0, 0, 1)] + ); + } + + let dns = config(&[("et.net", &["nas IN A 10.0.0.8"])]); + let data = data_with_default_zone("ET.NET.", Some(&dns)).await; + let req = UpdateDnsRecordRequest { + zone: "ET.NET".to_owned(), + routes: vec![Route { + hostname: "node".to_owned(), + ipv4_addr: Some(Ipv4Inet::from_str("10.0.0.1/24").unwrap().into()), + ..Default::default() + }], + }; + data.update_dns_record(controller(54001), req) + .await + .unwrap(); + assert_eq!( + a(&data, "node.et.net.").await, + vec![Ipv4Addr::new(10, 0, 0, 1)] + ); + assert_eq!( + a(&data, "nas.et.net.").await, + vec![Ipv4Addr::new(10, 0, 0, 8)] + ); + } + + #[tokio::test] + async fn static_zones_are_loaded_during_init_before_any_rpc() { + let dns = config(&[("example.test.", &["svc IN A 10.0.0.9"])]); + let data = data_with(Some(&dns)).await; + assert_eq!( + a(&data, "svc.example.test.").await, + vec![Ipv4Addr::new(10, 0, 0, 9)] + ); + } + + #[tokio::test] + async fn system_config_keeps_default_zone_first() { + let dns = config(&[("aaa.test.", &["@ IN A 10.0.0.10"]), ("et.net.", &[])]); + let mut data = data_with(Some(&dns)).await; + let calls = Arc::new(Mutex::new(Vec::new())); + data.system_config = Some(Box::new(SystemConfigSpy(calls.clone()))); + data.do_system_config().unwrap(); + assert_eq!( + *calls.lock().unwrap(), + vec![( + vec!["et.net.".to_owned()], + vec!["et.net.".to_owned(), "aaa.test.".to_owned()] + )] + ); + } + + #[tokio::test] + async fn invalid_local_configuration_is_rejected_before_startup() { + for dns in [ + config(&[("", &[])]), + config(&[("example.test.", &["nas IN A invalid"])]), + config(&[("example.test.", &["outside.invalid. IN A 10.0.0.8"])]), + config(&[("example.test.", &["$INCLUDE /etc/hosts"])]), + ] { + assert!(zone::configured_zones(Some(&dns)).is_err()); + } + assert!(zone::configured_zones(None).unwrap().is_empty()); + } +} diff --git a/easytier/src/instance/dns_server/tests.rs b/easytier/src/instance/dns_server/tests.rs index 709a29fb..9716e418 100644 --- a/easytier/src/instance/dns_server/tests.rs +++ b/easytier/src/instance/dns_server/tests.rs @@ -5,6 +5,7 @@ use std::time::Duration; use cidr::Ipv4Inet; use easytier_core::{ + config::toml::{DnsConfig, DnsZoneConfig}, gateway::magic_dns::MagicDnsRoute, host::packet::{HostPacket, HostPacketChannelSink, HostPacketReceiver}, process_runtime::CoreProcessRuntime, @@ -58,13 +59,13 @@ pub async fn prepare_env_with_tld_dns_zone( ctx.set_hostname(dns_name.to_owned()); ctx.set_ipv4(Some(tun_ip)); - if tld_dns_zone.is_some() { + if let Some(zone) = tld_dns_zone { let mut flags = ctx.config.get_flags(); flags.accept_dns = true; // Enable DNS - if let Some(zone) = tld_dns_zone { - flags.tld_dns_zone = zone.to_string(); - } ctx.set_flags(flags); + let mut dns = ctx.config.get_dns().unwrap_or_default(); + dns.domain = Some(zone.to_string()); + ctx.config.set_dns(Some(dns)); } let (core_instance, host_packet_rx) = build_test_core(ctx.clone()).await; @@ -119,6 +120,18 @@ async fn test_magic_dns_server_instance() { let (global_ctx, core_instance, virtual_nic) = prepare_env("test1", tun_ip).await; let tun_name = virtual_nic.ifname().await.unwrap(); let fake_ip = Ipv4Addr::from_str("100.100.100.101").unwrap(); + + let mut dns = global_ctx.config.get_dns().unwrap_or_default(); + dns.zone = Some( + vec![DnsZoneConfig { + origin: "example.test.".to_owned(), + ttl: Some(60), + records: Some(vec!["svc IN A 10.0.0.9".to_owned()].into()), + }] + .into(), + ); + global_ctx.config.set_dns(Some(dns)); + let dns_server_inst = MagicDnsServerInstance::new( core_instance.packet_plane(), global_ctx, @@ -129,6 +142,8 @@ async fn test_magic_dns_server_instance() { .await .unwrap(); + check_dns_record(&fake_ip, "svc.example.test", "10.0.0.9").await; + let routes = [ MagicDnsRoute { hostname: "test1".to_string(), @@ -145,7 +160,10 @@ async fn test_magic_dns_server_instance() { ]; dns_server_inst .data - .update_dns_records(routes.iter(), DEFAULT_ET_DNS_ZONE) + .update_dns_records( + routes.iter(), + &super::zone::parse_origin(DEFAULT_ET_DNS_ZONE).unwrap(), + ) .await .unwrap(); @@ -224,6 +242,17 @@ async fn test_magic_dns_update_replaces_records_for_same_client() { let ctx = get_mock_global_ctx(); ctx.set_hostname("test1".to_string()); ctx.set_ipv4(Some(tun_ip)); + ctx.config.set_dns(Some(DnsConfig { + domain: None, + zone: Some( + vec![DnsZoneConfig { + origin: "example.test.".to_owned(), + ttl: Some(60), + records: Some(vec!["svc IN A 10.0.0.9".to_owned()].into()), + }] + .into(), + ), + })); let (core_instance, _packet_receiver) = build_test_core(ctx.clone()).await; @@ -232,6 +261,14 @@ async fn test_magic_dns_update_replaces_records_for_same_client() { MagicDnsServerInstance::new(core_instance.packet_plane(), ctx, None, tun_ip, fake_ip) .await .unwrap(); + assert!( + dns_server_inst + .data + .dns_server + .read_catalog() + .await + .contains(&"example.test.".parse().unwrap()) + ); let mut ctrl = BaseController::default(); ctrl.set_tunnel_info(Some(crate::proto::common::TunnelInfo { diff --git a/easytier/src/instance/dns_server/zone.rs b/easytier/src/instance/dns_server/zone.rs new file mode 100644 index 00000000..edb5f364 --- /dev/null +++ b/easytier/src/instance/dns_server/zone.rs @@ -0,0 +1,285 @@ +use crate::proto::utils::RepeatedMessageModel; +use anyhow::{Context, ensure}; +use easytier_core::config::toml::{ConfigLoader, DEFAULT_ET_DNS_ZONE, DnsConfig, DnsZoneConfig}; +use hickory_proto::rr::{DNSClass, LowerName, Name, Record, RecordSet, RrKey}; +use hickory_proto::serialize::txt::Parser; +use hickory_server::store::in_memory::InMemoryAuthority; +use std::collections::BTreeMap; + +pub(crate) fn parse_origin(origin: &str) -> anyhow::Result { + ensure!( + !origin.trim().is_empty(), + "DNS zone origin must not be empty" + ); + let mut name: Name = origin.parse().context("invalid DNS zone origin")?; + ensure!(!name.is_root(), "DNS zone origin must not be root"); + name.set_fqdn(true); + Ok(LowerName::new(&name)) +} + +/// Resolves the default Magic DNS zone origin from `dns.domain`, falling back +/// to the deprecated `flags.tld_dns_zone` patch when `dns.domain` is unset, and +/// finally to [`DEFAULT_ET_DNS_ZONE`]. +#[allow(deprecated)] +pub(crate) fn dns_domain(config: &impl ConfigLoader) -> anyhow::Result { + let dns = config.get_dns(); + if let Some(domain) = dns.as_ref().and_then(|dns| dns.domain.as_deref()) { + return parse_origin(domain).context("invalid dns.domain"); + } + if let Some(legacy) = config.get_flags_patch().tld_dns_zone.as_deref() + && !legacy.is_empty() + { + return parse_origin(legacy).context("invalid flags.tld_dns_zone"); + } + parse_origin(DEFAULT_ET_DNS_ZONE) +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct Zone { + origin: LowerName, + records: BTreeMap, +} + +impl Zone { + #[cfg(test)] + pub fn origin(&self) -> &LowerName { + &self.origin + } + + pub fn iter_records(&self) -> impl Iterator { + self.records + .values() + .flat_map(RecordSet::records_without_rrsigs) + } +} + +impl TryFrom<&DnsZoneConfig> for Zone { + type Error = anyhow::Error; + + fn try_from(value: &DnsZoneConfig) -> Result { + let origin = parse_origin(&value.origin)?; + let ttl = value.ttl.unwrap_or_default(); + let lines = value + .records + .as_ref() + .map(|list| list.items.as_slice()) + .unwrap_or_default(); + ensure!( + !lines + .iter() + .flat_map(|record| record.lines()) + .any(|line| line.trim_start().starts_with('$')), + "zone file directives are not supported in DNS records" + ); + let content = format!( + "$ORIGIN {origin}\n$TTL {ttl}\n{}\n", + // Hickory 0.25 carries an explicit record TTL into later records. + lines.join(&format!("\n$TTL {ttl}\n")) + ); + let name = Name::from(&origin); + let (_, records) = Parser::new(content, None, Some(name.clone())) + .parse() + .with_context(|| format!("invalid DNS records for zone {origin}"))?; + + for record in records.values().flat_map(RecordSet::records_without_rrsigs) { + ensure!( + name.zone_of(record.name()), + "DNS record {} is outside zone {origin}", + record.name() + ); + ensure!( + record.dns_class() == DNSClass::IN, + "DNS records must use class IN" + ); + } + + Ok(Self { origin, records }) + } +} + +impl From for DnsZoneConfig { + fn from(value: Zone) -> Self { + let records = value + .iter_records() + .map(ToString::to_string) + .collect::>(); + Self { + origin: value.origin.to_string(), + ttl: None, + records: Some(records.into()), + } + } +} + +pub(crate) type ZoneGroup = RepeatedMessageModel; + +impl ZoneGroup { + pub fn into_groups(self) -> BTreeMap { + self.into_iter().fold(BTreeMap::new(), |mut map, zone| { + map.entry(zone.origin.clone()).or_default().push(zone); + map + }) + } + + /// Merges records of every zone into one authority, since an in-memory + /// authority answers authoritatively and never falls through to another. + pub fn extend_authority(&self, authority: &mut InMemoryAuthority) { + for record in self.iter().flat_map(Zone::iter_records) { + authority.upsert_mut(record.clone(), 0); + } + } +} + +/// Parses the local static records once, grouped by origin. Called before the +/// server election so an invalid local configuration cannot win the listener. +pub(crate) fn configured_zones( + dns: Option<&DnsConfig>, +) -> anyhow::Result> { + let items = dns + .and_then(|dns| dns.zone.as_ref()) + .map(|zones| zones.items.as_slice()) + .unwrap_or_default(); + Ok(ZoneGroup::try_from(items)?.into_groups()) +} + +#[cfg(test)] +mod tests { + use super::*; + use easytier_core::config::toml::{DnsConfig, TomlConfigLoader}; + use hickory_proto::rr::RecordType; + + fn zones(config: &DnsConfig) -> anyhow::Result { + let items = config + .zone + .as_ref() + .map(|zones| zones.items.as_slice()) + .unwrap_or_default(); + ZoneGroup::try_from(items) + } + + fn config(records: &[&str]) -> DnsConfig { + DnsConfig { + domain: None, + zone: Some( + vec![DnsZoneConfig { + origin: "EXAMPLE.test".to_owned(), + ttl: Some(60), + records: Some( + records + .iter() + .map(|record| (*record).to_owned()) + .collect::>() + .into(), + ), + }] + .into(), + ), + } + } + + #[test] + fn custom_records_parse_common_types_and_ttls() { + let config = config(&[ + "nas IN A 10.0.0.8", + "nas IN AAAA fd00::8", + "alias 120 IN CNAME nas", + "@ IN TXT \"local records\"", + ]); + let parsed = zones(&config).unwrap(); + assert_eq!(parsed.len(), 1); + assert_eq!(parsed[0].origin().to_string(), "example.test."); + let records = parsed[0].iter_records().collect::>(); + assert_eq!(records.len(), 4); + for record in records { + assert!( + record.name().to_string().ends_with(".example.test.") + || record.name().to_string() == "example.test." + ); + assert_eq!( + record.ttl(), + if record.record_type() == RecordType::CNAME { + 120 + } else { + 60 + } + ); + } + let mut config = config; + config.zone.as_mut().unwrap().items[0].ttl = Some(0); + let parsed = zones(&config).unwrap(); + assert_eq!(parsed[0].iter_records().next().unwrap().ttl(), 0); + } + + #[test] + fn custom_records_reject_invalid_records_and_directives() { + for record in [ + "nas IN A not-an-address", + "outside.invalid. IN A 10.0.0.8", + "$INCLUDE /etc/hosts", + "nas IN A 10.0.0.8\n$ORIGIN other.test.", + ] { + assert!(zones(&config(&[record])).is_err(), "accepted {record}"); + } + for origin in ["", "."] { + let mut config = config(&[]); + config.zone.as_mut().unwrap().items[0].origin = origin.to_owned(); + assert!(zones(&config).is_err(), "accepted origin {origin}"); + } + assert!( + zones(&DnsConfig { + domain: None, + zone: None + }) + .unwrap() + .is_empty() + ); + } + + #[test] + fn zone_group_into_groups_merges_same_origin() { + let mut config = config(&["nas IN A 10.0.0.8"]); + let mut second = config.zone.as_ref().unwrap().items[0].clone(); + second.origin = "example.test.".to_owned(); + config.zone.as_mut().unwrap().items.push(second); + let groups = zones(&config).unwrap().into_groups(); + assert_eq!(groups.len(), 1); + assert_eq!( + groups[&"example.test.".parse::().unwrap()].len(), + 2 + ); + } + + #[test] + fn dns_domain_prefers_dns_domain_and_falls_back_to_legacy_flag() { + let loader = TomlConfigLoader::new_from_str("").unwrap(); + assert_eq!(dns_domain(&loader).unwrap().to_string(), "et.net."); + + let loader = TomlConfigLoader::new_from_str("[dns]\ndomain = 'MESH.Example'\n").unwrap(); + assert_eq!(dns_domain(&loader).unwrap().to_string(), "mesh.example."); + + let loader = + TomlConfigLoader::new_from_str("[flags]\ntld_dns_zone = 'LEGACY.Example'\n").unwrap(); + assert_eq!(dns_domain(&loader).unwrap().to_string(), "legacy.example."); + + let loader = TomlConfigLoader::new_from_str( + "[dns]\ndomain = 'mesh.example'\n[flags]\ntld_dns_zone = 'legacy.example'\n", + ) + .unwrap(); + assert_eq!(dns_domain(&loader).unwrap().to_string(), "mesh.example."); + + let loader = TomlConfigLoader::new_from_str( + "[dns]\ndomain = ''\n[flags]\ntld_dns_zone = 'legacy.example'\n", + ) + .unwrap(); + assert!(dns_domain(&loader).is_err()); + + let loader = TomlConfigLoader::new_from_str( + "[dns]\ndomain = '.'\n[flags]\ntld_dns_zone = 'legacy.example'\n", + ) + .unwrap(); + assert!(dns_domain(&loader).is_err()); + + let loader = TomlConfigLoader::new_from_str("[flags]\ntld_dns_zone = '.'\n").unwrap(); + assert!(dns_domain(&loader).is_err()); + } +} diff --git a/easytier/src/proto/mod.rs b/easytier/src/proto/mod.rs index efe71faa..6501c826 100644 --- a/easytier/src/proto/mod.rs +++ b/easytier/src/proto/mod.rs @@ -12,3 +12,4 @@ pub use easytier_proto::magic_dns; pub mod tests; pub mod rpc; +pub mod utils; diff --git a/easytier/src/proto/utils.rs b/easytier/src/proto/utils.rs new file mode 100644 index 00000000..5c1105a7 --- /dev/null +++ b/easytier/src/proto/utils.rs @@ -0,0 +1,63 @@ +use derive_more::{AsMut, AsRef, Deref, DerefMut, From, IntoIterator}; + +pub trait MessageModel: + Into + for<'m> TryFrom<&'m Message> +{ +} + +impl MessageModel for Model +where + Message: prost::Message, + Model: Into + for<'m> TryFrom<&'m Message>, +{ +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, From, Deref, DerefMut, AsRef, AsMut, IntoIterator)] +#[as_ref(forward)] +#[as_mut(forward)] +#[into_iterator(owned, ref, ref_mut)] +pub struct RepeatedMessageModel { + pub models: Vec, +} + +impl Default for RepeatedMessageModel { + fn default() -> Self { + Self { models: Vec::new() } + } +} + +impl FromIterator for RepeatedMessageModel { + fn from_iter>(iter: I) -> Self { + Self { + models: iter.into_iter().collect(), + } + } +} + +impl Extend for RepeatedMessageModel { + fn extend>(&mut self, iter: T) { + self.models.extend(iter); + } +} + +impl<'m, Message, Model> TryFrom<&'m [Message]> for RepeatedMessageModel +where + Message: prost::Message, + Model: MessageModel, +{ + type Error = >::Error; + + fn try_from(value: &'m [Message]) -> Result { + value.iter().map(TryInto::try_into).collect() + } +} + +impl From> for Vec +where + Message: prost::Message, + Model: MessageModel, +{ + fn from(value: RepeatedMessageModel) -> Self { + value.into_iter().map(Into::into).collect() + } +}