本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

本作品 (李兆龙 博文, 由 李兆龙 创作),由 李兆龙 确认,转载请注明版权。

若无特殊说明,内核源码版本为4.16.1

文章目录

  • 引言
  • 源码解析
    • tproxy_tg4_v0
    • tproxy_tg4
    • nf_tproxy_get_sock_v4
  • IP_TRANSPARENT
  • 总结

引言

Cool!一种完全透明的流量劫持方式。

从功能来看类似于让操作系统充当路由器,或者内置一个代理服务器,并允许部分流量被转移到用户空间处理,除此之外与iptables REDIRECT不同,基于tproxy的方案并不会修改源地址和端口,对于IP相关的过滤以及再次重定向来说是一个必要的功能点。

在[1][2]中原作者对于使用方式以及阐述的很清楚了,一个简单demo的基本步骤如下:

  1. iptables -t mangle -N DIVERT 在nat表上新建名为DIVERT自定义链
  2. iptables -t mangle -A DIVERT -j MARK --set-mark 1 进入DIVERT的数据包设置标记(skb->cb?看看源码吧)
  3. iptables -t mangle -A DIVERT -j ACCEPT 默认情况下,内核会丢弃数据包,现在要确保不会
  4. iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT 已建立的socket的TCP数据包执行DIVERT
  5. ip rule add fwmark 1 lookup 100 所有带有1标记的数据包都不再使用默认路由表,而是使用100
  6. ip route add local 0.0.0.0/0 dev lo table 100 添加一个路由规则,使得所有数据包(0.0.0.0)最终都被认为是本地的包
  7. iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 1234 --on-ip 192.168.123.1 所有发送到80端口的TCP请求会被标记0x1并被转发到192.168.123.1:1234

注意路由表的修改是必要的,不然无法认为绑定的IP是所谓”本地范围“的,也就没法转发到绑定IP_TRANSPARENT的套接字上了。

只是理论太过单调,我们来看看iptables tproxy在内核中到底做了什么。具体调用链如下 tproxy_tg4_v0 -> tproxy_tg4 -> nf_tproxy_get_sock_v4

源码解析

tproxy_tg4_v0

static unsigned int
tproxy_tg4_v0(struct sk_buff *skb, const struct xt_action_param *par)
{const struct xt_tproxy_target_info *tgi = par->targinfo;/*struct xt_tproxy_target_info {__u32 mark_mask;__u32 mark_value;__be32 laddr;__be16 lport;};*/return tproxy_tg4(xt_net(par), skb, tgi->laddr, tgi->lport,tgi->mark_mask, tgi->mark_value);
}

tproxy_tg4

static unsigned int
tproxy_tg4(struct net *net, struct sk_buff *skb, __be32 laddr, __be16 lport,u_int32_t mark_mask, u_int32_t mark_value)
{const struct iphdr *iph = ip_hdr(skb);struct udphdr _hdr, *hp;struct sock *sk;// 实际调用__skb_header_pointer,拿到skb的包头hp = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_hdr), &_hdr);if (hp == NULL)return NF_DROP;// 首先检查这个数据包是否存在已经连接的套接字sk = nf_tproxy_get_sock_v4(net, skb, hp, iph->protocol,iph->saddr, iph->daddr,hp->source, hp->dest,skb->dev, NFT_LOOKUP_ESTABLISHED);laddr = tproxy_laddr4(skb, laddr, iph->daddr);if (!lport)lport = hp->dest;/* UDP has no TCP_TIME_WAIT state, so we never enter here */if (sk && sk->sk_state == TCP_TIME_WAIT)/* reopening a TIME_WAIT connection needs special handling */sk = tproxy_handle_time_wait4(net, skb, laddr, lport, sk);else if (!sk)// 在listen队列中查找监听套接字,sk = nf_tproxy_get_sock_v4(net, skb, hp, iph->protocol,iph->saddr, laddr,hp->source, lport,skb->dev, NFT_LOOKUP_LISTENER);// 为了节省内存,一个套接字的不同阶段内部结构并不相同,这里检查此sock是否有transparent属性if (sk && tproxy_sk_is_transparent(sk)) {/* This should be in a separate target, but we don't do multipletargets on the same rule yet */skb->mark = (skb->mark & ~mark_mask) ^ mark_value;pr_debug("redirecting: proto %hhu %pI4:%hu -> %pI4:%hu, mark: %x\n",iph->protocol, &iph->daddr, ntohs(hp->dest),&laddr, ntohs(lport), skb->mark);/*static voidnf_tproxy_assign_sock(struct sk_buff *skb, struct sock *sk){skb_orphan(skb);skb->sk = sk;skb->destructor = sock_edemux;}*/// 修改此数据包的的对应套接字,其实就是转发nf_tproxy_assign_sock(skb, sk);return NF_ACCEPT;}pr_debug("no socket, dropping: proto %hhu %pI4:%hu -> %pI4:%hu, mark: %x\n",iph->protocol, &iph->saddr, ntohs(hp->source),&iph->daddr, ntohs(hp->dest), skb->mark);return NF_DROP;
}

