软路由折腾记录二

前言

我从大二开始接触linux, 最开始安装的就是基于debian的kali, 之后直接将debian作为主系统日常使用. 包括现在购买的VPS, 又或是wsl都安装的debian, 就如同我对firefox一样, 产生了深厚的感情.

改造

接上篇文章, 历经千幸万苦终于可以在OpenWrt上运行Docker后, tproxy透明代理失效了. 因为OpenWrt系统十分精简, 再加上是我自己编译的版本, 缺少官方的软件源, 所以调试变得异常困难. 最终我不得不放弃, 转向debian. 我熟练地下载官方镜像, 使用rufus写入U盘, 按部就班的完成了系统安装.
那么接下来就需要将它改造成路由器.

网桥

我的主机有4个网口, 我将第一个网口当做WAN, 连接着联通光猫. 那么我需要将剩下三个网口进行桥接, 使它们处于一个网段, 当为LAN口使用.

1
2
3
4
5
6
# 安装
apt install bridge-utils

# 添加桥接网卡
brctl addbr br0
brctl addif br0 enp0s8 enp0s9

你可以选择使用brctl手动添加桥接网卡, 但是重启之后会消失, 所以建议写入网络配置:

1
2
3
4
5
6
7
# /etc/network/interfaces
auto br0
iface br0 inet static
address 192.168.12.1
broadcast 192.168.12.255
netmask 255.255.255.0
bridge-ports enp0s8 enp0s9

bridge-ports参数替换成具体的网卡, 上面的例子只桥接了两个网卡, 这并不是我实际使用的配置, 仅供参考.

DNS/DHCP

路由器最重要的自然是DHCP/DNS, 而dnsmasq帮我们轻松搞定:

1
apt install dnsmasq

dnsmasq会提供DHCP/DNS服务, DNS默认转发到上游DNS, 暂时不需要修改, 但是DHCP需要修改配置文件指定网段等信息:

1
2
3
# /etc/dnsmasq.conf
interface=br0
dhcp-range=192.168.12.100,192.168.12.250,72h

转发

现在将电脑网线插入J1900主机网口, 电脑可以使用DHCP获取到IP, 可惜现在还上不了网. 因为linux默认将目标IP不是本机的流量全部DROP, 首先打开IP转发:

1
2
# 重启后失效
echo 1 > /proc/sys/net/ipv4/ip_forward

光开启转发还是无法上网, 因为内网网段访问外网需要NAT, 所以需要配置iptables:

1
iptables -t nat -A POSTROUTING -s 192.168.12.0/24 -o enp0s3 -j MASQUERADE

enp0s3是WAN口网卡, MASQUERADE将把子网流量自动NAT转换, 现在你可以自由的进行网上冲浪了.

复现

完成上面一切, debian现在与OpenWrt有着同样的功能, 而且还废弃了那些累赘的组件. 我现在准备安装docker-ce, 过程很迅速, 安装完后我开始基于docker-ce搭建自己的SMB服务.
一切都完成了, 我长舒一口气, 我在终端执行

1
dig google.com @8.8.8.8

命令卡住了!!! tproxy又罢工了!!! 我突然意识到我错怪了OpenWrt, 这似乎是Docker惹的祸, 于是我又开始了漫长的debug.

调试

网络架构


这张图显示的是linux的网络架构, 包括iptables的链条顺序、路由选择, 下面所有的调试都是基于这张图.

Docker

我知道Docker的网络建立在iptables上, 它一样使用虚拟桥接网卡连接各个容器, 然后在iptables添加规则进行NAT. 可是没理由它会影响到主机的网络, 我一条一条地查看Docker添加的防火墙规则, 唯一异常的点是它将filter表的FORWARD默认策略改成了DROP. 但tproxy压根不会经过FORWARD, 路由规则会让流量进入本地协议栈.
根据架构图可以知道, 如果包进入了filter表的INPUT链, 那么说明流量已经过了路由选择, 即将进入本地协议栈. 如果tproxy正常工作, 修改了包的结构体, 那么此时代理端口与包是相关联的, 所以包会被正常接收.
我开始猜测tproxy没有正常运作, 导致路由失败, 包被FORWARD出去了:

1
iptables -t filter -I INPUT 1 -p udp -j LOG --log-prefix '** SUSPECT filter INPUT**'

