您好,欢迎来到微智科技网。
搜索
您的当前位置:首页使用TUN设备实现一个简单的UDP代理隧道

使用TUN设备实现一个简单的UDP代理隧道

来源:微智科技网
使⽤TUN设备实现⼀个简单的UDP代理隧道若要实现在 Linux 下的代理程序,⽅法有很多,⽐如看着 来实现⼀个 socks5 代理并⾃⾏设置程序经过 socks5 代理等⽅式,下⽂是使⽤ Linux 提供的 tun/tap设备来实现 UDP 代理隧道的⼤体思路和过程讲解。TUN 设备tun / tap 是由 Linux (可能还有其他 *NIX 系统提供⽀持)提供的,可以⽤来实现⽤户态的⽹络路由等处理的虚拟⽹络接⼝。也就是说,它们允许⽤户态的程序直接管理这个⽹络接⼝,⽽不是让内核协议栈来处理⽹络包。那么很明显,如果我们要实现⼀个代理隧道程序,那么我们第⼀步就要解决包从哪⾥来的问题,这很好解决,我们知道我们可以通过路由表来指定包到底应该流向哪个⽹络设备,等数据包进⼊我们能够控制的⽹络接⼝后,我们⾃⾏处理⽹络包的转发就好了。⽽这⾥,tun / tap 就是我们所需要的,能够为所欲为的⾃⾏处理包状态的虚拟⽹络接⼝。TUN 和 TAP 分别是虚拟的三层和⼆层⽹络设备,也就是说,我们可以从 TUN 拿到的就是 IP 层的⽹络数据包了,⽽ TAP 则是⼆层⽹络包,⽐如以太⽹包。因为我只打算对 IP 层的包进⾏处理(其实只打算处理 TCP 和 UDP),故接下来就只讨论 TUN 设备了。要想使⽤ 臀 TUN 设备,⾸先需要启⽤这个内核模块,在我的系统( Arch linux )上,只需 insmod ⼀下就⾏了。find /lib/modules/ -iname 'tun.ko.gz' # 找到在哪⼉sudo insmod /lib/modules/4.12.12-1-ARCH/kernel/drivers/net/tun.ko.gz # 插⼊该模块modprobe tun # 加载该内核模块,当然你也可以选择 the Windows way: 重启⼀下modinfo tun # 可以检查⼀下了lsmod | grep tun # 也可以这样检查为了⽅便,我选择重启...TUN 设备可以由程序创建和销毁(这种情况下即便程序没有主动的销毁创建的 TUN 设备,程序退出时 TUN 设备也会⾃⾏销毁),也可以使⽤ cli ⼯具创建和销毁,⽐如 ip tuntap ,tunctl ,或是 open --mktun。在我们⽤程序实现之前,我们先使⽤ ip tuntap 来创建⼀个 TUN 设备来进⾏简易的测试。简易测试建⽴⼀个 tun 设备(⽹络接⼝),然后设置路由表把数据包路由到 tun 设备⾥。sudo ip tuntap add dev dummytun mode tun # 添加⼀个叫 dummytun 的 tun 设备sudo ip link set dummytun up # 把设备 dummytun 开起来sudo route add 123.123.123.123 dummytun # 把 123.123.123.123 路由到 dummytunip route get 123.123.123.123 # 试试看是否搞成了如上并没有给这个 tun 设备 IP (但 ioctl() 打开这个 tun 设备后就会有⼀个 IPv6 地址了),当然也可以给它⼀个 IP :sudo ip addr add 192.168.61.0/24 dev dummytun由于 tun 设备需要我们编写⽤户态的程序来操作数据包,所以需要写个东西来处理包数据的 IO 。tun 是三层设备,故能拿到的都是三层( IP 层)的包了。下⾯是⼀个⾮常简单的代码⽚段,仅仅简单的把东西从 dummytun 中读出来⽽已。// 此处省略了 tun_open() 的原型。// 作⽤仅仅是 `open()` 该设备,`ioctl()` 连接到该设备最终返回⽂件描述符int fd = tun_open(\"dummytun\");// 我们假设是按照刚刚的步骤,在运⾏程序之前就已经创建并 `up` 了设备 dummytun// 如果你希望通过代码创建 tun 设备,别忘了把创建的设备 `up` 起来printf(\"Device dummytun opened\\n\");while(1) { int nbytes = read(fd, buf, sizeof(buf)); // FYI: char buf[1600]; printf(\"Read %d bytes from dummytun\\n\}另外额外需要注意的事是,在写过路由表规则以及给设备绑 IP 之后,最好还是刷⼀下缓存⽐较好,以免出现测了半天才发现压根没经过⾃⼰的 tun 设备的情况。sudo ip route flush cache当开始处理包时,我们就可以在 wireshark 或其他类似软件中看到流经该 tun 的⽹络包了。值得⼀提的是,即便事先没有给 tun 设备分配任何 IP 地址的情况下,在使⽤ ioctl() 打开 tun 设备后, tun 设备也将⾃动分得⼀个 IPv6 地址,所以在 wireshark 中是可以看到 ICMPv6 包存在的。TUN 设备的使⽤根据上⾯的简单测试,我们可以发现,实际上我们的程序本⾝就是完全接管所创建的虚拟⽹络设备的,我们程序所处于的职责就是,不断的 read 看看哪些⽹络包需要处理,然后程序进⾏处理并 write 就是了。作为最简单的实践,我们可以写⼀个⽆脑的程序去伪装远程端响应我们⽹络设备中出现的 ICMP 包,⾸先本地拦截 123.123.123.123 到我们的 TUN ⽹络设备,然后我们在 ping 的时候,就可以从 TUN 中 read 到 ICMP 包了,那么接下来,我们只需要互换 IP 包的源地址和⽬的地址,并修改 ICMP 包中的标志位为ECHO_REPLY ,(别忘了重算包的checksum)然后写回 TUN 设备,就可以让 ping 程序认为远程端服务器正确的做了响应了。我在编写时使⽤了⼀个叫 libtins 的第三⽅库来做包的拼装和解析,下⾯则是对上⾯描述的步骤的⼀个简单的例⼦。while(1) { nbytes = read(fd, buf, sizeof(buf)); // 假设 fd 为所创建的 tun 设备的⽂件描述符 printf(\"Read %d bytes from dummytun\\n\ RawPDU p((uint8_t *)buf, nbytes); try { IP ip(p.to()); cout << \"IP Packet: \" << ip.src_addr() << \" -> \" << ip.dst_addr() << std::endl; Tins::IPv4Address srcaddr = ip.src_addr(); ip.src_addr(ip.dst_addr()); ip.dst_addr(srcaddr); ICMP &icmp = ip.rfind_pdu(); icmp.type(ICMP::ECHO_REPLY); write(fd, ip.serialize().data(),ip.serialize().size()); // 其实不建议 serialize 调⽤两次,这⾥⽆所谓了 } catch (...) { continue; }

}