nf_tproxy_get_sock_v4

// 这个函数逻辑比较简单,就是根据四元组和传入的参数去尝试拿到对应的套接字
static inline struct sock *
nf_tproxy_get_sock_v4(struct net *net, struct sk_buff *skb, void *hp,const u8 protocol,const __be32 saddr, const __be32 daddr,const __be16 sport, const __be16 dport,const struct net_device *in,const enum nf_tproxy_lookup_t lookup_type)
{struct sock *sk;struct tcphdr *tcph;switch (protocol) {case IPPROTO_TCP:switch (lookup_type) {case NFT_LOOKUP_LISTENER:tcph = hp;sk = inet_lookup_listener(net, &tcp_hashinfo, skb,ip_hdrlen(skb) +__tcp_hdrlen(tcph),saddr, sport,daddr, dport,in->ifindex, 0);if (sk && !refcount_inc_not_zero(&sk->sk_refcnt))sk = NULL;/* NOTE: we return listeners even if bound to* 0.0.0.0, those are filtered out in* xt_socket, since xt_TPROXY needs 0 bound* listeners too*/break;case NFT_LOOKUP_ESTABLISHED:sk = inet_lookup_established(net, &tcp_hashinfo,saddr, sport, daddr, dport,in->ifindex);break;default:BUG();}break;case IPPROTO_UDP:sk = udp4_lib_lookup(net, saddr, sport, daddr, dport,in->ifindex);if (sk) {int connected = (sk->sk_state == TCP_ESTABLISHED);int wildcard = (inet_sk(sk)->inet_rcv_saddr == 0);/* NOTE: we return listeners even if bound to* 0.0.0.0, those are filtered out in* xt_socket, since xt_TPROXY needs 0 bound* listeners too*/if ((lookup_type == NFT_LOOKUP_ESTABLISHED && (!connected || wildcard)) ||(lookup_type == NFT_LOOKUP_LISTENER && connected)) {sock_put(sk);sk = NULL;}}break;default:WARN_ON(1);sk = NULL;}pr_debug("tproxy socket lookup: proto %u %08x:%u -> %08x:%u, lookup type: %d, sock %p\n",protocol, ntohl(saddr), ntohs(sport), ntohl(daddr), ntohs(dport), lookup_type, sk);return sk;
}

可以发现tproxy的代码实现非常简单,让我大声为您朗读一遍:tproxy尝试从连接或者监听套接字列表中找到一个符合四元组的套接字,然后如果发现此套接字含有IP_TRANSPARENT就修改此数据包的sock结构,这个行为其实就是转发。

所以要实际转发的话首先我们需要一个绑定了IP_TRANSPARENT选项的套接字,

IP_TRANSPARENT

IP_TRANSPARENT的作用不仅仅是用于tproxy的转发,还可以使得被设置的套接字绑定非本地的IP地址,但是必须设置路由表,否则数据包会被路由、丢弃,根本无法被转发到绑定IP_TRANSPARENT到套接字。除非如果目标地址与本地地址匹配,则由系统本身接受处理,这就需要手动设置一个单独的路由表,并由mark去指示。man文档中[10]中描述如下:

Setting this boolean option enables transparent proxying on this socket. This socket option allows the calling application to bind to a nonlocal IP address and operate both as a client and a server with the foreign address as the local endpoint. NOTE: this requires that routing be set up in a way that packets going to the foreign address are routed through the TProxy box (i.e., the system hosting the application that employs the IP_TRANSPARENT socket option). Enabling this socket option requires superuser privileges (the CAP_NET_ADMIN capability). TProxy redirection with the iptables TPROXY target also requires that this option be set on the redirected socket.

设置此布尔选项可在此套接字上启用透明代理。 此套接字选项允许调用应用程序绑定到非本地 IP 地址,并以外部地址作为本地端点作为客户端和服务器运行。 注意:这需要设置路由,使去往外部地址的数据包通过 TProxy box(即托管使用 IP_TRANSPARENT 套接字选项的应用程序的系统)进行路由。 启用此套接字选项需要超级用户权限(CAP_NET_ADMIN 功能)。 使用 iptables TPROXY 目标的 TProxy 重定向还要求在重定向的套接字上设置此选项

总结

打开思路,透明代理实际是在网络数据包从程序实际面对的套接字到物理网卡之间加入了一个处理数据包的过程,而且因为此数据包的处理流程不像是eBPF一样在内核中充满阻碍的处理代码,而是在被转发到用户态的套接字,这意味着我们可以捕获流量实现任意的注入,统计,分析等等。

但是注意基于容器做透明代理是还是需要注意network namespace的设置,因为ehashlhash在做哈希时实际是带着network namespace选项的[11],这会在tproxy实际实行转发时被使用。

一个简单的使用demo如下:tproxy-http_hijacking。

