fix: 修复docker内容器使用透明代理问题

This commit is contained in:
chuan
2026-05-27 01:51:37 +08:00
parent 4d801b321c
commit 764b276c5b
9 changed files with 158 additions and 10 deletions
+2 -1
View File
@@ -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},
+1
View File
@@ -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)
+68 -8
View File
@@ -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-"))
+6 -1
View File
@@ -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>
+4
View File
@@ -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,
+59
View File
@@ -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"
+13
View File
@@ -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
+1
View File
@@ -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",