我添加了一条iptables规则, 当匹配到包时会打印出日志, 于是我重复了一次DNS查询操作. 日志出现了, 所以我断定它已经走过了路由选择. 我又接着猜测了几种可能:

  • tproxy关联包信息失败
  • IP_TRANSPARENT属性失效

这两种情况我都没有有效的测试手段, 所以我陷入了困境, 不过我觉得原因大概率是docker-ce加载了某个内核模块. 于是我使用dpkg查看所有安装的包, 只发现了一个aufs.ko, 貌似不是很符合.
当我束手无策时, 我发现了这篇帖子TPROXY compatibility with Docker.
作者遇到了和我一样的问题, 于是它使用strace跟踪dockerd的系统调用, 发现它加载了一个内核模块”br_netfilter”. 我不抱任何希望的执行了”rmmod br_netfilter”, 它恢复了!!! 原来是这个br_netfilter的问题!!!

ebtables

这时我才仔细去观察上面的架构图, 我发现我遗漏了一个问题, 就是网桥的流量会走Link Layer, 所以可能是这部分出了问题. 图中的蓝色表代表的是ebtables, 绿色表代表iptables, ebtables可以理解成链路的iptables.
我想弄清楚流量的具体走向, 所以我在每个点都加上log规则, 数据包的流向将一览无遗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# raw
iptables -t raw -I PREROUTING 1 -p udp -j LOG --log-prefix '** SUSPECT raw PREROUTING**'
iptables -t raw -I OUTPUT 1 -p udp -j LOG --log-prefix '** SUSPECT raw OUTPUT**'

# mangle
iptables -t mangle -I PREROUTING 1 -p udp -j LOG --log-prefix '** SUSPECT mangle PREROUTING**'
iptables -t mangle -I INPUT 1 -p udp -j LOG --log-prefix '** SUSPECT mangle INPUT**'
iptables -t mangle -I FORWARD 1 -p udp -j LOG --log-prefix '** SUSPECT mangle FORWARD**'

# nat
iptables -t nat -I PREROUTING 1 -p udp -j LOG --log-prefix '** SUSPECT nat PREROUTING**'
iptables -t nat -I INPUT 1 -p udp -j LOG --log-prefix '** SUSPECT nat INPUT**'

# filter
iptables -t filter -I INPUT 1 -p udp -j LOG --log-prefix '** SUSPECT filter INPUT**'
iptables -t filter -I FORWARD 1 -p udp -j LOG --log-prefix '** SUSPECT filter FORWARD**'

# BROUTING
ebtables-legacy -t broute -A BROUTING -p ipv4 --ip-proto udp --log-level 6 --log-ip --log-prefix "TRACE: eb:broute:BROUTING" -j CONTINUE

# nat
ebtables-legacy -t nat -A OUTPUT -p ipv4 --ip-proto udp --log-level 6 --log-ip --log-prefix "TRACE: eb:nat:OUTPUT" -j CONTINUE
ebtables-legacy -t nat -A PREROUTING -p ipv4 --ip-proto udp --log-level 6 --log-ip --log-prefix "TRACE: eb:nat:PREROUTING" -j CONTINUE
ebtables-legacy -t nat -A POSTROUTING -p ipv4 --ip-proto udp --log-level 6 --log-ip --log-prefix "TRACE: eb:nat:POSTROUTING" -j CONTINUE

# filter
ebtables-legacy -t filter -A INPUT -p ipv4 --ip-proto udp --log-level 6 --log-ip --log-prefix "TRACE: eb:filter:INPUT" -j CONTINUE
ebtables-legacy -t filter -A FORWARD -p ipv4 --ip-proto udp --log-level 6 --log-ip --log-prefix "TRACE: eb:filter:FORWARD" -j CONTINUE
ebtables-legacy -t filter -A OUTPUT -p ipv4 --ip-proto udp --log-level 6 --log-ip --log-prefix "TRACE: eb:filter:OUTPUT" -j CONTINUE

