feat: 实现 fake dns
Docker Build / docker-build (push) Successful in 18s

This commit is contained in:
chuan
2026-05-28 00:34:54 +08:00
Unverified
parent 4079623636
commit b0af17bcb4
7 changed files with 58 additions and 3 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "pyxray"
version = "1.0.4"
version = "1.0.5"
description = "A lightweight Linux xray control plane."
readme = "README.md"
requires-python = ">=3.14"
+19 -1
View File
@@ -31,6 +31,8 @@ def generate_xray_config(node: Node, settings: XrayConfigSettings | None = None)
log = _build_log(settings)
if log:
config["log"] = log
if _fakedns_enabled(settings):
config["fakedns"] = [{"ipPool": "198.18.0.0/15", "poolSize": 65535}]
config["routing"]["rules"].extend(_build_dns_routing(settings))
config["routing"]["rules"].append({"type": "field", "inboundTag": _dns_inbound_tags(settings), "outboundTag": "dns-out"})
config["routing"]["rules"].extend(_build_rule_port_routing(settings))
@@ -157,9 +159,12 @@ def _transparent_inbounds(settings: XrayConfigSettings) -> list[dict[str, Any]]:
def _with_sniffing(inbound: dict[str, Any], settings: XrayConfigSettings) -> dict[str, Any]:
if settings.inbounds.inbound_sniffing == "disable":
return inbound
dest_override = settings.inbounds.inbound_sniffing.split(",")
if _fakedns_enabled(settings) and "fakedns" not in dest_override:
dest_override.append("fakedns")
inbound["sniffing"] = {
"enabled": True,
"destOverride": settings.inbounds.inbound_sniffing.split(","),
"destOverride": dest_override,
"domainsExcluded": _split_lines(settings.inbounds.domains_excluded),
"routeOnly": settings.inbounds.route_only,
}
@@ -207,6 +212,9 @@ def _dns_inbound_tags(settings: XrayConfigSettings) -> list[str]:
def _build_dns(settings: XrayConfigSettings, node: Node) -> dict[str, Any]:
servers: list[Any] = []
fakedns_domains = _fakedns_domains(settings)
if fakedns_domains:
servers.append({"address": "fakedns", "domains": fakedns_domains})
routing_domains = _domains_to_lookup(settings, node)
for rule in settings.dns.rules:
domains = _split_lines(rule.domains)
@@ -230,6 +238,16 @@ def _build_dns(settings: XrayConfigSettings, node: Node) -> dict[str, Any]:
return dns
def _fakedns_enabled(settings: XrayConfigSettings) -> bool:
return settings.dns.special_mode == "fakedns"
def _fakedns_domains(settings: XrayConfigSettings) -> list[str]:
if not _fakedns_enabled(settings):
return []
return _split_lines(settings.dns.fakedns_domains) or ["geosite:geolocation-!cn"]
def _dns_server(rule: DnsRuleSettings, domains: list[str]) -> Any:
address, port = _parse_dns_addr(rule.server)
server_address = rule.server if "://" in rule.server else address
+1
View File
@@ -126,6 +126,7 @@ class DnsSettings:
)
antipollution: str = "closed"
special_mode: str = "none"
fakedns_domains: str = "geosite:geolocation-!cn"
@dataclass(slots=True)
+4
View File
@@ -43,6 +43,10 @@
</select>
</label>
</div>
<label class="config-field mt-4">
<span>FakeDNS 域名范围,每行一个 domain/geosite/keyword 规则</span>
<textarea name="dns.fakedns_domains" placeholder="geosite:geolocation-!cn">{{ settings.dns.fakedns_domains }}</textarea>
</label>
<label class="config-field mt-4">
<span>DNS 规则,每行 server|domains|outbound</span>
<textarea name="dns.rules">{% for item in settings.dns.rules %}{{ item.server }}|{{ item.domains }}|{{ item.outbound }}
+1
View File
@@ -203,6 +203,7 @@ def _settings_from_request() -> XrayConfigSettings:
settings.dns.local_dns_listen = _bool("dns.local_dns_listen")
settings.dns.antipollution = form.get("dns.antipollution", settings.dns.antipollution)
settings.dns.special_mode = form.get("dns.special_mode", settings.dns.special_mode)
settings.dns.fakedns_domains = form.get("dns.fakedns_domains", settings.dns.fakedns_domains)
settings.dns.rules = [
DnsRuleSettings(server=parts[0], domains=parts[1], outbound=parts[2])
for parts in _split_table("dns.rules", 3)
+26
View File
@@ -89,6 +89,8 @@ def test_settings_defaults_match_v2raya_core_values() -> None:
assert settings.transparent.docker_transparent_cidrs == "172.16.0.0/12"
assert settings.transparent.output_bypass_rules == ""
assert settings.dns.query_strategy == "UseIPv4"
assert settings.dns.special_mode == "none"
assert settings.dns.fakedns_domains == "geosite:geolocation-!cn"
assert settings.dns.rules == [
DnsRuleSettings(server="localhost", domains="geosite:private", outbound="direct"),
DnsRuleSettings(server="223.5.5.5", domains="geosite:cn", outbound="direct"),
@@ -403,6 +405,30 @@ def test_generate_dns_rules_and_dns_routing() -> None:
assert any(rule.get("domain") == ["dns.google"] and rule.get("outboundTag") == "proxy" for rule in config["routing"]["rules"])
def test_generate_fakedns_adds_dns_server_pool_and_sniffing() -> None:
settings = XrayConfigSettings()
settings.transparent.mode = "gfwlist"
settings.transparent.type = "redirect"
settings.dns.special_mode = "fakedns"
config = generate_xray_config(parse_node_link(_ss_link()), settings)
assert config["fakedns"] == [{"ipPool": "198.18.0.0/15", "poolSize": 65535}]
assert {"address": "fakedns", "domains": ["geosite:geolocation-!cn"]} in config["dns"]["servers"]
assert "fakedns" in _inbound(config, "transparent")["sniffing"]["destOverride"]
assert "fakedns" in _inbound(config, "rule-mixed")["sniffing"]["destOverride"]
def test_generate_fakedns_uses_custom_domain_scope() -> None:
settings = XrayConfigSettings()
settings.dns.special_mode = "fakedns"
settings.dns.fakedns_domains = "geosite:gfw\nkeyword:example"
config = generate_xray_config(parse_node_link(_ss_link()), settings)
assert {"address": "fakedns", "domains": ["geosite:gfw", "keyword:example"]} in config["dns"]["servers"]
def test_validate_settings_rejects_invalid_values() -> None:
settings = XrayConfigSettings()
settings.transparent.type = "bad"
+6 -1
View File
@@ -667,7 +667,8 @@ def test_xray_config_api_saves_settings_from_form_controls(tmp_path: Path) -> No
"dns.query_strategy": "UseIPv4",
"dns.local_dns_listen": "on",
"dns.antipollution": "closed",
"dns.special_mode": "none",
"dns.special_mode": "fakedns",
"dns.fakedns_domains": "geosite:gfw\nkeyword:example",
"dns.rules": "localhost|geosite:private|direct\n8.8.8.8||proxy",
"outbounds.0.tag": "proxy",
"outbounds.0.probe_url": "https://www.gstatic.com/generate_204",
@@ -691,6 +692,8 @@ def test_xray_config_api_saves_settings_from_form_controls(tmp_path: Path) -> No
assert "auth_password = \"secret\"" in saved.get_json()["settings_toml"]
assert "route_only = true" in saved.get_json()["settings_toml"]
assert "output_bypass_rules = \"tcp 117.72.47.28:33010\"" in saved.get_json()["settings_toml"]
assert "special_mode = \"fakedns\"" in saved.get_json()["settings_toml"]
assert "fakedns_domains = \"geosite:gfw\\nkeyword:example\"" in saved.get_json()["settings_toml"]
assert generated.status_code == 200
assert config["log"]["loglevel"] == "error"
assert any(inbound["tag"] == "rule-mixed" and inbound["port"] == 20181 for inbound in config["inbounds"])
@@ -699,6 +702,8 @@ def test_xray_config_api_saves_settings_from_form_controls(tmp_path: Path) -> No
]
assert config["outbounds"][0]["mux"] == {"enabled": True, "concurrency": 16}
assert config["dns"]["queryStrategy"] == "UseIPv4"
assert config["fakedns"] == [{"ipPool": "198.18.0.0/15", "poolSize": 65535}]
assert {"address": "fakedns", "domains": ["geosite:gfw", "keyword:example"]} in config["dns"]["servers"]
def test_xray_config_api_mux_zero_disables_mux(tmp_path: Path) -> None: