feat(web): enable tld_dns_zone end-to-end from Web Console to MagicDNS (#2425)

This commit is contained in:
Alrcatraz
2026-10-28 20:37:54 +08:00
committed by GitHub
parent 6487a5e463
commit 4bdba58ca9
11 changed files with 93 additions and 4 deletions
+33
View File
@@ -87,6 +87,7 @@ 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;
result.mtu = flags.mtu.map(|mtu| mtu as i32);
result.data_compress_algo = flags.data_compress_algo;
result.encryption_algorithm = flags.encryption_algorithm;
@@ -513,6 +514,7 @@ impl NetworkConfigExt for NetworkConfig {
}
}),
accept_dns: self.enable_magic_dns,
tld_dns_zone: self.tld_dns_zone.clone(),
mtu: self
.mtu
.map(u32::try_from)
@@ -929,6 +931,21 @@ 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 {
@@ -1045,6 +1062,22 @@ 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() {
+1
View File
@@ -50,6 +50,7 @@ message NetworkConfig {
optional bool use_smoltcp = 21;
optional bool disable_ipv6 = 47;
optional string tld_dns_zone = 73;
optional bool enable_kcp_proxy = 22;
optional bool disable_kcp_input = 23;
optional bool disable_p2p = 24;
+1 -1
View File
@@ -66,7 +66,7 @@ message Flags {
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." }];
string tld_dns_zone = 31 [(easytier.flag) = { default: "et.net.", form: true }];
bool p2p_only = 32 [(easytier.flag) = { default: "false" }];
@@ -168,6 +168,7 @@ function allFieldFixture() {
mtu: 1280,
mapped_listeners: ['tcp://127.0.0.1:13010'],
enable_magic_dns: true,
tld_dns_zone: 'nas.internal.',
enable_private_mode: true,
enable_quic_proxy: true,
disable_quic_input: true,
@@ -387,6 +387,18 @@ function removeVpnPortalClient(index: number) {
</div>
</div>
<div class="flex flex-row gap-x-9 flex-wrap">
<div class="flex flex-col gap-2 basis-5/12 grow">
<div class="flex">
<label for="tld_dns_zone">{{ t('tld_dns_zone') }}</label>
<span class="pi pi-question-circle ml-2 self-center" v-tooltip="t('tld_dns_zone_help')"></span>
</div>
<InputText id="tld_dns_zone" :model-value="curNetwork.tld_dns_zone"
@update:model-value="curNetwork.tld_dns_zone = $event?.trim() || undefined"
aria-describedby="tld_dns_zone-help" :placeholder="t('tld_dns_zone_placeholder')" />
</div>
</div>
<div class="flex flex-row gap-x-9 flex-wrap">
<div class="flex flex-col gap-2 basis-5/12 grow">
<label for="hostname">{{ t('hostname') }}</label>
@@ -255,7 +255,11 @@ 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: |
启用私有模式,则不允许使用了与本网络不相同的网络名称和密码的节点通过本节点进行握手或中转。
@@ -254,7 +254,11 @@ 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.
@@ -207,6 +207,7 @@ export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
mtu: null,
instance_recv_bps_limit: null,
mapped_listeners: [],
tld_dns_zone: 'et.net.',
port_forwards: [],
acl: emptyAcl(),
}
@@ -416,6 +416,7 @@ 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')
@@ -446,6 +447,7 @@ 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')
@@ -474,6 +476,7 @@ 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',
@@ -509,6 +512,7 @@ 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: {
@@ -528,6 +532,9 @@ 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 () => {
+7 -1
View File
@@ -1086,6 +1086,7 @@ 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!(
@@ -1112,6 +1113,7 @@ mod tests {
"relay_all_peer_rpc": false,
"multi_thread": false,
"disable_relay_data": false,
"tld_dns_zone": "initial.internal.",
"mtu": 1380
})
}
@@ -1162,6 +1164,7 @@ 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),
@@ -1651,10 +1654,11 @@ mod tests {
.await
.unwrap();
wait_for_runtime_config(&core_manager, instance_id, |config| {
let initial = 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
@@ -1771,6 +1775,7 @@ 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),
@@ -1806,6 +1811,7 @@ 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();
@@ -730,6 +730,26 @@ 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(&current, 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(&current, 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(