Skip to content

常见问题

zfl9 edited this page Apr 28, 2024 · 22 revisions

请使用 root 权限执行 ss-tproxy

因为脚本需要修改 iptables 规则、内核参数;所以,若无特殊说明,请用 root 权限执行脚本。

非标准的内网地址段

标准内网地址段如:10.0.0.0/8172.16.0.0/12192.168.0.0/16,如果你将其它 IP 段作为内网使用,强烈建议你纠正这个错误,这不仅会导致透明代理出问题,也会引发其它 bug,因为很多软件设计者并没有考虑到你使用的是一个非标准内网地址段。如果因为各种原因无法更改,比如公司内部,请将相关网段加入 ignlist.ext

内网主机无法访问白名单

ss-tproxy 主机上都正常,但其他主机上,黑名单正常,白名单不正常(如百度无法访问)。请将 ipts_set_snat(IPv4)、ipts_set_snat6(IPv6)设为 true。并检查 ss-tproxy 主机的 iptables 规则,有些系统会将 FORWARD 链的默认策略设为 DROP,如果有这种情况,请进行合理的调整,如果不知道怎么设置,可以参照下面的步骤,配置 pre_start 钩子函数。

代理异常时,应留意系统是否预设了某些 iptables 规则、是否与 ss-tproxy 冲突

比如,你可以通过 pre_start 钩子函数,在 start 之前,将已有的 iptables 规则清空,并将默认策略设为 ACCEPT。编辑 ss-tproxy.conf,添加如下内容(这些命令会在 ss-tproxy start 之前执行):