参考:

  1. Transparent proxy support
  2. Linux transparent proxy support
  3. man iptable
  4. tproxy 用例
  5. 透明代理(TPROXY)
  6. What is the purpose of TPROXY, how should you use it and what happens internally?
  7. iptables:tproxy做透明代理
  8. Abusing Linux’s firewall: the hack that allowed us to build Spectrum
  9. 透明代理解决方案(一)
  10. ip man
  11. 再谈Linux服务端编程

记玄妙莫测的透明代理相关推荐

  1. 透明代理Transparent Proxy

    透明代理Transparent Proxy 透明代理Transparent Proxy类似于普通代理,它可以使得处于局域网的主机直接访问外网.但不同之处,它不需要客户端进行任何设置.这样,客户端误以为 ...

  2. squid+iptables实现透明代理

    NAT 网络地址转换(网络地址映射) 就是把数据包的源IP或者目标IP进行修改. 作用: 修改源IP,叫源地址映射,一般为了实现让私有网络的机器能够访问互联网 修改目标IP,叫目标地址映射,一般为了实 ...

  3. squid+iptalbes实现透明代理配置记录

    首先描述下硬件环境cpu P4 2.93\2G\80G 单网卡;系统环境为CentOS 5.4 x86_64,所需的squid由yum安装,squid包的版本为squid-2.6.STABLE21-6 ...

  4. 使用squid配置透明代理并对上网行为进行控制

    使用Squid配置透明代理 环境:CentOS 6.4 + squid-3.1.10-20.el6_5.3.x86_641.检查squid是否默认安装,没有安装先安装 rpm -qa squid 假如 ...

  5. Haproxy全透明代理

    1. 系统环境搭建 操作系统Centos7 内核版本3.10 Centos7已自带TPROXY模块,不需要安装TPROXY 2. Haproxy下载,编译,安装,配置 下载地址 http://www. ...

  6. mysql 透明代理_透明代理MySQL_基于zbus的MySQL透明代理(100行)-云栖社区

    我们上次讲到zbus网络通讯的核心API: Dispatcher -- 负责-NIO网络事件Selector引擎的管理,对Selector引擎负载均衡 IoAdaptor -- 网络事件的处理,服务器 ...

  7. Squid代理(传统代理、透明代理、反向代理)、日志分析、ACL访问控制

    Squid代理(传统代理.透明代理.反向代理).日志分析.ACL访问控制 一.Squid代理服务器 1.代理的工作机制 2.代理的类型 二.安装Squid服务 1.编译安装Squid 2.修改Squi ...

  8. nginx做透明代理

    前一阵子在帮一朋友解决问题时,聊及nginx的透明代理的问题,当时就想修改nginx来实现透明代理,幸好一直没有付诸实现,不然又一次重造轮子. 下午在邮件列表中讨论到这个问题,nginx的作者Igor ...

  9. 构建Squid代理服务器-传统代理、透明代理、反向代理

    Squid是Linux系统中最常用的一款开源代理服务软件,主要提供缓存加速和应用层过滤控制的功能,可以很好的实现HTTP.FTP.DNS查询以及SSL等应用的缓存代理. 正向代理: 根据实现的方式不同 ...

最新文章

  1. 对话谢宝友:搞真正自研的国产操作系统,而不是伪创新
  2. Paper之CV:《One Millisecond Face Alignment with an Ensemble of Regression Trees》的翻译与解读
  3. 软考网络工程师学习笔记3-广域通信网
  4. 为什么Prim算法不适用于带权有向图
  5. 监听者模式 java_java监听者模式
  6. Log4J 最佳实践之全能配置文件
  7. 手机模板区块(HTML、CSS)
  8. pcd格式点云的显示程序
  9. 第19节 扫描技术——基于Windows系统的工具
  10. 重庆江北鲁能旁边孩子学计算机,家长们注意!重庆多个区县中小学划片公布!这些学校民转公...
  11. tauri打包慢:解决tauri的打包慢以及超时的方法
  12. 最新短网址链接生成系统源码+短链防红功能
  13. 王牌投手·MLB棒球创造营
  14. 从零开始Android游戏编程(第二版) 前言
  15. 【AE工具】AE一键切换中英文小工具,免费下载 支持CC2014-CC2019
  16. 去看看《自动化学报》等等这种期刊,这样看可能有感觉些,比你单纯在知网零散搜可能要好些。
  17. BugKu_ez_java_serialize
  18. 爬虫一 requests库与BeautifulSoup库、HTML
  19. Spark的深入浅出
  20. 【微信电子书制作软件】名编辑电子杂志大师教程 | 查看添加的元素

热门文章

  1. failure:Build failed with an exception.
  2. 灵性·挖掘:自我迭代之路
  3. Python爬虫实战——获取电影影评
  4. 记录借款、还款、修改的详细步骤
  5. CSS display详解
  6. PMP十五至尊图(PMBOK第六版)
  7. 振兴会杜振国:上证指数编制要调整了,3000点永别了?
  8. 智慧旅游建设包含了哪些内容?
  9. 服务器压缩PNG图片
  10. Linux安装最新版Privoxy