在未加载br_netfilter的情况下, 我向8.8.8.8查询域名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
TRACE: eb:broute:BROUTING IN=enp0s8 OUT= MAC source = 08:00:27:6d:c8:0b MAC dest = 08:00:27:9b:fa:c9 proto = 0x0800 IP SRC=192.168.12.192 IP DST=8.8.8.8, IP tos=0x00, IP proto=17 SPT=36289 DPT=53
TRACE: eb:nat:PREROUTING IN=enp0s8 OUT= MAC source = 08:00:27:6d:c8:0b MAC dest = 08:00:27:9b:fa:c9 proto = 0x0800 IP SRC=192.168.12.192 IP DST=8.8.8.8, IP tos=0x00, IP proto=17 SPT=36289 DPT=53
TRACE: eb:filter:INPUT IN=enp0s8 OUT= MAC source = 08:00:27:6d:c8:0b MAC dest = 08:00:27:9b:fa:c9 proto = 0x0800 IP SRC=192.168.12.192 IP DST=8.8.8.8, IP tos=0x00, IP proto=17 SPT=36289 DPT=53
** SUSPECT raw PREROUTING**IN=br0 OUT= MAC=08:00:27:9b:fa:c9:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=24626 PROTO=UDP SPT=36289 DPT=53 LEN=46
** SUSPECT mangle PREROUTING*IN=br0 OUT= MAC=08:00:27:9b:fa:c9:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=24626 PROTO=UDP SPT=36289 DPT=53 LEN=46
** SUSPECT nat PREROUTING**IN=br0 OUT= MAC=08:00:27:9b:fa:c9:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=24626 PROTO=UDP SPT=36289 DPT=53 LEN=46 MARK=0x162
** SUSPECT mangle INPUT**IN=br0 OUT= MAC=08:00:27:9b:fa:c9:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=24626 PROTO=UDP SPT=36289 DPT=53 LEN=46 MARK=0x162
** SUSPECT filter INPUT**IN=br0 OUT= MAC=08:00:27:9b:fa:c9:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=24626 PROTO=UDP SPT=36289 DPT=53 LEN=46 MARK=0x162
** SUSPECT nat INPUT**IN=br0 OUT= MAC=08:00:27:9b:fa:c9:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=24626 PROTO=UDP SPT=36289 DPT=53 LEN=46 MARK=0x162
** SUSPECT raw OUTPUT**IN= OUT=enp0s3 SRC=10.0.2.15 DST=10.91.0.1 LEN=57 TOS=0x00 PREC=0x00 TTL=64 ID=56764 DF PROTO=UDP SPT=45711 DPT=53 LEN=37
** SUSPECT raw OUTPUT**IN= OUT=enp0s3 SRC=10.0.2.15 DST=10.91.0.1 LEN=57 TOS=0x00 PREC=0x00 TTL=64 ID=56765 DF PROTO=UDP SPT=59060 DPT=53 LEN=37
** SUSPECT raw PREROUTING**IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=114 TOS=0x00 PREC=0x00 TTL=64 ID=1722 PROTO=UDP SPT=53 DPT=45711 LEN=94
** SUSPECT mangle PREROUTING*IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=114 TOS=0x00 PREC=0x00 TTL=64 ID=1722 PROTO=UDP SPT=53 DPT=45711 LEN=94
** SUSPECT mangle INPUT**IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=114 TOS=0x00 PREC=0x00 TTL=64 ID=1722 PROTO=UDP SPT=53 DPT=45711 LEN=94
** SUSPECT filter INPUT**IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=114 TOS=0x00 PREC=0x00 TTL=64 ID=1722 PROTO=UDP SPT=53 DPT=45711 LEN=94
** SUSPECT raw PREROUTING**IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=73 TOS=0x00 PREC=0x00 TTL=64 ID=1723 PROTO=UDP SPT=53 DPT=59060 LEN=53
** SUSPECT mangle PREROUTING*IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=73 TOS=0x00 PREC=0x00 TTL=64 ID=1723 PROTO=UDP SPT=53 DPT=59060 LEN=53
** SUSPECT mangle INPUT**IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=73 TOS=0x00 PREC=0x00 TTL=64 ID=1723 PROTO=UDP SPT=53 DPT=59060 LEN=53
** SUSPECT filter INPUT**IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=73 TOS=0x00 PREC=0x00 TTL=64 ID=1723 PROTO=UDP SPT=53 DPT=59060 LEN=53
** SUSPECT raw OUTPUT**IN= OUT=br0 SRC=8.8.8.8 DST=192.168.12.192 LEN=98 TOS=0x00 PREC=0x00 TTL=64 ID=40027 DF PROTO=UDP SPT=53 DPT=36289 LEN=78
TRACE: eb:nat:OUTPUT IN= OUT=enp0s8 MAC source = 08:00:27:9b:fa:c9 MAC dest = 08:00:27:6d:c8:0b proto = 0x0800 IP SRC=8.8.8.8 IP DST=192.168.12.192, IP tos=0x00, IP proto=17 SPT=53 DPT=36289
TRACE: eb:filter:OUTPUT IN= OUT=enp0s8 MAC source = 08:00:27:9b:fa:c9 MAC dest = 08:00:27:6d:c8:0b proto = 0x0800 IP SRC=8.8.8.8 IP DST=192.168.12.192, IP tos=0x00, IP proto=17 SPT=53 DPT=36289
TRACE: eb:nat:POSTROUTING IN= OUT=enp0s8 MAC source = 08:00:27:9b:fa:c9 MAC dest = 08:00:27:6d:c8:0b proto = 0x0800 IP SRC=8.8.8.8 IP DST=192.168.12.192, IP tos=0x00, IP proto=17 SPT=53 DPT=36289

