feat: add hosts settings to DNS settings and enhance DNS config validation

This commit is contained in:
wonfen
2025-05-11 22:55:31 +08:00
parent b37b121afb
commit 10576780ed
9 changed files with 239 additions and 135 deletions

View File

@@ -233,3 +233,26 @@ pub async fn get_dns_config_content() -> CmdResult<String> {
let content = fs::read_to_string(&dns_path).map_err(|e| e.to_string())?;
Ok(content)
}
/// 验证DNS配置文件
#[tauri::command]
pub async fn validate_dns_config() -> CmdResult<(bool, String)> {
use crate::core::CoreManager;
use crate::utils::dirs;
let app_dir = dirs::app_home_dir().map_err(|e| e.to_string())?;
let dns_path = app_dir.join("dns_config.yaml");
let dns_path_str = dns_path.to_str().unwrap_or_default();
if !dns_path.exists() {
return Ok((false, "DNS config file not found".to_string()));
}
match CoreManager::global()
.validate_config_file(dns_path_str, None)
.await
{
Ok(result) => Ok(result),
Err(e) => Err(e.to_string()),
}
}

View File

@@ -261,16 +261,29 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
use crate::utils::dirs;
use std::fs;
// 尝试读取dns_config.yaml
if let Ok(app_dir) = dirs::app_home_dir() {
let dns_path = app_dir.join("dns_config.yaml");
if dns_path.exists() {
if let Ok(dns_yaml) = fs::read_to_string(&dns_path) {
if let Ok(dns_config) = serde_yaml::from_str::<serde_yaml::Mapping>(&dns_yaml) {
// 将DNS配置合并到最终配置中
config.insert("dns".into(), dns_config.into());
log::info!(target: "app", "apply dns_config.yaml");
// 处理hosts配置
if let Some(hosts_value) = dns_config.get("hosts") {
if hosts_value.is_mapping() {
config.insert("hosts".into(), hosts_value.clone());
log::info!(target: "app", "apply hosts configuration");
}
}
if let Some(dns_value) = dns_config.get("dns") {
if let Some(dns_mapping) = dns_value.as_mapping() {
config.insert("dns".into(), dns_mapping.clone().into());
log::info!(target: "app", "apply dns_config.yaml (dns section)");
}
} else {
config.insert("dns".into(), dns_config.into());
log::info!(target: "app", "apply dns_config.yaml");
}
}
}
}

View File

@@ -252,6 +252,7 @@ pub fn run() {
cmd::apply_dns_config,
cmd::check_dns_config_exists,
cmd::get_dns_config_content,
cmd::validate_dns_config,
// verge
cmd::get_verge_config,
cmd::patch_verge_config,

View File

@@ -142,8 +142,8 @@ pub fn delete_log() -> Result<()> {
fn init_dns_config() -> Result<()> {
use serde_yaml::Value;
// 获取默认DNS配置
let default_dns_config = serde_yaml::Mapping::from_iter([
// 创建DNS配置
let dns_config = serde_yaml::Mapping::from_iter([
("enable".into(), Value::Bool(true)),
("listen".into(), Value::String(":53".into())),
("enhanced-mode".into(), Value::String("fake-ip".into())),
@@ -231,6 +231,12 @@ fn init_dns_config() -> Result<()> {
),
]);
// 获取默认DNS和host配置
let default_dns_config = serde_yaml::Mapping::from_iter([
("dns".into(), Value::Mapping(dns_config)),
("hosts".into(), Value::Mapping(serde_yaml::Mapping::new())),
]);
// 检查DNS配置文件是否存在
let app_dir = dirs::app_home_dir()?;
let dns_path = app_dir.join("dns_config.yaml");

View File

@@ -15,9 +15,6 @@ pub const ITEM_MERGE: &str = "# Profile Enhancement Merge Template for Clash Ver
profile:
store-selected: true
dns:
use-system-hosts: false
";
pub const ITEM_MERGE_EMPTY: &str = "# Profile Enhancement Merge Template for Clash Verge

View File

@@ -112,6 +112,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
fallbackIpcidr: string;
fallbackDomain: string;
nameserverPolicy: string;
hosts: string; // hosts设置独立于dns
}>({
enable: DEFAULT_DNS_CONFIG.enable,
listen: DEFAULT_DNS_CONFIG.listen,
@@ -139,6 +140,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
fallbackDomain:
DEFAULT_DNS_CONFIG["fallback-filter"].domain?.join(", ") || "",
nameserverPolicy: "",
hosts: "",
});
// 用于YAML编辑模式
@@ -185,15 +187,20 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
const updateValuesFromConfig = (config: any) => {
if (!config) return;
// 提取dns配置
const dnsConfig = config.dns || {};
// 提取hosts配置与dns同级
const hostsConfig = config.hosts || {};
const enhancedMode =
config["enhanced-mode"] || DEFAULT_DNS_CONFIG["enhanced-mode"];
dnsConfig["enhanced-mode"] || DEFAULT_DNS_CONFIG["enhanced-mode"];
const validEnhancedMode =
enhancedMode === "fake-ip" || enhancedMode === "redir-host"
? enhancedMode
: DEFAULT_DNS_CONFIG["enhanced-mode"];
const fakeIpFilterMode =
config["fake-ip-filter-mode"] ||
dnsConfig["fake-ip-filter-mode"] ||
DEFAULT_DNS_CONFIG["fake-ip-filter-mode"];
const validFakeIpFilterMode =
fakeIpFilterMode === "blacklist" || fakeIpFilterMode === "whitelist"
@@ -201,53 +208,55 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
: DEFAULT_DNS_CONFIG["fake-ip-filter-mode"];
setValues({
enable: config.enable ?? DEFAULT_DNS_CONFIG.enable,
listen: config.listen ?? DEFAULT_DNS_CONFIG.listen,
enable: dnsConfig.enable ?? DEFAULT_DNS_CONFIG.enable,
listen: dnsConfig.listen ?? DEFAULT_DNS_CONFIG.listen,
enhancedMode: validEnhancedMode,
fakeIpRange:
config["fake-ip-range"] ?? DEFAULT_DNS_CONFIG["fake-ip-range"],
dnsConfig["fake-ip-range"] ?? DEFAULT_DNS_CONFIG["fake-ip-range"],
fakeIpFilterMode: validFakeIpFilterMode,
preferH3: config["prefer-h3"] ?? DEFAULT_DNS_CONFIG["prefer-h3"],
preferH3: dnsConfig["prefer-h3"] ?? DEFAULT_DNS_CONFIG["prefer-h3"],
respectRules:
config["respect-rules"] ?? DEFAULT_DNS_CONFIG["respect-rules"],
useHosts: config["use-hosts"] ?? DEFAULT_DNS_CONFIG["use-hosts"],
dnsConfig["respect-rules"] ?? DEFAULT_DNS_CONFIG["respect-rules"],
useHosts: dnsConfig["use-hosts"] ?? DEFAULT_DNS_CONFIG["use-hosts"],
useSystemHosts:
config["use-system-hosts"] ?? DEFAULT_DNS_CONFIG["use-system-hosts"],
ipv6: config.ipv6 ?? DEFAULT_DNS_CONFIG.ipv6,
dnsConfig["use-system-hosts"] ??
DEFAULT_DNS_CONFIG["use-system-hosts"],
ipv6: dnsConfig.ipv6 ?? DEFAULT_DNS_CONFIG.ipv6,
fakeIpFilter:
config["fake-ip-filter"]?.join(", ") ??
dnsConfig["fake-ip-filter"]?.join(", ") ??
DEFAULT_DNS_CONFIG["fake-ip-filter"].join(", "),
nameserver:
config.nameserver?.join(", ") ??
dnsConfig.nameserver?.join(", ") ??
DEFAULT_DNS_CONFIG.nameserver.join(", "),
fallback:
config.fallback?.join(", ") ?? DEFAULT_DNS_CONFIG.fallback.join(", "),
dnsConfig.fallback?.join(", ") ?? DEFAULT_DNS_CONFIG.fallback.join(", "),
defaultNameserver:
config["default-nameserver"]?.join(", ") ??
dnsConfig["default-nameserver"]?.join(", ") ??
DEFAULT_DNS_CONFIG["default-nameserver"].join(", "),
proxyServerNameserver:
config["proxy-server-nameserver"]?.join(", ") ??
dnsConfig["proxy-server-nameserver"]?.join(", ") ??
(DEFAULT_DNS_CONFIG["proxy-server-nameserver"]?.join(", ") || ""),
directNameserver:
config["direct-nameserver"]?.join(", ") ??
dnsConfig["direct-nameserver"]?.join(", ") ??
(DEFAULT_DNS_CONFIG["direct-nameserver"]?.join(", ") || ""),
directNameserverFollowPolicy:
config["direct-nameserver-follow-policy"] ??
dnsConfig["direct-nameserver-follow-policy"] ??
DEFAULT_DNS_CONFIG["direct-nameserver-follow-policy"],
fallbackGeoip:
config["fallback-filter"]?.geoip ??
dnsConfig["fallback-filter"]?.geoip ??
DEFAULT_DNS_CONFIG["fallback-filter"].geoip,
fallbackGeoipCode:
config["fallback-filter"]?.["geoip-code"] ??
dnsConfig["fallback-filter"]?.["geoip-code"] ??
DEFAULT_DNS_CONFIG["fallback-filter"]["geoip-code"],
fallbackIpcidr:
config["fallback-filter"]?.ipcidr?.join(", ") ??
dnsConfig["fallback-filter"]?.ipcidr?.join(", ") ??
DEFAULT_DNS_CONFIG["fallback-filter"].ipcidr.join(", "),
fallbackDomain:
config["fallback-filter"]?.domain?.join(", ") ??
dnsConfig["fallback-filter"]?.domain?.join(", ") ??
DEFAULT_DNS_CONFIG["fallback-filter"].domain.join(", "),
nameserverPolicy:
formatNameserverPolicy(config["nameserver-policy"]) || "",
formatNameserverPolicy(dnsConfig["nameserver-policy"]) || "",
hosts: formatHosts(hostsConfig) || "",
});
};
@@ -281,99 +290,38 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
fallbackDomain:
DEFAULT_DNS_CONFIG["fallback-filter"].domain?.join(", ") || "",
nameserverPolicy: "",
hosts: "",
});
// 更新YAML编辑器内容
updateYamlFromValues(DEFAULT_DNS_CONFIG);
updateYamlFromValues();
};
// 从表单值更新YAML内容
const updateYamlFromValues = (dnsConfig: any = null) => {
// 如果提供了dnsConfig直接使用它
if (dnsConfig) {
setYamlContent(yaml.dump(dnsConfig, { forceQuotes: true }));
return;
const updateYamlFromValues = () => {
const config: Record<string, any> = {};
const dnsConfig = generateDnsConfig();
if (Object.keys(dnsConfig).length > 0) {
config.dns = dnsConfig;
}
// 否则从当前表单值生成
const config = generateDnsConfig();
const hosts = parseHosts(values.hosts);
if (Object.keys(hosts).length > 0) {
config.hosts = hosts;
}
setYamlContent(yaml.dump(config, { forceQuotes: true }));
};
// 从YAML更新表单值
const updateValuesFromYaml = () => {
try {
const dnsConfig = yaml.load(yamlContent) as any;
if (!dnsConfig) return;
const enhancedMode =
dnsConfig["enhanced-mode"] || DEFAULT_DNS_CONFIG["enhanced-mode"];
// 确保enhancedMode只能是"fake-ip"或"redir-host"
const validEnhancedMode =
enhancedMode === "fake-ip" || enhancedMode === "redir-host"
? enhancedMode
: DEFAULT_DNS_CONFIG["enhanced-mode"];
const fakeIpFilterMode =
dnsConfig["fake-ip-filter-mode"] ||
DEFAULT_DNS_CONFIG["fake-ip-filter-mode"];
// 确保fakeIpFilterMode只能是"blacklist"或"whitelist"
const validFakeIpFilterMode =
fakeIpFilterMode === "blacklist" || fakeIpFilterMode === "whitelist"
? fakeIpFilterMode
: DEFAULT_DNS_CONFIG["fake-ip-filter-mode"];
setValues({
enable: dnsConfig.enable ?? DEFAULT_DNS_CONFIG.enable,
listen: dnsConfig.listen ?? DEFAULT_DNS_CONFIG.listen,
enhancedMode: validEnhancedMode,
fakeIpRange:
dnsConfig["fake-ip-range"] ?? DEFAULT_DNS_CONFIG["fake-ip-range"],
fakeIpFilterMode: validFakeIpFilterMode,
preferH3: dnsConfig["prefer-h3"] ?? DEFAULT_DNS_CONFIG["prefer-h3"],
respectRules:
dnsConfig["respect-rules"] ?? DEFAULT_DNS_CONFIG["respect-rules"],
useHosts: dnsConfig["use-hosts"] ?? DEFAULT_DNS_CONFIG["use-hosts"],
useSystemHosts:
dnsConfig["use-system-hosts"] ??
DEFAULT_DNS_CONFIG["use-system-hosts"],
ipv6: dnsConfig.ipv6 ?? DEFAULT_DNS_CONFIG.ipv6,
fakeIpFilter:
dnsConfig["fake-ip-filter"]?.join(", ") ??
DEFAULT_DNS_CONFIG["fake-ip-filter"].join(", "),
defaultNameserver:
dnsConfig["default-nameserver"]?.join(", ") ??
DEFAULT_DNS_CONFIG["default-nameserver"].join(", "),
nameserver:
dnsConfig.nameserver?.join(", ") ??
DEFAULT_DNS_CONFIG.nameserver.join(", "),
fallback:
dnsConfig.fallback?.join(", ") ??
DEFAULT_DNS_CONFIG.fallback.join(", "),
proxyServerNameserver:
dnsConfig["proxy-server-nameserver"]?.join(", ") ??
(DEFAULT_DNS_CONFIG["proxy-server-nameserver"]?.join(", ") || ""),
directNameserver:
dnsConfig["direct-nameserver"]?.join(", ") ??
(DEFAULT_DNS_CONFIG["direct-nameserver"]?.join(", ") || ""),
directNameserverFollowPolicy:
dnsConfig["direct-nameserver-follow-policy"] ??
DEFAULT_DNS_CONFIG["direct-nameserver-follow-policy"],
fallbackGeoip:
dnsConfig["fallback-filter"]?.geoip ??
DEFAULT_DNS_CONFIG["fallback-filter"].geoip,
fallbackGeoipCode:
dnsConfig["fallback-filter"]?.["geoip-code"] ??
DEFAULT_DNS_CONFIG["fallback-filter"]["geoip-code"],
fallbackIpcidr:
dnsConfig["fallback-filter"]?.ipcidr?.join(", ") ??
DEFAULT_DNS_CONFIG["fallback-filter"].ipcidr.join(", "),
fallbackDomain:
dnsConfig["fallback-filter"]?.domain?.join(", ") ??
DEFAULT_DNS_CONFIG["fallback-filter"].domain.join(", "),
nameserverPolicy:
formatNameserverPolicy(dnsConfig["nameserver-policy"]) || "",
});
const parsedYaml = yaml.load(yamlContent) as any;
if (!parsedYaml) return;
updateValuesFromConfig(parsedYaml);
} catch (err: any) {
showNotice('error', t("Invalid YAML format"));
}
@@ -427,18 +375,62 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
return result;
};
// 格式化hosts为字符串
const formatHosts = (hosts: any): string => {
if (!hosts || typeof hosts !== "object") return "";
let result: string[] = [];
Object.entries(hosts).forEach(([domain, value]) => {
if (Array.isArray(value)) {
// 处理数组格式的IP
const ipsStr = value.join(";");
result.push(`${domain}=${ipsStr}`);
} else {
// 处理单个IP或域名
result.push(`${domain}=${value}`);
}
});
return result.join(", ");
};
// 解析hosts字符串为对象
const parseHosts = (str: string): Record<string, any> => {
const result: Record<string, any> = {};
if (!str) return result;
str.split(",").forEach((item) => {
const parts = item.trim().split("=");
if (parts.length < 2) return;
const domain = parts[0].trim();
const valueStr = parts.slice(1).join("=").trim();
// 检查是否包含多个分号分隔的IP
if (valueStr.includes(";")) {
result[domain] = valueStr
.split(";")
.map((s) => s.trim())
.filter(Boolean);
} else {
result[domain] = valueStr;
}
});
return result;
};
// 初始化时设置默认YAML
useEffect(() => {
updateYamlFromValues(DEFAULT_DNS_CONFIG);
updateYamlFromValues();
}, []);
// 切换编辑模式时的处理
useEffect(() => {
if (visualization) {
// 从YAML更新表单值
updateValuesFromYaml();
} else {
// 从表单值更新YAML
updateYamlFromValues();
}
}, [visualization]);
@@ -503,27 +495,63 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
// 处理保存操作
const onSave = useLockFn(async () => {
try {
let dnsConfig;
let config: Record<string, any>;
if (visualization) {
// 使用表单值
dnsConfig = generateDnsConfig();
// 使用表单值生成配置
config = {};
const dnsConfig = generateDnsConfig();
if (Object.keys(dnsConfig).length > 0) {
config.dns = dnsConfig;
}
const hosts = parseHosts(values.hosts);
if (Object.keys(hosts).length > 0) {
config.hosts = hosts;
}
} else {
// 使用YAML编辑器的值
const parsedConfig = yaml.load(yamlContent);
if (typeof parsedConfig !== "object" || parsedConfig === null) {
throw new Error(t("Invalid DNS configuration"));
throw new Error(t("Invalid configuration"));
}
dnsConfig = parsedConfig;
config = parsedConfig as Record<string, any>;
}
// 不直接应用到clash配置而是保存到单独文件
await invoke("save_dns_config", { dnsConfig });
// 保存配置
await invoke("save_dns_config", { dnsConfig: config });
// 验证配置
const [isValid, errorMsg] = await invoke<[boolean, string]>("validate_dns_config", {});
if (!isValid) {
let cleanErrorMsg = errorMsg;
// 提取关键错误信息
if (errorMsg.includes("level=error")) {
const errorLines = errorMsg.split('\n').filter(line =>
line.includes("level=error") ||
line.includes("level=fatal") ||
line.includes("failed")
);
if (errorLines.length > 0) {
cleanErrorMsg = errorLines.map(line => {
const msgMatch = line.match(/msg="([^"]+)"/);
return msgMatch ? msgMatch[1] : line;
}).join(", ");
}
}
showNotice('error', t("DNS configuration error") + ": " + cleanErrorMsg);
return;
}
// 如果DNS开关当前是打开的则需要应用新的DNS配置
if (clash?.dns?.enable) {
await invoke("apply_dns_config", { apply: true });
mutateClash(); // 刷新UI
mutateClash();
}
setOpen(false);
@@ -539,15 +567,13 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
// 允许YAML编辑后立即分析和更新表单值
try {
const dnsConfig = yaml.load(value) as any;
if (dnsConfig && typeof dnsConfig === "object") {
// 稍微延迟更新,以避免性能问题
const config = yaml.load(value) as any;
if (config && typeof config === "object") {
setTimeout(() => {
updateValuesFromYaml();
updateValuesFromConfig(config);
}, 300);
}
} catch (err) {
// 忽略解析错误只有当YAML有效时才更新表单
console.log("YAML解析错误忽略自动更新", err);
}
};
@@ -568,7 +594,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
// 当可视化编辑模式下的值变化时自动更新YAML
if (visualization) {
setTimeout(() => {
updateYamlFromValues(null);
updateYamlFromValues();
}, 0);
}
@@ -628,6 +654,13 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
{visualization ? (
<List>
<Typography
variant="subtitle1"
sx={{ mt: 1, mb: 1, fontWeight: "bold" }}
>
{t("DNS Settings")}
</Typography>
<Item>
<ListItemText primary={t("Enable DNS")} />
<Switch
@@ -714,7 +747,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
<Item>
<ListItemText
primary={t("Respect Rules")}
secondary={t("DNS连接遵守路由规则")}
secondary={t("DNS connections follow routing rules")}
/>
<Switch
edge="end"
@@ -750,7 +783,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
<Item>
<ListItemText
primary={t("Direct Nameserver Follow Policy")}
secondary={t("是否遵循nameserver-policy")}
secondary={t("Whether to follow nameserver policy")}
/>
<Switch
edge="end"
@@ -942,6 +975,31 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
placeholder="+.google.com, +.facebook.com, +.youtube.com"
/>
</Item>
{/* Hosts 配置部分 */}
<Typography
variant="subtitle1"
sx={{ mt: 3, mb: 0, fontWeight: "bold" }}
>
{t("Hosts Settings")}
</Typography>
<Item sx={{ flexDirection: "column", alignItems: "flex-start" }}>
<ListItemText
primary={t("Hosts")}
secondary={t("Custom domain to IP or domain mapping")}
/>
<TextField
fullWidth
multiline
minRows={2}
maxRows={4}
size="small"
value={values.hosts}
onChange={handleChange("hosts")}
placeholder="*.clash.dev=127.0.0.1, alpha.clash.dev=::1, test.com=1.1.1.1;2.2.2.2, baidu.com=google.com"
/>
</Item>
</List>
) : (
<MonacoEditor

View File

@@ -507,13 +507,13 @@
"Prefer H3": "Prefer H3",
"DNS DOH使用HTTP/3": "DNS DOH uses HTTP/3",
"Respect Rules": "Respect Rules",
"DNS连接遵守路由规则": "DNS connections follow routing rules",
"DNS connections follow routing rules": "DNS connections follow routing rules",
"Use Hosts": "Use Hosts",
"Enable to resolve hosts through hosts file": "Enable to resolve hosts through hosts file",
"Use System Hosts": "Use System Hosts",
"Enable to resolve hosts through system hosts file": "Enable to resolve hosts through system hosts file",
"Direct Nameserver Follow Policy": "Direct Nameserver Follow Policy",
"是否遵循nameserver-policy": "Whether to follow nameserver policy",
"Whether to follow nameserver policy": "Whether to follow nameserver policy",
"Default Nameserver": "Default Nameserver",
"Default DNS servers used to resolve DNS servers": "Default DNS servers used to resolve DNS servers",
"Nameserver": "Nameserver",
@@ -536,6 +536,9 @@
"IP CIDRs not using fallback servers": "IP CIDRs not using fallback servers, comma separated",
"Fallback Domain": "Fallback Domain",
"Domains using fallback servers": "Domains using fallback servers, comma separated",
"Hosts Settings": "Hosts Settings",
"Hosts": "Hosts",
"Custom domain to IP or domain mapping": "Custom domain to IP or domain mapping",
"Enable Alpha Channel": "Enable Alpha Channel",
"Alpha versions may contain experimental features and bugs": "Alpha versions may contain experimental features and bugs",
"Home Settings": "Home Settings",

View File

@@ -501,13 +501,13 @@
"Prefer H3": "Предпочитать H3",
"DNS DOH使用HTTP/3": "DNS DOH использует http/3",
"Respect Rules": "Приоритизировать правила",
"DNS连接遵守路由规则": "Соединения DNS следуют правилам маршрутизации",
"DNS connections follow routing rules": "Соединения DNS следуют правилам маршрутизации",
"Use Hosts": "Использовать файл Hosts",
"Enable to resolve hosts through hosts file": "Включить разрешение хостов через файл Hosts",
"Use System Hosts": "Использовать системный файл Hosts",
"Enable to resolve hosts through system hosts file": "Включить разрешение хостов через системный файл Hosts",
"Direct Nameserver Follow Policy": "Прямой сервер имен следует политике",
"是否遵循nameserver-policy": "Следовать ли политике DNS-серверов",
"Whether to follow nameserver policy": "Следовать ли политике DNS-серверов",
"Default Nameserver": "DNS-сервер по умолчанию",
"Default DNS servers used to resolve DNS servers": "DNS-серверы по умолчанию, используемые для разрешения адресов серверов DNS",
"Nameserver": "DNS-сервер",

View File

@@ -507,13 +507,13 @@
"Prefer H3": "优先使用 HTTP/3",
"DNS DOH使用HTTP/3": "DNS DOH 使用 HTTP/3 协议",
"Respect Rules": "遵循路由规则",
"DNS连接遵守路由规则": "DNS 连接遵循路由规则",
"DNS connections follow routing rules": "DNS 连接遵循路由规则",
"Use Hosts": "使用 Hosts",
"Enable to resolve hosts through hosts file": "启用通过 hosts 文件解析域名",
"Use System Hosts": "使用系统 Hosts",
"Enable to resolve hosts through system hosts file": "启用通过系统 hosts 文件解析域名",
"Direct Nameserver Follow Policy": "直连域名服务器遵循策略",
"是否遵循nameserver-policy": "是否遵循 nameserver-policy 设置",
"Whether to follow nameserver policy": "是否遵循 nameserver-policy 设置",
"Default Nameserver": "默认域名服务器",
"Default DNS servers used to resolve DNS servers": "用于解析 DNS 服务器的默认 DNS 服务器",
"Nameserver": "域名服务器",
@@ -536,6 +536,9 @@
"IP CIDRs not using fallback servers": "不使用回退服务器的 IP CIDR用逗号分隔",
"Fallback Domain": "回退域名",
"Domains using fallback servers": "使用回退服务器的域名,用逗号分隔",
"Hosts Settings": "Hosts 设置",
"Hosts": "Hosts",
"Custom domain to IP or domain mapping": "自定义域名到 IP 或域名的映射,用逗号分隔",
"Enable Alpha Channel": "启用 Alpha 通道",
"Alpha versions may contain experimental features and bugs": "Alpha 版本可能包含实验性功能和已知问题",
"Home Settings": "首页设置",