(); 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 然后把数据包发回客户端,就⼤功告成了。
整个过程并不复杂,我的⼀份简单实现可以见 。上述内容可能有遗漏和错误,如果你发现了任何错误,都欢迎在下⾯评论指正,感激不尽!