可以看到包先过了ebtables规则, 然后转入了iptables规则, 也就是包按层级从下到上. 此时的tproxy是正常工作的, 可能你会感到疑惑, 为什么本机向10.91.0.1发送了UDP. 其实是因为我使用了VPN, 所以这是在解析VPN服务器的域名, UDP的转发走的是VPN的TCP通道.
接着我加载了br_netfilter, 观察日志:

1
2
3
4
5
6
7
8
9
TRACE: eb:broute:BROUTING IN=enp0s8 OUT= MAC source = 08:00:27:6d:c8:0b MAC dest = 08:00:27:9b:fa:c9 proto = 0x0800 IP SRC=192.168.12.192 IP DST=8.8.8.8, IP tos=0x00, IP proto=17 SPT=36003 DPT=53
TRACE: eb:nat:PREROUTING IN=enp0s8 OUT= MAC source = 08:00:27:6d:c8:0b MAC dest = 08:00:27:9b:fa:c9 proto = 0x0800 IP SRC=192.168.12.192 IP DST=8.8.8.8, IP tos=0x00, IP proto=17 SPT=36003 DPT=53
** SUSPECT raw PREROUTING**IN=br0 OUT= PHYSIN=enp0s8 MAC=08:00:27:9b:fa:c9:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=35740 PROTO=UDP SPT=36003 DPT=53 LEN=46
** SUSPECT mangle PREROUTING*IN=br0 OUT= PHYSIN=enp0s8 MAC=08:00:27:9b:fa:c9:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=35740 PROTO=UDP SPT=36003 DPT=53 LEN=46
** SUSPECT nat PREROUTING**IN=br0 OUT= PHYSIN=enp0s8 MAC=08:00:27:9b:fa:c9:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=35740 PROTO=UDP SPT=36003 DPT=53 LEN=46 MARK=0x162
TRACE: eb:filter:INPUT IN=enp0s8 OUT= MAC source = 08:00:27:6d:c8:0b MAC dest = 08:00:27:9b:fa:c9 proto = 0x0800 IP SRC=192.168.12.192 IP DST=8.8.8.8, IP tos=0x00, IP proto=17 SPT=36003 DPT=53
** SUSPECT mangle INPUT**IN=br0 OUT= PHYSIN=enp0s8 MAC=08:00:27:9b:fa:c9:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=35740 PROTO=UDP SPT=36003 DPT=53 LEN=46 MARK=0x162
** SUSPECT filter INPUT**IN=br0 OUT= PHYSIN=enp0s8 MAC=08:00:27:9b:fa:c9:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=35740 PROTO=UDP SPT=36003 DPT=53 LEN=46 MARK=0x162
** SUSPECT nat INPUT**IN=br0 OUT= PHYSIN=enp0s8 MAC=08:00:27:9b:fa:c9:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=35740 PROTO=UDP SPT=36003 DPT=53 LEN=46 MARK=0x162

