fix: 修复docker内容器使用透明代理问题
This commit is contained in:
@@ -106,10 +106,11 @@ def _transparent_inbounds(settings: XrayConfigSettings) -> list[dict[str, Any]]:
|
||||
return []
|
||||
if settings.transparent.type in {"redirect", "tproxy"}:
|
||||
tproxy = settings.transparent.type
|
||||
listen = "0.0.0.0" if settings.transparent.docker_transparent and settings.transparent.type == "redirect" else "127.0.0.1"
|
||||
return [
|
||||
_with_sniffing(
|
||||
{
|
||||
"listen": "127.0.0.1",
|
||||
"listen": listen,
|
||||
"port": settings.transparent.port,
|
||||
"protocol": "dokodemo-door",
|
||||
"settings": {"network": "tcp,udp", "followRedirect": True},
|
||||
|
||||
@@ -81,6 +81,7 @@ class TransparentSettings:
|
||||
socks_port: int = 52306
|
||||
ipforward: bool = False
|
||||
docker_transparent: bool = True
|
||||
docker_transparent_cidrs: str = "172.16.0.0/12"
|
||||
tproxy_excluded_interfaces: str = "docker*,veth*,wg*,ppp*,br-*"
|
||||
tproxy_white_country_codes: list[str] = field(default_factory=list)
|
||||
tproxy_white_custom_ips: list[str] = field(default_factory=list)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ipaddress
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
@@ -171,13 +172,13 @@ def _redirect_rules(settings: XrayConfigSettings, *, backend: str, ipv6: bool, n
|
||||
"iptables -w 2 -t nat -N TP_OUT",
|
||||
"iptables -w 2 -t nat -N TP_PRE",
|
||||
"iptables -w 2 -t nat -N TP_RULE",
|
||||
*[f"iptables -w 2 -t nat -A TP_RULE -d {cidr} -j RETURN" for cidr in IPV4_RESERVED_CIDRS],
|
||||
*[f"iptables -w 2 -t nat -A TP_RULE -d {cidr} -j RETURN" for cidr in _ipv4_reserved_cidrs(settings)],
|
||||
"iptables -w 2 -t nat -A TP_RULE -m mark --mark 0x80/0x80 -j RETURN",
|
||||
*[f"iptables -w 2 -t nat -A TP_RULE -i {_iptables_interface(value)} -j RETURN" for value in _excluded_interfaces(settings)],
|
||||
f"iptables -w 2 -t nat -A TP_RULE -p tcp -j REDIRECT --to-ports {settings.transparent.port}",
|
||||
"iptables -w 2 -t nat -I PREROUTING -p tcp -j TP_PRE",
|
||||
"iptables -w 2 -t nat -I OUTPUT -p tcp -j TP_OUT",
|
||||
"iptables -w 2 -t nat -A TP_PRE -j TP_RULE",
|
||||
*_redirect_prerouting_jumps(settings),
|
||||
"iptables -w 2 -t nat -A TP_OUT -j TP_RULE",
|
||||
]
|
||||
cleanup = [
|
||||
@@ -227,8 +228,7 @@ def _tproxy_rules(settings: XrayConfigSettings, *, backend: str, ipv6: bool, nft
|
||||
"iptables -w 2 -t mangle -A TP_OUT -p tcp -m addrtype --src-type LOCAL ! --dst-type LOCAL -j TP_RULE",
|
||||
"iptables -w 2 -t mangle -A TP_OUT -p udp -m addrtype --src-type LOCAL ! --dst-type LOCAL -j TP_RULE",
|
||||
"iptables -w 2 -t mangle -A TP_PRE -i lo -m mark ! --mark 0x40/0xc0 -j RETURN",
|
||||
"iptables -w 2 -t mangle -A TP_PRE -p tcp -m addrtype ! --src-type LOCAL ! --dst-type LOCAL -j TP_RULE",
|
||||
"iptables -w 2 -t mangle -A TP_PRE -p udp -m addrtype ! --src-type LOCAL ! --dst-type LOCAL -j TP_RULE",
|
||||
*_tproxy_prerouting_jumps(settings),
|
||||
f"iptables -w 2 -t mangle -A TP_PRE -p tcp -m mark --mark 0x40/0xc0 -j TPROXY --on-port {settings.transparent.port} --on-ip 127.0.0.1",
|
||||
f"iptables -w 2 -t mangle -A TP_PRE -p udp -m mark --mark 0x40/0xc0 -j TPROXY --on-port {settings.transparent.port} --on-ip 127.0.0.1",
|
||||
"iptables -w 2 -t mangle -A TP_RULE -j CONNMARK --restore-mark",
|
||||
@@ -283,7 +283,7 @@ def _ip_forward_script(settings: XrayConfigSettings) -> str:
|
||||
这里保持相同语义,只把操作写成可审计脚本,不主动执行。
|
||||
"""
|
||||
|
||||
value = "1" if settings.transparent.ipforward else "0"
|
||||
value = "1" if settings.transparent.ipforward or settings.transparent.docker_transparent else "0"
|
||||
return "\n".join([
|
||||
f"printf '%s' {value} > /proc/sys/net/ipv4/ip_forward",
|
||||
f"printf '%s' {value} > /proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || true",
|
||||
@@ -322,7 +322,7 @@ def _redirect_nft_table(settings: XrayConfigSettings, *, ipv6: bool) -> str:
|
||||
interface_returns = "\n".join(f' iifname "{value}" return' for value in _excluded_interfaces(settings))
|
||||
whitelist6 = _nft_set("whitelist6", "ipv6_addr", IPV6_RESERVED_CIDRS) if ipv6 else ""
|
||||
return f"""table inet v2raya {{
|
||||
{_nft_set("whitelist", "ipv4_addr", IPV4_RESERVED_CIDRS)}
|
||||
{_nft_set("whitelist", "ipv4_addr", _ipv4_reserved_cidrs(settings))}
|
||||
{whitelist6}
|
||||
set interface {{
|
||||
type ipv4_addr
|
||||
@@ -348,7 +348,7 @@ def _redirect_nft_table(settings: XrayConfigSettings, *, ipv6: bool) -> str:
|
||||
|
||||
chain tp_pre {{
|
||||
type nat hook prerouting priority dstnat - 5
|
||||
{nfproto} meta l4proto tcp jump tp_rule
|
||||
{_redirect_nft_prerouting_jumps(settings, nfproto)}
|
||||
}}
|
||||
|
||||
chain tp_out {{
|
||||
@@ -382,7 +382,7 @@ def _tproxy_nft_table(settings: XrayConfigSettings, *, ipv6: bool) -> str:
|
||||
|
||||
chain tp_pre {{
|
||||
iifname "lo" mark & 0xc0 != 0x40 return
|
||||
meta l4proto {{ tcp, udp }} fib saddr type != local fib daddr type != local jump tp_rule
|
||||
{_tproxy_nft_prerouting_jumps(settings, nfproto)}
|
||||
meta l4proto {{ tcp, udp }} mark & 0xc0 == 0x40 tproxy ip to 127.0.0.1:{settings.transparent.port}
|
||||
{'meta l4proto { tcp, udp } mark & 0xc0 == 0x40 tproxy ip6 to [::1]:' + str(settings.transparent.port) if ipv6 else ''}
|
||||
}}
|
||||
@@ -489,6 +489,66 @@ def _excluded_interfaces(settings: XrayConfigSettings) -> list[str]:
|
||||
return [item for item in interfaces if not _is_docker_interface_pattern(item)]
|
||||
|
||||
|
||||
def _ipv4_reserved_cidrs(settings: XrayConfigSettings) -> list[str]:
|
||||
return IPV4_RESERVED_CIDRS.copy()
|
||||
|
||||
|
||||
def _docker_transparent_cidrs(settings: XrayConfigSettings) -> list[ipaddress.IPv4Network]:
|
||||
cidrs: list[ipaddress.IPv4Network] = []
|
||||
for raw in settings.transparent.docker_transparent_cidrs.split(";"):
|
||||
value = raw.strip()
|
||||
if not value:
|
||||
continue
|
||||
network = ipaddress.ip_network(value, strict=False)
|
||||
if isinstance(network, ipaddress.IPv4Network):
|
||||
cidrs.append(network)
|
||||
return cidrs
|
||||
|
||||
|
||||
def _docker_transparent_cidr_strings(settings: XrayConfigSettings) -> list[str]:
|
||||
return [str(cidr) for cidr in _docker_transparent_cidrs(settings)]
|
||||
|
||||
|
||||
def _redirect_prerouting_jumps(settings: XrayConfigSettings) -> list[str]:
|
||||
cidrs = _docker_transparent_cidr_strings(settings)
|
||||
if not settings.transparent.docker_transparent or not cidrs:
|
||||
return ["iptables -w 2 -t nat -A TP_PRE -j TP_RULE"]
|
||||
return [f"iptables -w 2 -t nat -A TP_PRE -s {cidr} -j TP_RULE" for cidr in cidrs]
|
||||
|
||||
|
||||
def _tproxy_prerouting_jumps(settings: XrayConfigSettings) -> list[str]:
|
||||
cidrs = _docker_transparent_cidr_strings(settings)
|
||||
if not settings.transparent.docker_transparent or not cidrs:
|
||||
return [
|
||||
"iptables -w 2 -t mangle -A TP_PRE -p tcp -m addrtype ! --src-type LOCAL ! --dst-type LOCAL -j TP_RULE",
|
||||
"iptables -w 2 -t mangle -A TP_PRE -p udp -m addrtype ! --src-type LOCAL ! --dst-type LOCAL -j TP_RULE",
|
||||
]
|
||||
return [
|
||||
f"iptables -w 2 -t mangle -A TP_PRE -s {cidr} -p tcp -m addrtype ! --src-type LOCAL ! --dst-type LOCAL -j TP_RULE"
|
||||
for cidr in cidrs
|
||||
] + [
|
||||
f"iptables -w 2 -t mangle -A TP_PRE -s {cidr} -p udp -m addrtype ! --src-type LOCAL ! --dst-type LOCAL -j TP_RULE"
|
||||
for cidr in cidrs
|
||||
]
|
||||
|
||||
|
||||
def _redirect_nft_prerouting_jumps(settings: XrayConfigSettings, nfproto: str) -> str:
|
||||
cidrs = _docker_transparent_cidr_strings(settings)
|
||||
if not settings.transparent.docker_transparent or not cidrs:
|
||||
return f" {nfproto} meta l4proto tcp jump tp_rule"
|
||||
return "\n".join(f" {nfproto} ip saddr {cidr} meta l4proto tcp jump tp_rule" for cidr in cidrs)
|
||||
|
||||
|
||||
def _tproxy_nft_prerouting_jumps(settings: XrayConfigSettings, nfproto: str) -> str:
|
||||
cidrs = _docker_transparent_cidr_strings(settings)
|
||||
if not settings.transparent.docker_transparent or not cidrs:
|
||||
return f" {nfproto} meta l4proto {{ tcp, udp }} fib saddr type != local fib daddr type != local jump tp_rule"
|
||||
return "\n".join(
|
||||
f" {nfproto} ip saddr {cidr} meta l4proto {{ tcp, udp }} fib saddr type != local fib daddr type != local jump tp_rule"
|
||||
for cidr in cidrs
|
||||
)
|
||||
|
||||
|
||||
def _is_docker_interface_pattern(value: str) -> bool:
|
||||
normalized = value.strip().lower().replace("+", "*")
|
||||
return normalized.startswith(("docker", "veth", "br-"))
|
||||
|
||||
@@ -201,17 +201,22 @@ function initTransparentSettings() {
|
||||
const typeSelect = document.querySelector("#transparent-type");
|
||||
const excludedInput = document.querySelector("#tproxy-excluded-interfaces");
|
||||
const dockerTransparent = document.querySelector("#docker-transparent");
|
||||
if (!typeSelect || !excludedInput || !dockerTransparent) return;
|
||||
const dockerCidrs = document.querySelector("#docker-transparent-cidrs");
|
||||
if (!typeSelect || !excludedInput || !dockerTransparent || !dockerCidrs) return;
|
||||
|
||||
const update = () => {
|
||||
const disabled = !["redirect", "tproxy"].includes(typeSelect.value);
|
||||
const cidrsDisabled = disabled || dockerTransparent.value !== "on";
|
||||
excludedInput.disabled = disabled;
|
||||
dockerTransparent.disabled = disabled;
|
||||
dockerCidrs.disabled = cidrsDisabled;
|
||||
excludedInput.classList.toggle("opacity-50", disabled);
|
||||
dockerTransparent.classList.toggle("opacity-50", disabled);
|
||||
dockerCidrs.classList.toggle("opacity-50", cidrsDisabled);
|
||||
};
|
||||
|
||||
typeSelect.addEventListener("change", update);
|
||||
dockerTransparent.addEventListener("change", update);
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
@@ -57,4 +57,8 @@
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<label class="config-field mt-4">
|
||||
<span>Docker 透明代理关注网段</span>
|
||||
<input id="docker-transparent-cidrs" name="transparent.docker_transparent_cidrs" value="{{ settings.transparent.docker_transparent_cidrs }}" placeholder="172.16.0.0/12;172.18.0.0/16" />
|
||||
</label>
|
||||
</section>
|
||||
|
||||
@@ -163,6 +163,10 @@ def _settings_from_request() -> XrayConfigSettings:
|
||||
"transparent.docker_transparent",
|
||||
settings.transparent.docker_transparent,
|
||||
)
|
||||
settings.transparent.docker_transparent_cidrs = form.get(
|
||||
"transparent.docker_transparent_cidrs",
|
||||
settings.transparent.docker_transparent_cidrs,
|
||||
)
|
||||
settings.transparent.tproxy_excluded_interfaces = form.get(
|
||||
"transparent.tproxy_excluded_interfaces",
|
||||
settings.transparent.tproxy_excluded_interfaces,
|
||||
|
||||
@@ -79,6 +79,53 @@ def test_docker_transparent_removes_docker_interface_exclusions() -> None:
|
||||
assert "iifname \"wg*\" return" in nft_rules.nftables
|
||||
|
||||
|
||||
def test_docker_transparent_keeps_configured_docker_cidrs_as_destination_returns() -> None:
|
||||
settings = XrayConfigSettings()
|
||||
settings.transparent.mode = "proxy"
|
||||
settings.transparent.type = "redirect"
|
||||
settings.transparent.docker_transparent = True
|
||||
settings.transparent.docker_transparent_cidrs = "172.16.0.0/12"
|
||||
|
||||
iptables_rules = generate_transparent_rules(settings, backend="iptables")
|
||||
nft_rules = generate_transparent_rules(settings, backend="nft")
|
||||
|
||||
assert "iptables -w 2 -t nat -A TP_RULE -d 172.16.0.0/12 -j RETURN" in iptables_rules.setup
|
||||
assert "172.16.0.0/12" in nft_rules.nftables
|
||||
|
||||
|
||||
def test_docker_transparent_limits_prerouting_to_configured_source_cidrs() -> None:
|
||||
settings = XrayConfigSettings()
|
||||
settings.transparent.mode = "proxy"
|
||||
settings.transparent.type = "redirect"
|
||||
settings.transparent.docker_transparent = True
|
||||
settings.transparent.docker_transparent_cidrs = "172.16.0.0/12;172.30.250.0/24"
|
||||
|
||||
iptables_rules = generate_transparent_rules(settings, backend="iptables")
|
||||
nft_rules = generate_transparent_rules(settings, backend="nft")
|
||||
|
||||
assert "iptables -w 2 -t nat -A TP_PRE -s 172.16.0.0/12 -j TP_RULE" in iptables_rules.setup
|
||||
assert "iptables -w 2 -t nat -A TP_PRE -s 172.30.250.0/24 -j TP_RULE" in iptables_rules.setup
|
||||
assert "iptables -w 2 -t nat -A TP_PRE -j TP_RULE" not in iptables_rules.setup
|
||||
assert "ip saddr 172.16.0.0/12 meta l4proto tcp jump tp_rule" in nft_rules.nftables
|
||||
assert "ip saddr 172.30.250.0/24 meta l4proto tcp jump tp_rule" in nft_rules.nftables
|
||||
|
||||
|
||||
def test_docker_transparent_limits_tproxy_prerouting_to_configured_source_cidrs() -> None:
|
||||
settings = XrayConfigSettings()
|
||||
settings.transparent.mode = "proxy"
|
||||
settings.transparent.type = "tproxy"
|
||||
settings.transparent.docker_transparent = True
|
||||
settings.transparent.docker_transparent_cidrs = "172.16.0.0/12"
|
||||
|
||||
iptables_rules = generate_transparent_rules(settings, backend="iptables")
|
||||
nft_rules = generate_transparent_rules(settings, backend="nft")
|
||||
|
||||
assert "iptables -w 2 -t mangle -A TP_PRE -s 172.16.0.0/12 -p tcp" in iptables_rules.setup
|
||||
assert "iptables -w 2 -t mangle -A TP_PRE -s 172.16.0.0/12 -p udp" in iptables_rules.setup
|
||||
assert "iptables -w 2 -t mangle -A TP_PRE -p tcp -m addrtype" not in iptables_rules.setup
|
||||
assert "ip saddr 172.16.0.0/12 meta l4proto { tcp, udp }" in nft_rules.nftables
|
||||
|
||||
|
||||
def test_close_mode_has_no_system_rules() -> None:
|
||||
settings = XrayConfigSettings()
|
||||
settings.transparent.mode = "close"
|
||||
@@ -93,6 +140,7 @@ def test_write_transparent_rule_files_outputs_auditable_scripts(tmp_path) -> Non
|
||||
settings = XrayConfigSettings()
|
||||
settings.transparent.mode = "proxy"
|
||||
settings.transparent.type = "redirect"
|
||||
settings.transparent.docker_transparent = False
|
||||
|
||||
files = write_transparent_rule_files(settings, tmp_path)
|
||||
|
||||
@@ -116,6 +164,17 @@ def test_ip_forward_script_follows_setting_like_v2raya(tmp_path) -> None:
|
||||
assert "printf '%s' 1 > /proc/sys/net/ipv6/conf/all/forwarding" in content
|
||||
|
||||
|
||||
def test_docker_transparent_forces_ip_forward_script(tmp_path) -> None:
|
||||
settings = XrayConfigSettings()
|
||||
settings.transparent.ipforward = False
|
||||
settings.transparent.docker_transparent = True
|
||||
|
||||
files = write_transparent_rule_files(settings, tmp_path)
|
||||
content = files.ip_forward.read_text(encoding="utf-8")
|
||||
|
||||
assert "printf '%s' 1 > /proc/sys/net/ipv4/ip_forward" in content
|
||||
|
||||
|
||||
def test_resolv_hijack_scripts_follow_v2raya_redirect_dns_behavior(tmp_path) -> None:
|
||||
settings = XrayConfigSettings()
|
||||
settings.transparent.mode = "proxy"
|
||||
|
||||
@@ -84,6 +84,7 @@ def test_settings_defaults_match_v2raya_core_values() -> None:
|
||||
assert settings.transparent.type == "redirect"
|
||||
assert settings.transparent.port == 52345
|
||||
assert settings.transparent.docker_transparent is True
|
||||
assert settings.transparent.docker_transparent_cidrs == "172.16.0.0/12"
|
||||
assert settings.dns.query_strategy == "UseIPv4"
|
||||
assert settings.dns.rules == [
|
||||
DnsRuleSettings(server="localhost", domains="geosite:private", outbound="direct"),
|
||||
@@ -131,11 +132,23 @@ def test_generate_redirect_dns_inbound_when_transparent_redirect_enabled() -> No
|
||||
|
||||
config = generate_xray_config(parse_node_link(_ss_link()), settings)
|
||||
|
||||
assert _inbound(config, "transparent")["listen"] == "0.0.0.0"
|
||||
assert _inbound(config, "dns-in")["listen"] == "127.2.0.17"
|
||||
assert {"type": "field", "inboundTag": ["dns-in"], "outboundTag": "dns-out"} in config["routing"]["rules"]
|
||||
assert _outbound(config, "proxy")["streamSettings"]["sockopt"]["mark"] == 128
|
||||
|
||||
|
||||
def test_generate_redirect_transparent_inbound_stays_local_when_docker_transparent_disabled() -> None:
|
||||
settings = XrayConfigSettings()
|
||||
settings.transparent.mode = "proxy"
|
||||
settings.transparent.type = "redirect"
|
||||
settings.transparent.docker_transparent = False
|
||||
|
||||
config = generate_xray_config(parse_node_link(_ss_link()), settings)
|
||||
|
||||
assert _inbound(config, "transparent")["listen"] == "127.0.0.1"
|
||||
|
||||
|
||||
def test_generate_lan_dns_inbound_when_redirect_and_port_sharing_enabled() -> None:
|
||||
settings = XrayConfigSettings()
|
||||
settings.inbounds.port_sharing = True
|
||||
|
||||
@@ -646,6 +646,7 @@ def test_xray_config_api_generates_transparent_rule_files(tmp_path: Path) -> Non
|
||||
"transparent.socks_port": "52306",
|
||||
"transparent.ipforward": "off",
|
||||
"transparent.docker_transparent": "off",
|
||||
"transparent.docker_transparent_cidrs": "172.16.0.0/12;172.18.0.0/16",
|
||||
"transparent.tproxy_excluded_interfaces": "docker*,veth*",
|
||||
"transparent.tun_auto_route": "on",
|
||||
"dns.disable_fallback": "off",
|
||||
|
||||
Reference in New Issue
Block a user