今天看到这个叫做socket的iptables 模块挺有意思的,但是没有找到太多的资料;想了想翻译一下文档,应该可以作为理解一下这个模块的作用的开始。
(然而发现并不能,所以又研究了一大堆相关的其他东西,就有了这一篇)
文档
socket
如果能通过分组执行socket搜索后,能够找到一个打开的TCP/UDP
socket,则会进行匹配。具体来说,这会匹配一条已建立(establshed)
的链接,或者非0绑定监听器(不是监听0.0.0.0或者::/非INADDR_ANY)
socket(比如说有可能是非本地地址)。搜索是通过TCP/UDP分组的元
组进行的,对于ICMP/ICMPv6错误分组来说,则是使用内嵌在内的原始
TCP/UDP头部中的元组。
--transparent
忽略非透明(non-transparent) sockets.
--nowildcard
不要忽略绑定在'任意'地址的sockets。默认情况下,不会
匹配0绑定监听器;因为在那种情况下,本地的服务可以
拦截本来会被转发的流量。因此,在匹配使用策略路由重
定向到本地的转发流量时,本选项有安全影响。当使用
socket 匹配器来实现绑定在非本地地址的完全透明代理
时,更推荐使用 --transparent 选项。
例如(假如被标记为1的分组会被发送到本地):
-t mangle -A PREROUTING -m socket --transparent -j MARK
--set-mark 1
--restore-skmark
将分组标记设置为匹配到的socket的标记。可以和 --transparent
以及 --nowildcard 选项组合使用来限制恢复分组标记时匹配的
sockets。
例如:一个打开了2个透明(IP_TRANSPARENT)的sockets,并
使用SO_MARK选项设置了一个标记。我们可以这样过滤匹配的分组:
-t mangle -I PREROUTING -m socket --transparent --restore-
skmark -j action
-t mangle -A action -m mark --mark 10 -j action2
-t mangle -A action -m mark --mark 11 -j action3
源代码中的注释
/* 基于 "socket" 匹配的重定向 (无特定规则)
* ===================================================
*
* 有一些链接使用了动态对端/端口(如FTP数据链接),
* 这种链接用户无法显式为其创建规则。
* 这些情况都可以通过通用的“socket”规则来处理。
* 假设信任的代理应用不需要显示的规则就可以建立链接
* (当然除了通用的"socket"规则除外)。在这种情况下
* 以下socket按照下列优先顺序匹配:
*
* - 匹配: 如果使用__分组__元组匹配到了一条完全
* 建立的链接(established)
*
* - 匹配:如果存在非0绑定(不是监听0.0.0.0或者::/非
* INADDR_ANY)的监听器(比如说有可能是非本
* 地地址)。我们不允许0绑定监听器是因为本地服
* 务可能会拦截通过机器的流量。
*/
PowerDNS的说明
你在到处看到的-m socket
网上很多TPROXY的iptables示例中都包含一条使用了-m socket -p tcp
也没有解释的改进。这个iptables的socket模块补丁能匹配到对应的本地socket,这可能比执行到一组特定的规则更加精确或快速。
下面这个到处都是的配置设定了一个接受并标记分组的redirect chain:
iptables -t mangle -N DIVERT
iptables -t mangle -A DIVERT -j MARK --set-mark 1
iptables -t mangle -A DIVERT -j ACCEPT
接下来这些规则保证了已建立的本地socket对应的所有内容都被发送到那;然后就是新分组需要进行的操作:
iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT
iptables -t mangle -A PREROUTING -p tcp --dport 25 -j TPROXY \
--tproxy-mark 0x1/0x1 --on-port 10025 --on-ip 127.0.0.1
iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY \
--tproxy-mark 0x1/0x1 --on-port 10080 --on-ip 127.0.0.1
TProxy-exmaple中的说明
唯一需要的步骤就是策略路由和TPROXY iptables-rule. DIVERT规则是为了防止tproxy目标做不必要的操作而做的优化(-m socks检查socket是否符合一个网络分组头部)
到底socket 模块是什么
我看了这么多注释,其实对socket模块也没有一个清晰地了解,很多使用到的也没有说的太细,看的云里雾里的。
但是普遍接受的一个观点是新建 DIVERT 规则,避免已有TCP连接的包二次通过 TPROXY,理论上有一定的性能提升
;仔细想了想可能有这么个道理。
所以要说清楚socket模块到底是什么,我们有必要稍微说明下TPROXY模块是什么。
TPROXY模块系咩来尬?
它做了什么?
TPROXY的作用主要是将链接在想要建立的socket(skb)上执行预操作,使内核在协议栈层面上对这些分组/socket重定向到本地做准备;其中很重要的一点就是,重定向到某个端口,以及重定向到某个IP监听的socket。而在这个过程中,整个分组(packet)本身并未被修改,只是socket本身被系统悄悄咪咪地替换了,所以我们还是能够获取到这个分组原始的目的地(通过分析分组头部)。
为啥要策略路由?
那有的同学会问了,既然这样要给分组打标记做策略路由的目的是什么?
原因其实就是上面说明的内容,虽然系统把socket偷梁换柱了,但是分组本身没有发生变化,它还会在系统中走完剩下的正常流程(比如说路由决策)。这时我们就需要将这个包引导到本地,不然就会给转发出去了。因为我们做的骚操作其实是在L4,而路由决策等操作是在L3,所以我们得一并把L3的事情给做了(路由),所以就需要策略路由来把我们悄悄咪咪做的事做完全套。
附赠的收获:TPROXY不支持转发到远程
从上述分析我们可以得出一个额外的结论:TPROXY并不支持重定向到远程机器;因为我们在L4层做的所有操作都是本地Only级别的(分组本身没有改变);一旦分组被转发出去,就GG了;远程的机器根本不知道我们暗地里做了这些事。
正如iptables-extensions
所说:
It redirects the packet to a local socket without changing the packet header in any way.
他会重定向分组到一个本地socket,而不会以任何方式修改分组头部本身。
内核中的阻碍
好不容易,我们终于让本地的socket有希望(其实还收不到)收到了来自TPROXY的分组。
因为分组头部未改变,我们的程序很容易的可以获取分组中原来目的的地址和端口;但是此时如果想要建立链接,我们的程序要在socket中绑定不属于自己的地址(非本地地址),才能让链接能够建立。
即程序要假装自己是目标地址,返回数据包给还蒙在鼓里的客户端。但是这种行为默认是在系统中禁止的;得过了系统的这一关,这种浑水摸鱼的操作才能成功。
所以我们就有了SOL_IP, IP_TRANSPARENT这个socket监听选(后)项(门)来说服系统让我们这么做(毕竟这种高风险操作,那不能是谁都可以做的呀)。
有了这个选项以后,我们就可以假装是目标地址,使用它的信息来在客户端不知情的情况下成功建立链接。
所以socket模块有什么关系?
说了这么多,我们把TPROXY的基本操作说完了,但是似乎这些和socket模块有什么关系呢?
其实和TPROXY的模块就在刚才说的替换socket这一步。我们要怎么确定要替换的socket究竟是哪个呢?
这时候我们再回来看一下上面关于socket模块的匹配描述。
* - 匹配: 如果使用__分组__元组匹配到了一条完全
* 建立的链接(established)
*
* - 匹配:如果存在非0绑定(不是监听0.0.0.0或者::/非
* INADDR_ANY)的监听器(比如说有可能是非本
* 地地址)。我们不允许0绑定监听器是因为本地服
* 务可能会拦截通过机器的流量。
这时我们就会发现,这个匹配行为似乎和TPROXY非常相似;那么其实socket模块是不是就是为了简化连接建立后TPROXY复杂的行为,从而提升性能的优化呢?
我们来看看TPROXY的源代码中有没有蛛丝马迹。
来自内核开发组的一封信
// https://elixir.bootlin.com/linux/v6.1.11/source/include/net/netfilter/nf_tproxy.h#L75
/*
* 这是用于用户想要显式通过iptables拦截符合规则的链接。在这种情况
* 下,我们假设sockets按照以下顺序匹配:
*
* - 匹配: 如果使用__分组__元组匹配到了一条完全建立的链接,
* 就直接返回它;我们假设重定向已经完成了,因为我们正在
* 处理的是一条已经建立的链接。
*
* - 匹配:如果有与重定向目的地(就是链接的on-porth和on-ip)
* 匹配的监听socket,不管socket是监听在0.0.0.0还是一个具体
* 的地址,都直接返回它。我们这么做的理由是因为已经存在
* 显式的规则了,所以监听器不管是不是监听在某个网卡上,
* 还是0地址都不重要。用户已经声明了他想要重定向(因为
* 他新增了这条规则)。
*
* 请注意,TPROXY目标和socket模块的匹配之间存在重叠。通常情况
* 下,如果这两条规则你都创建了,“socket”模块会是第一条规则,实际
* 上所有已建立的链接都会通过这条规则。
*/
还有另一段小注释
// https://elixir.bootlin.com/linux/v6.1.11/source/net/ipv4/netfilter/nf_tproxy_ipv4.c#L103
/* 提示: 就算是监听在0.0.0.0的监听器,我们
* 也会返回。那种情况在xt_socket中被过滤
* 了,因为xt_TPROXY也需要0绑定监听器。
*/
尘埃落定
到这里,关于socket模块作用的这个问题,就呼之欲出了。
但是我们还是不要飞的这么快,再捋一下这个逻辑:
TPROXY和socket模块的匹配规则有重叠之处;链接建立后(established)不管是TPROXY还是socket模块,都是直接匹配的这个socket。
同时链接建立后,系统中该分组对应的socket也不会再发生改变了(不然链接就错乱啦);而TPROXY内部的逻辑远比socket复杂,那么内核组的开发者们自然就想利用这个来优化下性能了(当然这里指的是TCP,UDP的TPROXY内核实现有些许不同,但是大体的思路是一致的)。
我们再回想下TPROXY之前的操作,L4偷换socket,L3标记分组修改路由;那现在socket已经换过,差的就是给分组打上对应标记从而使得策略路由生效了。
再回过头来看我们的DIVERT CHAIN做的不正好就是这一件事嘛?而socks模块的逻辑比TPROXY简单许多,这就存在了性能差异。
结论:socks模块在这里的作用,确实是在尝试优化TPROXY,以获取更高的性能。
尾声
折腾了这么久,终于找到了这个问题的答案了。
期间在网上漫无目的的搜索,也没有找到太多有用的信息,最后还是通过源代码阅读之术和运气的加持之下,才磕磕碰碰的勉强找到了答案。
其实之前也尝试过很多次看Linux内核的源码,但是Linux内核的设计实在是太高端了,没能够看懂太多东西。
但是这个Netfilter/iptables之旅,恰好给了我这么个机会。这一路上见识到了TProxy的奇妙想法、设计;简洁的实现(核心思想真的非常简单,没想到还可以有这种骚操作),充满魅力的Linux代码(特别精妙);非常强大的Netfilter/iptables。(小声:可惜iptables还没搞明白,nftables它又来了;你不要过来啊啊啊啊啊啊(:з」∠))
第一次阅读Linux内核代码(挖坑:以后看看有没有机会能不能把源码分析过程也整理出来),可能有一些错误,欢迎大家指正。
小尾巴
最后的最后,其实我还是好奇上面的DIVERT
规则是否能优化一下;看起来socks
模块现在似乎新增了一些选项,现在似乎都没有利用上。之后测试一下来更新一下结果看看哈哈哈哈哈~
// 选项1,只用 --transparent
iptables -t mangle -N DIVERT
iptables -t mangle -A DIVERT -j MARK --set-mark 1
iptables -t mangle -A DIVERT -j ACCEPT
iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j DIVERT
// 选项2,结合 --transparent 和 --restore-skmark
iptables -t mangle -A PREROUTING -p tcp -m socket --transparent --restore-skmark -j ACCEPT
选项1使用了--transparent
选项,只应用本地socket中透明代理的部分,理论上应该会降低其他分组被意外打上标记的问题。
选项2则在选项1的基础上更进一步,使用了--restore-skmark
的新特性,将多条语句合并为一句;根据提交记录[10]介绍,应该是能达到和最初优化语句一样的目标的,同时还增加了更多可能性。
小尾巴2——来自Nftables的说明
时隔文章发布一年半以后,今天看到tproxy的Kernel文档中包含了使用Nftbales的示例,而Nftables的文档更清晰的说明了socket模块的作用,所以特意加上来以帮助大家理解。
你也可以通过以下nft命令来实现
# nft add table filter
# nft add chain filter divert "{ type filter hook prerouting priority -150; }"
# nft add rule filter divert meta l4proto tcp socket transparent 1 meta mark set 1 accept
Nftables的文档[11]
SOCKET 语法
socket {transparent | mark | wildcard}
socket cgroupv2 level NUM
Socket语法可以用来搜索一个已经存在的打开的TCP/UDP socket以及其可与分组关联的属性。它寻找的是已经建立的或者非0绑定的监听socket(可能使用非本地地址)。你也可以通过此语法来匹配给定祖先等级/ancestor level的cgroupv2 socket。比如如果一个socket属于cgroupv2 a/b,祖先等级1会检查并匹配cgroup a,而祖先等级2则会检查并匹配cgroup b.
Table 32. 可使用的socket属性
属性名称 | 描述 | 类型 |
---|---|---|
transparent | 匹配到的socket中的IP_TRANSPARENT选项的值。可以为0或者1。 | boolean (1 bit) |
mark | socket mark的值 (SOL_SOCKET, SO_MARK)。 | mark |
wildcard | 表明该socket是否为通配符绑定 (e.g. 0.0.0.0 or ::0)。 | boolean (1 bit) |
cgroupv2 | 此socket的cgroup version 2 (/sys/fs/cgroup的路径) | cgroupv2 |
# 标记与透明socket相关的分组。“socket wildcard 0” 表示**不**匹配
# 0绑定监听socket(这通常正是您想要的)。
table inet x {
chain y {
type filter hook prerouting priority mangle; policy accept;
socket transparent 1 socket wildcard 0 mark set 0x00000001 accept
}
}
# 追踪mark为15的socket对应的分组
table inet x {
chain y {
type filter hook prerouting priority mangle; policy accept;
socket mark 0x0000000f nftrace set 1
}
}
# 将分组mark设置为socket mark
table inet x {
chain y {
type filter hook prerouting priority mangle; policy accept;
tcp dport 8080 mark set socket mark
}
}
# cgroupv2第一级为"user.slice"的计数器
table inet x {
chain y {
type filter hook input priority filter; policy accept;
socket cgroupv2 level 1 "user.slice" counter
}
}
参考文章
[1] https://man7.org/linux/man-pages/man8/iptables-extensions.8.html
[2] https://ipset.netfilter.org/iptables-extensions.man.html
[3] https://patchwork.ozlabs.org/project/netdev/patch/20081001142431.4893.56954.stgit@este/
[4] https://elixir.bootlin.com/linux/latest/source/net/netfilter/xt_socket.c
[5] https://github.com/kristrev/tproxy-example
[6] http://wiki.squid-cache.org/Features/Tproxy4
[7] https://powerdns.org/tproxydoc/tproxy.md.html
[8] https://www.kernel.org/doc/html/latest/networking/tproxy.html
[9] https://elixir.bootlin.com/linux/v6.1.11/source/include/net/netfilter/nf_tproxy.h#L75
[10] https://patchwork.ozlabs.org/project/netfilter-devel/patch/1434415279-15517-1-git-send-email-harouth@codeaurora.org/
[11] https://www.netfilter.org/projects/nftables/manpage.html
user
三酱贴贴
bcmm
xdnmb.com域名是不是快释放了|∀` )三酱还考虑注册不
另外在这里悄悄放置一个备胎岛数据回归希望