现在就可以看出我们的程序到底是⼲嘛的了吧?所以,当我们要实现隧道程序时,我们实际只是需要从我们创建的⽹络接⼝上读取数据包,并把数据包通过我们⾃⼰的⽅式发给代理隧道服务端,并等待代理隧道服务端的回复并写回我们创建的的⽹络接⼝就好了。也就是说,我们的客户端程序做的事情就是:从 TUN 设备读取数据包

将数据包发送给代理隧道服务端(本篇所⽤的是 UDP 发送未做任何加密处理的数据包)读取代理隧道服务端发回的数据包

把发回的数据包写回 TUN 设备

于是接下来我们可以试着把上⾯的程序改成两部分,客户端仅发送和接收,服务端则仅仅把源地址和⽬的地址交换并修改 ICMP 头的标志为 ECHO_REPLY。⽰例代码这⾥就不贴了,有兴趣的读者可以⾃⼰试着写⼀写,很简单的内容。所以,服务端呢?

实际上,客户端的⼯作就是简单的监听 UDP socket 和 TUN 设备的⽂件描述符,然后读写就是了,那么服务端怎么把客户端发来的包写到⽹卡中,⼜怎么把远程端服务器返回的数据包捕获到程序中呢?

为了让我们的数据包发出去后远程端返回的数据包依然能回到我们的代理隧道服务端程序所处的服务器上,我们⾃然要对数据包进⾏⼀次 sNAT。⽽假如我们把源地址改成了服务器的 IP,返回的数据包就会进⼊服务器的默认⽹络接⼝。⼀旦数据包进⼊由内核控制的⽹络接⼝,内核协议栈就会处理 SYN 包并⾃动做出响应,如果我们隧道中的 TCP 数据包发出去,结果远程端服务器与我们的服务器建⽴的连接,这就很糟糕了,于是我们需要让我们的数据包不经过协议栈处理⽽由我们控制。

我们能控制什么?当然是 TUN 设备啦!还记得我们可以给 TUN 设备指定⽹络地址吗?我们可以在服务端也建⽴⼀个 TUN 设备,并指定⼀个内⽹⽹络地址(我假设指定的是192.168.61.123),我们把要发的数据包写⼊该 TUN ,数据包就发出去了。接下来呢?我们当然是写⼀条 iptables 规则来把数据包导到我们的TUN 设备了。⼤概是这样的:

iptables -t nat -A POSTROUTING -s 192.168.61.0/24 -o eth0 -j MASQUERADE # 这样写

iptables -t nat -A POSTROUTING -s 192.168.61.0/24 -o eth0 -j SNAT --to-source xxx.xxx.xxx.xxx # 或这样,xx是服务器 IP哦对了。别忘了打开服务器的路由转发功能:echo \"1\" > /proc/sys/net/ipv4/ip_forward

这样的话,我们做 sNAT 的时候把源地址改为 tun 设备的地址就是了,⽽数据包回来时,⽹络数据包就会进⼊我们的能为所欲为控制的 TUN 设备啦!此时我们只需要做⼀次 dNAT 然后把数据包发回客户端,就⼤功告成了。

整个过程并不复杂,我的⼀份简单实现可以见 。上述内容可能有遗漏和错误,如果你发现了任何错误,都欢迎在下⾯评论指正,感激不尽!

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- 7swz.com 版权所有 赣ICP备2024042798号-8

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务