+1
-1
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -126,6 +126,7 @@ class DnsSettings:
|
||||
)
|
||||
antipollution: str = "closed"
|
||||
special_mode: str = "none"
|
||||
fakedns_domains: str = "geosite:geolocation-!cn"
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user