menu

Passwall

一个 tcp 请求的生命周期:

Domain -> dnsmasq(route#53)-不匹配任何名单-> 本地 dns -> ip <
               |_> 匹配代理名单 -> tproxy -> 远程 dns -> ip -> 写入相应 ipset nameset <
               |_> 匹配直连名单 -> 本地 dns -> ip -> 写入相应 ipset nameset <

ip -> iptables
        |_> 匹配代理 nameset -> rediect -> tproxy
        |_> 直接转发

名单

passwall 预配置有多个名单和支持自定义名单,也包括未匹配名单的数据。当中总体上可分为两种:

  1. 代理名单
  2. 直连名单

按名单项的类型又可分为

  1. ip
  2. 域名

ip 直接导入 ipset 对应的集合中,配合 iptables 实现各个代理策略。

域名,需要通过 dnsmasq 查询后,由 dnsmasq 添加到对应的 ipset 集合中。

dnsmasq 又通过域名所在名单的不同,选择不同的上游 ns 服务器。

ns 服务器也分为两种:

  1. 本地 ns
  2. 远程 ns

名单

DNS

对域名进行分流解析,也就是白名单用一个 dns,黑名单用一个 dns。

DNS 端口是写死的。DOH 端口是 7912,本地 DNS 是 7913, other(china_ng_gfw) 是 7914

dnsmasq

https://stackoverflow.com/a/37449551/851344

dnsmasq 是路由默认的 dns 服务。也是 dhcp 下发的默认 dns 地址。

dnsmasq 的上游有两个 dns,一个是本地 dns,一个是远程 dns。

配合黑白名单, dnsmasq 将黑名单里的域名通过远程 dns 查询。白名单的域名通过本地 dns 查询。

当然实际 passwall 的名单不只两个。但总的来说就是黑白名单两类。

最后 dnsmasq 再将查询到的地址,根据对应名单的性质添加进相应的 ipset 中。

Local Dns

用来解析白名单的 dns,默认是 DEFAULT_DNS,是指在 /tmp/resolv.conf.d/resolv.conf.auto/tmp/resolv.conf.auto 配置的 dns,

若未配置则取 119.29.29.29

openwrt-passwall/app.sh at main · xiaorouji/openwrt-passwall

DOH

用了 aarond10/https_dns_proxy: A lightweight DNS-over-HTTPS proxy. ,将 udp 的请求转发到 doh 服务器。

但是 https_dns_proxy 配置成只接受本地链接(127.0.0.1),其他主机的 dns 请求是如何转发的?

新版用 [xray-doh],来实现转发 doh,还支持通过 socks 代理。直接 tcp 链接是通过:

"streamSettings": {
         "sockopt": {
           "mark": 255
         }

然后在 iptables 控制 mark 0xff 不走代理。

Socks5

https://tools.ietf.org/html/rfc1928

iptables

Nat 表 , Prerouting 链创建一条子链 PSW:

Pkts.	Traffic	Target	Prot.	In	Out	Source	Destination	Options	Comment
16.95 K	1.08 MB	RETURN	all	*	*	0.0.0.0/0	0.0.0.0/0	match-set laniplist dst	-
0 	0 B	RETURN	all	*	*	0.0.0.0/0	0.0.0.0/0	match-set vpsiplist dst	-
474 	33.11 KB	RETURN	all	*	*	0.0.0.0/0	0.0.0.0/0	match-set whitelist dst	-
0 	0 B	REDIRECT	tcp	*	*	0.0.0.0/0	0.0.0.0/0	multiport dports 22,25,53,143,465,587,993,995,80,443 match-set shuntlist dst redir ports 1041	'默认'
427 	26.02 KB	REDIRECT	tcp	*	*	0.0.0.0/0	0.0.0.0/0	multiport dports 22,25,53,143,465,587,993,995,80,443 match-set blacklist dst redir ports 1041	'默认'
18.34 K	1.11 MB	REDIRECT	tcp	*	*	0.0.0.0/0	0.0.0.0/0	multiport dports 22,25,53,143,465,587,993,995,80,443 ! match-set chnroute dst redir ports 1041	'默认'
4.93 K	344.18 KB	RETURN	tcp	*	*	0.0.0.0/0	0.0.0.0/0	-	'默认'

haproxy 负载均衡

passwall 生成的负载均衡配置文件:

global
    log         127.0.0.1 local2
    chroot      /usr/bin
    maxconn     60000
    stats socket  /var/etc/passwall/haproxy/haproxy.sock
    user        root
    daemon

defaults
    mode                    tcp
    log                     global
    option                  tcplog
    option                  dontlognull
    option http-server-close
    #option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 2
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 3000

listen 1181
    mode tcp
    bind 0.0.0.0:1181
    server g1.dourok.info:443 g1.dourok.info:443 weight 5 check inter 1500 rise 1 fall 3
    server g2.dourok.info:443 g2.dourok.info:443 weight 5 check inter 1500 rise 1 fall 3
listen console
    bind 0.0.0.0:1188
    mode http
    stats refresh 30s
    stats uri /
    stats admin if TRUE
    stats auth

我以为 backend 是本地的 tcp 端口,比如多个本地 xray 服务监听不同短空,一个 haproxy frontend。 结果是 backend 是 xray 服务端的地址,与域名无关的可以用这种方式(tcp mode),与域名有关的,比如 xless 估计用不了,另外 xray 配置文件也没有涉及 haproxy 对应的端口,不清楚这样做的目的是什么。

Rule list

/usr/bin/lua/luci/model/cbi/passwall/client/rule_list.lua

重点在这个函数

function m.on_apply(self)
luci.sys.call("/etc/init.d/passwall reload > /dev/null 2>&1 &")
end

reload 未实现,所以每次保存列表都会重启服务

Fake Ip

Socks5 是支持将整个请求打包给代理服务器。

TUN/TAP

Redirect 与 TProxy 的区别

  • REDIRECT 只在 nat PREROUTING、OUTPUT 有效。
  • TPROXY 自在 mangle PREROUTING 有效

REDIRECT 相当于目标地址固定为 localhost 的 DNAT。REDIRECT 不能通过 getsockname 获取原始链接的目标地址。不过 TCP 链接仍然能通过 SO_ORIGINAL_DST 获取到原始目标地址。这也就是 REDIRECT 只支持 TCP 透明代理的原因。

DNAT 与 REDIRECT 是可以获取到真实的源地址(实际发起请求的客户端的地址)。

REDIRECT 的目标地址已经被改为 local,所有是不需要重新路由的,也没必要再打标签。

ip rule add fwmark 1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100

TPROXY 不会修改传输层 Header。

Original destination was: 192.168.5.32:5301                                                       Connected by <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0,laddr=('192.168.50.32', 10025), raddr=('192.168.50.211', 50312)>

何谓透明

透明意味着不可见,透明代理中这里的主语指的是链接的发起端或接收端。客户端向真实的服务端ip发起请求,服务端看到的也是真实的ip。中间链路的作妖不可见。FIXME

operating system functions as a router, but some (or all) traffic gets redirected for userspace processing.

It is entirely possible to tell Linux 0.0.0.0/0 (‘everything’) is local, but this would leave it unable to connect to any remote address.

ip rule add fwmark 1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100

让所有 ip 都经过本地回环,这样才能被本地监听 0.0.0.0:PORT 的应用程序收到包。

tcp        0      0 :::1041                 :::*                    LISTEN      2654/xray

IP_TRANSPARENT

https://man7.org/linux/man-pages/man7/ip.7.html

  1. Binding to addresses that are not (usually) considered local
  2. Receiving connections and packets from iptables TPROXY redirected sessions

因为不论是 TCP 还是 UDP,编程模型都需要 bind 本机地址(或者 0.0.0.0),不是给本机的包,进程不收。但 2.6.24 的内核出了个 IP_TRANSPARENT 的 socket 选项,打开这个选项,就可以接收任意目的地址的包了。

-m socket

文档所说的:

It matches if there is an established or non-zero bound listening socket (possibly with a non-local address).

经验证 tcp 第一个包 syn 是不会触发 -m socket 后续的包就会触发。也就是说后续的包直接走 DIVERT 不会触发 TPROXY 规则了,不过仍然能被正确转发到目标透明代理的进程,因为“链接”已经被建立了?反正绕过了后续的规则最终起到提高效率的作用。面对无链接的 udp 貌似就不起作用了。

本地流量如何走透明代理

TPROXY 不能用于 OUTPUT 链,不可以通过给 OUTPUT 链给流量加标识。

iptables -t mangle -A OUTPUT -p udp -j MARK --set-mark 1
iptables -t mangle -A OUTPUT -p tcp -j MARK --set-mark 1

猜测是根据之前配置的路由规则,流量会被路由至 lo,这时就会走 PREROUTING 链。所以也能应用 TPROXY,经常测试应用 TPROXY 后就不会经过 OUTPUT 链了,所以也就不会有死循环的问题。

测试

keyboard_arrow_up