最近有个需求,需要在路由器设备中截获数据包,从而实现中转。为了这个事情,老师顺便给安排了三个小弟,一是给我找点帮手,二是让我带带他们。按照下面的拓补,说明。我们需要在主机h2上截获h1发往h3的TCP协议包。最先实现的版本是基于tun设备,数据包截获之后,采用UDP协议中转,类似openvpn的方式。
h1----s1----h2------h3
采用tun的方式,需要配置h1的默认网关。但是我们的需求比较特别,我们对h1是没有修改权限的。之前师弟实现了一个版本,采用tap的方式,将h2-eth0同tap设备进行桥接。这种解决方案,师弟产出一篇博客[1]。
采用桥接的模式,确实不用修改h1中路由表配置。但是我们又进一步的需求,h2截获了h1发送的tcp数据包,h2能够回复ack报文给h1。如果采用桥接的模式,截获的是以太网帧,回复ack,就需要移植一个用户态TCP协议栈,也看了几天,相中了mtcp。但是,感觉太麻烦。麻烦的事情,坚持走下去,就需要大量的时间去填坑。我希望这事可以快点。我写代码的能力有限,摘抄别人的代码很厉害。比如今天写的demo,连抄带写,不到500行。
之后,就查到了tcp透明代理[3,4,5],可以通过修改h2的路由表,截获TCP数据包,中转应用层数据。
但是,这种模式,需要在h1中指定h2位默认网关。之前测试的时候,在h1指定了h2作为网关,h1就能ping通h3。h1指定h3或者一个不存在的一个ip作为网关,h1 ping h3就失败。我一直在猜,h1在发送icmp包之前和h2有什么握手协议。无奈,本科时候计算机网络学的太差,实在不知道这里有个什么握手协议。在照本宣科的课堂上,能学到鬼的东西。当时学习的教材还是国内的一本经典教材,不过也是从坦南鲍姆那本书中抄的。特殊时代的特殊产物。在知乎上有个很有意思的问题:谭浩强是c++之父吗? 有个回答很有意思:不是,谭是c+++++++之父。学过他的教材的就知道这个梗:请问 i+++++i,i最后的取值是什么?据说谭根本不会编程。不过也要承认他的贡献,在那个国内计算机教育起步的年代,他出的书,也算是播下火种。
后来,很偶然,在抓包的时候,发现h1在发送ip数据包时候,会向外广播arp报文,获得下一跳的mac地址。有了h2mac地址,h2才能处理数据包。后来就采用arp欺骗的方式[5],在不更改h1的路由配置的情况下,h2可以回复arp请求,将h2-eth0的mac地址返回给h1。
分配的三个小弟,实在是不给力。其中写博客的师弟,已经算不错的了,起码可以帮我写点mininet测试拓补,测试代码。这样我就能节省点时间。不然,我既要配置拓补,又要写代码,还要测试。我一天到晚,什么都不用干了。师弟在测试的过程中,我会告诉他我代码的逻辑。我相信,他是学到东西的。在我们这个三流学校,缺少self motivated(自驱,学习主动)的学生。不能自驱,那就靠他驱,同样可以学到东西。两者都没有,那就只能待着这里,看着时间悄悄流走,只长年龄,不长本领。
抄了这么多代码,我有个体会,写代码的能力和见到的bug数量成正相关。该踩过的坑,一个都少不了。每一个坑,都需要拿时间去填。就比如下面的拓补文件,我在h1中配置路由表,采用的是route add命令,在中间节点h2采用的是ip route add命令。感兴趣,可以在h2中采用route add,测试h1 ping h3。这种情况,h2是不会中转数据包的。我第一次使用mininet时候,在这里是卡了不少时间的。
测试拓补, 这里只测试透明代理,关于arp欺骗,参考[5]。
#!/usr/bin/python
from mininet.topo import Topo
from mininet.net import Mininet
from mininet.cli import CLI
from mininet.link import TCLink
import time
import datetime
import subprocess
import os,signal
import sys
#
# h1----s1----h2------h3
#
bottleneckbw=6
nonbottlebw=500;
buffer_size =bottleneckbw*1000*30/(1500*8)
net = Mininet( cleanup=True )
h1 = net.addHost('h1',ip='10.0.1.1')
h2 = net.addHost('h2',ip='10.0.1.2')
h3 = net.addHost('h3',ip='10.0.2.2')
s1 = net.addSwitch( 's1' )
c0 = net.addController('c0')
net.addLink(h1,s1,intfName1='h1-eth0',intfName2='s1-eth0',cls=TCLink , bw=nonbottlebw, delay='10ms', max_queue_size=10*buffer_size)
net.addLink(s1,h2,intfName1='s1-eth1',intfName2='h2-eth0',cls=TCLink , bw=bottleneckbw, delay='10ms', max_queue_size=buffer_size)
net.addLink(h2,h3,intfName1='h2-eth1',intfName2='h3-eth0',cls=TCLink , bw=nonbottlebw, delay='50ms', max_queue_size=buffer_size)
net.build()
h1.cmd("ifconfig h1-eth0 10.0.1.1/24")
h1.cmd("route add default gw 10.0.1.2 dev h1-eth0")
h1.cmd('sysctl net.ipv4.ip_forward=1')
#tproxy
h2.cmd("iptables -t nat -N MY_TCP")
h2.cmd("iptables -t nat -A PREROUTING -j MY_TCP")
h2.cmd("iptables -t nat -A MY_TCP -p tcp -d 10.0.2.2 -j REDIRECT --to-ports 2223")
h2.cmd("iptables -N MY_TCP")
h2.cmd("iptables -A INPUT -j MY_TCP")
h2.cmd("iptables -A MY_TCP -p tcp --dport 2223 -j ACCEPT")
h2.cmd("ifconfig h2-eth0 10.0.1.2/24")
h2.cmd("ifconfig h2-eth1 10.0.2.1/24")
h2.cmd("ip route add to 10.0.2.0/24 via 10.0.2.2")
h2.cmd("ip route add to 10.0.1.0/24 via 10.0.1.1")
h2.cmd('sysctl net.ipv4.ip_forward=1')
h3.cmd("ifconfig h3-eth0 10.0.2.2/24")
h3.cmd("route add default gw 10.0.2.1 dev h3-eth0")
h3.cmd('sysctl net.ipv4.ip_forward=1')
net.start()
time.sleep(1)
CLI(net)
net.stop()
h2中代理服务器监听在2223端口。测试代码[7]. 可以在h1和h3中采用iperf测试。
[1] 利用TAP设备实现透明传输
[2] mtcp
[3] socket的IP_TRANSPARENT选项实现代理
[4] tproxy-example
[5] Linux透明代理 —— 使用iptables实现TCP透明代理
[6] 在mininet中测试arp欺骗
[7] tcp proxy