这是什么情况??? 数据包在两个层级之间跳来跳去??? 我开始搜索相关资料, 原来这是br_netfilter提供的透明防火墙功能, 使得三层规则可以作用在二层上. 因为桥接的端口之间发送包是被网桥直接转走的, 不会走到本机的iptables中, 而br_netfilter使得ebtables主动调用iptables规则, 这样所有的包都能接受过滤.
谜底揭开了, 我看到的日志显示包已经到了filter表的INPUT, 可实际上它还没有过IP层路由选择, 它此时在二层ebtables的filter表中, 只不过它主动的调用了iptables. 也就是说这个包没有通过路由进入本地协议栈, 它被丢弃或者转走了. 大概是因为iptables打上的fwmark, 在ebtables中丢失了, 导致进行路由选择时无法命中我们添加的路由规则.
于是我看阅读ebtables的文档, 文档中说道, 在broute表中将流量DROP, 会使得包进入Network Layer处理, 这样就是图中broute有个箭头指到上层的含义.

1
ebtables-legacy -t broute -A BROUTING -i enp0s8 -p ipv4 --ip-proto udp -j redirect --redirect-target DROP

我执行上面的命令后, 再执行查询操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
TRACE: eb:broute:BROUTING IN=enp0s8 OUT= MAC source = 08:00:27:6d:c8:0b MAC dest = 08:00:27:9b:fa:c9 proto = 0x0800 IP SRC=192.168.12.192 IP DST=8.8.8.8, IP tos=0x00, IP proto=17 SPT=47382 DPT=53
** SUSPECT raw PREROUTING**IN=enp0s8 OUT= MAC=08:00:27:ae:7d:59:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=25647 PROTO=UDP SPT=47382 DPT=53 LEN=46
** SUSPECT mangle PREROUTING*IN=enp0s8 OUT= MAC=08:00:27:ae:7d:59:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=25647 PROTO=UDP SPT=47382 DPT=53 LEN=46
** SUSPECT nat PREROUTING**IN=enp0s8 OUT= MAC=08:00:27:ae:7d:59:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=25647 PROTO=UDP SPT=47382 DPT=53 LEN=46 MARK=0x162
** SUSPECT mangle INPUT**IN=enp0s8 OUT= MAC=08:00:27:ae:7d:59:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=25647 PROTO=UDP SPT=47382 DPT=53 LEN=46 MARK=0x162
** SUSPECT filter INPUT**IN=enp0s8 OUT= MAC=08:00:27:ae:7d:59:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=25647 PROTO=UDP SPT=47382 DPT=53 LEN=46 MARK=0x162
** SUSPECT nat INPUT**IN=enp0s8 OUT= MAC=08:00:27:ae:7d:59:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=8.8.8.8 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=25647 PROTO=UDP SPT=47382 DPT=53 LEN=46 MARK=0x162
** SUSPECT raw OUTPUT**IN= OUT=enp0s3 SRC=10.0.2.15 DST=10.91.0.1 LEN=57 TOS=0x00 PREC=0x00 TTL=64 ID=40282 DF PROTO=UDP SPT=54985 DPT=53 LEN=37
** SUSPECT raw OUTPUT**IN= OUT=enp0s3 SRC=10.0.2.15 DST=10.91.0.1 LEN=57 TOS=0x00 PREC=0x00 TTL=64 ID=40283 DF PROTO=UDP SPT=37997 DPT=53 LEN=37
** SUSPECT raw PREROUTING**IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=73 TOS=0x00 PREC=0x00 TTL=64 ID=4012 PROTO=UDP SPT=53 DPT=37997 LEN=53
** SUSPECT mangle PREROUTING*IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=73 TOS=0x00 PREC=0x00 TTL=64 ID=4012 PROTO=UDP SPT=53 DPT=37997 LEN=53
** SUSPECT mangle INPUT**IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=73 TOS=0x00 PREC=0x00 TTL=64 ID=4012 PROTO=UDP SPT=53 DPT=37997 LEN=53
** SUSPECT filter INPUT**IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=73 TOS=0x00 PREC=0x00 TTL=64 ID=4012 PROTO=UDP SPT=53 DPT=37997 LEN=53
** SUSPECT raw PREROUTING**IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=114 TOS=0x00 PREC=0x00 TTL=64 ID=4018 PROTO=UDP SPT=53 DPT=54985 LEN=94
** SUSPECT mangle PREROUTING*IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=114 TOS=0x00 PREC=0x00 TTL=64 ID=4018 PROTO=UDP SPT=53 DPT=54985 LEN=94
** SUSPECT mangle INPUT**IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=114 TOS=0x00 PREC=0x00 TTL=64 ID=4018 PROTO=UDP SPT=53 DPT=54985 LEN=94
** SUSPECT filter INPUT**IN=enp0s3 OUT= MAC=08:00:27:4f:1e:2b:52:54:00:12:35:02:08:00 SRC=10.91.0.1 DST=10.0.2.15 LEN=114 TOS=0x00 PREC=0x00 TTL=64 ID=4018 PROTO=UDP SPT=53 DPT=54985 LEN=94
** SUSPECT raw OUTPUT**IN= OUT=br0 SRC=8.8.8.8 DST=192.168.12.192 LEN=98 TOS=0x00 PREC=0x00 TTL=64 ID=26403 DF PROTO=UDP SPT=53 DPT=47382 LEN=78
TRACE: eb:nat:OUTPUT IN= OUT=enp0s8 MAC source = 08:00:27:9b:fa:c9 MAC dest = 08:00:27:6d:c8:0b proto = 0x0800 IP SRC=8.8.8.8 IP DST=192.168.12.192, IP tos=0x00, IP proto=17 SPT=53 DPT=47382
TRACE: eb:filter:OUTPUT IN= OUT=enp0s8 MAC source = 08:00:27:9b:fa:c9 MAC dest = 08:00:27:6d:c8:0b proto = 0x0800 IP SRC=8.8.8.8 IP DST=192.168.12.192, IP tos=0x00, IP proto=17 SPT=53 DPT=47382
TRACE: eb:nat:POSTROUTING IN= OUT=enp0s8 MAC source = 08:00:27:9b:fa:c9 MAC dest = 08:00:27:6d:c8:0b proto = 0x0800 IP SRC=8.8.8.8 IP DST=192.168.12.192, IP tos=0x00, IP proto=17 SPT=53 DPT=47382