reset_ipt() {
    local table_chains=(
        raw 'PREROUTING OUTPUT'
        mangle 'PREROUTING INPUT FORWARD OUTPUT POSTROUTING'
        nat 'PREROUTING INPUT OUTPUT POSTROUTING'
        filter 'INPUT FORWARD OUTPUT'
    )
    for ((i = 0; i < ${#table_chains[@]}; i += 2)); do
        local table="${table_chains[i]}"
        local chains="${table_chains[i + 1]}"
        $1 -t $table -F
        $1 -t $table -X
        for chain in $chains; do
            $1 -t $table -P $chain ACCEPT
        done
    done
}

pre_start() {
    is_true "$ipv4" && reset_ipt iptables
    is_true "$ipv6" && reset_ipt ip6tables
}

ss-tproxy status 显示不准确(已运行却显示 stopped)

如果 start/restart 时某些服务显示 stopped,稍后执行 status 时又变为 running;可以不用管,这只是因为进程 从启动到监听端口 需要一个时间过程;比如,某些代理进程在启动时可能需要解析节点域名,这个过程可能比较慢。

另外,ss-tproxy 需要使用 root 权限执行,如果使用非 root 权限执行 ss-tproxy status,会因为权限不足,导致检测失败,显示 stopped 状态。

如果不是上面这些情况,请检查 ss-tproxy.conf 配置,看看是不是端口冲突了,另外就是检查代理进程、dns进程的日志,检查是否有报错,具体请见:故障排查

如果 udp 代理不可用,请使用 tcponly 模式

对于 ss/ssr,ss-redir/ssr-redir 需要开启 udp relay 功能(-u 参数);ss-server/ssr-server 也需要开启 udp relay 功能(有些机场没开启 udp);如果服务器有防火墙规则,也要放行对应的 udp 端口;另外,某些 ISP 会恶意丢弃 udp 包。

对于 v2ray/trojan,因为它的 udp 是通过 tcp 转发出去的(即 udp over tcp),所以不需要放行服务器的防火墙 udp 端口,也不需要担心 ISP 对 udp 流量的恶意干扰,因为走的是 tcp 而不是 udp。

如何只代理指定的 host/addr,其它都走直连

使用 gfwlist 模式即可;gfwlist 模式会读取 gfwlist.txt、gfwlist.ext 两个黑名单文件,你可以将 gfwlist.txt 文件清空,然后编辑 gfwlist.ext 文件,填写要代理的域名、IP、网段即可(文件中有格式说明)。注意,这种模式下不要执行 update-gfwlist 命令,因为它们会操作 gfwlist.txt 文件。

iptables: No chain/target/match by that name

出现错误时,请带上 -x 选项重新执行一次,查看是哪条命令报的,比如 ss-tproxy start -x,如果是 iptables -j TPROXY 报的错,那就是没有 TPROXY 模块。如果是其他命令报的错,请通过 issue 报告问题,报告前先搜下,看是否有人已经发过。

ss-tproxy.conf 中的函数不可重复定义

ss-tproxy 和 ss-tproxy.conf 都是 bash 脚本,这两文件的内容必须符合 bash 的语法规则,比如你不能重复定义一个函数,虽然不会报错,但只有最后一个函数生效,如果你定义了多个同名的 bash 函数,请将它们合并为一个。

另外,ss-tproxy 会使用 source 加载 ss-tproxy.conf 配置文件;因此在给变量、函数起名时,要注意下,避免覆盖已有的变量和函数。如果函数同名,由于加载顺序问题,ss-tproxy.conf 中的有效(也就是会覆盖原有函数),可以利用此特性,来重写一些方法,比如 update-chnroute。

切换分流模式后,某些网站没有走代理

从其它模式切换到 gfwlist 模式时,可能会出现此问题;这是因为内网主机有 DNS 缓存。在访问被墙网站时,客户机首先会进行 DNS 解析,因为之前解析过这个域名,因此客户机实际上并没有发送 DNS 解析请求,而是直接取出缓存中的 IP 地址,然后访问它;因为解析请求没有经过 ss-tproxy 主机处理,所以对应 IP 也没有添加到 ipset 集合,发往该 IP 的数据也就不会走代理。

解决方法也很简单,清理 DNS 缓存即可。对于 Windows,请先关闭浏览器,打开 cmd,执行 ipconfig /flushdns,重新打开浏览器,应该正常了;对于手机,切换一下飞行模式,或者切换一下 WiFi。

有时会无法访问代理服务器

如果你在 ss-tproxy 中使用自己的 VPS 代理服务,且出现这么一个现象:访问其它地址都没问题,唯独不能访问自己的 VPS,比如 ssh 连不上;请检查你是否修改了这两个内核参数:net.ipv4.tcp_tw_reusenet.ipv4.tcp_tw_recycle,确保它们的值都为 0(也就是禁用),VPS 端和本地主机都检查一下。

与 UFW 一起使用的问题

如非必要,请不要将 iptables(ss-tproxy) 与 UFW、FirewallD 等同时使用,因为很容易发生冲突。

本人对 UFW 不熟,以下方法来自 issue,请自行验证是否有效。

简单来说,就是将 INPUT 和 FORWARD 链的默认策略改为 ACCEPT,允许 ss-tproxy 主机作为“路由器/网关”:

vim /etc/default/ufw

#找到以下两行,将策略改为ACCEPT
DEFAULT_INPUT_POLICY="ACCEPT"
DEFAULT_FORWARD_POLICY="ACCEPT"

#重启UFW,使规则生效
systemctl restart ufw

ChatGPT 被识别为国内 IP,无法使用

应该是 ChatGPT 使用了 QUIC/HTTP3 协议,QUIC 底层使用 UDP,如果你恰好没有代理 UDP,比如 tcponly='true',就会出现此问题。解决方法就是将发往“黑名单”的 QUIC 流量给丢弃,配置 ipts_drop_quic='tcponly' 就可以了。

YouTube 测速比较低,可能是因为 QUIC

QUIC/HTTP3 使用 UDP 传输,如果你的 代理/机场/VPS 对 UDP 支持度不太好,或者 UDP 体验比较差(如 QoS);强烈建议在 ss-tproxy.conf 中配置 ipts_drop_quic='always',丢弃发往“黑名单”的 QUIC 流量,让这些网站始终使用 TCP。

代理不支持 IPv6,如何使“黑名单”只走 IPv4

从 DNS 解析入手,过滤 AAAA 查询。对于内置 DNS 方案,请在 chinadns_extra_options 中添加以下参数:

  • global 模式:-N tag:gfw
  • gfwlist 模式:-N tag:gfw
  • chnroute 模式:-N tag:gfw,ip:non_china

某些应用自带 DNS 解析组件,比如浏览器,请记得关闭

比如 Chrome,有一个叫做 安全 DNS 的功能,如果要使用 ss-tproxy 的代理,请保持默认值:使用您当前的服务提供商;否则分流将不会生效。其他浏览器也有类似功能,请注意检查。手机也有类似的功能(私人 DNS),请保持默认值。

Docker(桥接网络) 与 TPROXY 的兼容性问题

Docker 默认会加载 br_netfilter 内核模块,用于桥接网络,也就是 docker0。

br_netfilter 引入了几个内核参数,位于 /proc/sys/net/bridge/ 目录,主要关心这两个:

  • bridge-nf-call-iptables:在桥接层(2层)调用 iptables(3层)规则,默认为 1(启用)
  • bridge-nf-call-ip6tables:在桥接层(2层)调用 ip6tables(3层)规则,默认为 1(启用)

bridge(网桥)是 Linux 内核在软件层面实现的一个虚拟“交换机”。

在 网桥/数据链路层/L2 上下文 调用 网络层/L3 是有问题的,违反了网络分层模型;因为 iptables 相关代码都是假设它们在 网络层/L3 上下文被调用,这是完全合理的假设;在网桥中启用 call-iptables 功能后,这些 iptables 代码可能就会出现问题,因为调用这些函数时,所处的上下文是 L2 层。

相关参考:https://legacy.netdevconf.info/1.1/proceedings/papers/Bridge-filter-with-nftables.pdf

回到 TPROXY,按照正常的顺序,会先调用 IP 层的 ip_rcv_core 函数,然后再调用 netfilter 的 PREROUTING 钩子,调用 xt_TPROXY 的代码,执行 TPROXY 操作。

如果在 bridge 层调用 iptables(TPROXY),就会出现顺序问题:先在桥接层调用 tproxy_tg4,设置 skb->sk 字段,然后数据包才进入网络层,调用 ip_rcv_core,然后 skb->sk 字段就被置空了,导致 TPROXY 流程走不通。

call-iptables 的情况下,br_netfilter 做了很多特殊处理,保证一个数据包不会多次进入同一个 netfilter 钩子;因此,调用 ip_rcv_core 后,数据包不会进入 IP 层的 PREROUTING 钩子,也就是说 tproxy_tg4 不会调用第二次。

总结:要么使用 tcponly 模式 + REDIRECT 传入;要么将 call-iptables 改为 0,禁止在 L2 调用 L3 代码,如下:

# 在 ss-tproxy.conf 中设置 pre_start 钩子函数
pre_start() {
    if is_tcp_tproxy || is_enabled_udp; then
        modprobe br_netfilter # 确保模块已加载,否则 sysctl 会提示找不到条目
        sysctl -wq net.bridge.bridge-nf-call-iptables=0
        sysctl -wq net.bridge.bridge-nf-call-ip6tables=0
        sysctl -wq net.bridge.bridge-nf-call-arptables=0 # 与透明代理无关,不关也可以
    fi
}