可以看到包的确被重定向到了Network Layer, tproxy也正常工作了.

尾声

正当我以为一切都结束了的时候, 我打开了斗鱼直播, 可浏览器却显示网络异常, 我又上不了网了. 我在终端进行ping测试, IP可以ping通而域名不行, 这样看来是DNS的问题.

1
dig baidu.com @192.168.12.1

命令卡住了, 难道发往本地的包重定向到Network Layer就被拒收了? 我又开始观察ebtables/iptables的日志输出:

1
2
3
4
5
6
7
TRACE: eb:broute:BROUTING IN=enp0s8 OUT= MAC source = 08:00:27:6d:c8:0b MAC dest = 08:00:27:9b:fa:c9 proto = 0x0800 IP SRC=192.168.12.192 IP DST=192.168.12.1, IP tos=0x00, IP proto=17 SPT=34741 DPT=53
** SUSPECT raw PREROUTING**IN=enp0s8 OUT= MAC=08:00:27:ae:7d:59:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=192.168.12.1 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=7347 PROTO=UDP SPT=34741 DPT=53 LEN=46
** SUSPECT mangle PREROUTING*IN=enp0s8 OUT= MAC=08:00:27:ae:7d:59:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=192.168.12.1 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=7347 PROTO=UDP SPT=34741 DPT=53 LEN=46
** SUSPECT nat PREROUTING**IN=enp0s8 OUT= MAC=08:00:27:ae:7d:59:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=192.168.12.1 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=7347 PROTO=UDP SPT=34741 DPT=53 LEN=46
** SUSPECT mangle INPUT**IN=enp0s8 OUT= MAC=08:00:27:ae:7d:59:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=192.168.12.1 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=7347 PROTO=UDP SPT=34741 DPT=53 LEN=46
** SUSPECT filter INPUT**IN=enp0s8 OUT= MAC=08:00:27:ae:7d:59:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=192.168.12.1 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=7347 PROTO=UDP SPT=34741 DPT=53 LEN=46
** SUSPECT nat INPUT**IN=enp0s8 OUT= MAC=08:00:27:ae:7d:59:08:00:27:6d:c8:0b:08:00 SRC=192.168.12.192 DST=192.168.12.1 LEN=66 TOS=0x00 PREC=0x00 TTL=64 ID=7347 PROTO=UDP SPT=34741 DPT=53 LEN=46

数据包已经进入了INPUT, 这次的确是进入了本地协议栈, 但是需要注意数据包的IN网卡已经从桥接网卡bro变成了真实网卡端口enp0s8. 难道是因为这个改变, 而被协议栈拒收? 还是因为dnsmasq没有bind该网卡?
我手动使用nc进行测试:

1
nc -u -lp 3000

使用nc监听软路由上3000端口, 然后在电脑上执行:

1
dig baidu.com @192.168.12.1 -p 3000

如果数据能被接收, nc会打印出DNS查询报文, 结果令我诧异, nc打印出了报文内容. 那么看来即使IN interface被修改, 数据包依旧能够被正确接收.
最后我使用strace追踪dnsmasq的系统调用:

1
strace /sbin/dnsmasq

执行查询后, 我发现dnsmasq接收到了报文, 只是它似乎获取了网卡名称bro与enp0s8, 应该是它进行了网卡校验, 认为请求不应该由enp0s8流入, 所以没有进行回应.

总结

为了不影响Docker的正常使用, 我没有将所有Link Layer流量进行重定向, 放行了一些常见内网IP网段.
所以我最后的ebtables/iptables配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/sh
# tcp
iptables -t nat -N CLASH
iptables -t nat -A CLASH -d 127.0.0.0/8 -j RETURN
iptables -t nat -A CLASH -d 10.0.0.0/8 -j RETURN
iptables -t nat -A CLASH -d 169.254.0.0/16 -j RETURN
iptables -t nat -A CLASH -d 192.168.0.0/16 -j RETURN
iptables -t nat -A CLASH -d 224.0.0.0/4 -j RETURN
iptables -t nat -A CLASH -d 240.0.0.0/4 -j RETURN
iptables -t nat -A CLASH -d 172.16.0.0/12 -j RETURN
iptables -t nat -A CLASH -d 255.255.255.255 -j RETURN
iptables -t nat -A CLASH -p tcp -j REDIRECT --to-ports 7892
iptables -t nat -A PREROUTING -p tcp -j CLASH

# udp
ip rule add fwmark 0x162 table 0x162
ip route add local 0.0.0.0/0 dev lo table 0x162
iptables -t mangle -N CLASH
iptables -t mangle -A CLASH -d 127.0.0.0/8 -j RETURN
iptables -t mangle -A CLASH -d 10.0.0.0/8 -j RETURN
iptables -t mangle -A CLASH -d 169.254.0.0/16 -j RETURN
iptables -t mangle -A CLASH -d 192.168.0.0/16 -j RETURN
iptables -t mangle -A CLASH -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A CLASH -d 240.0.0.0/4 -j RETURN
iptables -t mangle -A CLASH -d 172.16.0.0/12 -j RETURN
iptables -t mangle -A CLASH -d 255.255.255.255 -j RETURN
iptables -t mangle -A CLASH -p udp -j TPROXY --on-port 7892 --tproxy-mark 0x162
iptables -t mangle -A PREROUTING -p udp -j CLASH

ebtables-legacy -t broute -A BROUTING -p ipv4 --ip-proto udp --ip-destination 127.0.0.0/8 -j ACCEPT
ebtables-legacy -t broute -A BROUTING -p ipv4 --ip-proto udp --ip-destination 10.0.0.0/8 -j ACCEPT
ebtables-legacy -t broute -A BROUTING -p ipv4 --ip-proto udp --ip-destination 169.254.0.0/16 -j ACCEPT
ebtables-legacy -t broute -A BROUTING -p ipv4 --ip-proto udp --ip-destination 192.168.0.0/16 -j ACCEPT
ebtables-legacy -t broute -A BROUTING -p ipv4 --ip-proto udp --ip-destination 224.0.0.0/4 -j ACCEPT
ebtables-legacy -t broute -A BROUTING -p ipv4 --ip-proto udp --ip-destination 240.0.0.0/4 -j ACCEPT
ebtables-legacy -t broute -A BROUTING -p ipv4 --ip-proto udp --ip-destination 172.16.0.0/12 -j ACCEPT
ebtables-legacy -t broute -A BROUTING -p ipv4 --ip-proto udp --ip-destination 255.255.255.255 -j ACCEPT
ebtables-legacy -t broute -A BROUTING -p ipv4 --ip-proto udp -j redirect --redirect-target DROP

有一点需要注意, ebtables规则不需要”-i name”指定网卡名称, 因为理论上docker容器与个人电脑(连接软路由LAN口)处于同一网络层面. 不管是LAN口网桥还是docker网桥, 都需要适配该规则, 否则UDP透明代理将失败.