用Netcat,SSH构建的IP层加密隧道搭建VPN

来源:互联网 发布:汽车销售网络平台 编辑:程序博客网 时间:2024/06/07 17:51

【关于题外话在最后】

写作本文主要基于两点,首先是因为我前段时间写了几篇关于VPN的新解,收到了很多的邮件反馈,我也思考了很多,另一个方面是因为很多人问我怎么用QQ,P2P搭建一个IP层的VPN,我的回答是“我也不知道”。我确实不知道,根本就没有试过,只是有个这样那样的想法...我主要是没有能力去Hack这些非Linux上的东西...所以说,我写这篇文章,用UNIX的方法“将多个小工具结合起来”实现我的那些没有实现的想法,抛砖引玉一下。

声明:

本文没有技术含量,甚至不会有什么代码,本文只是一些Linux bash和netcat的使用技巧之堆砌,但我觉得只有这种是理解Everything over App的最好方式,虽然我没有能力将TAP对接到QQ上,但我有能力将其对接到netcat以及ssh上,而netcat,ssh和QQ并没有什么本质的区别,我的结论是,只要你能找一个通信软件,隧道便可以嫁接于其上。

IP层的VPN

概念很简单,就是构建一条隧道,把IP报文从一个节点运输到另一个节点,该隧道可以是加密的,也可以是不加密的。



隧道的种类

一般而言,传统的VPN都是用新的IP报文来运输原始IP报文的,这种新的IP报文往往封装一个新的四层头。比如IPSec VPN。但是也有用TCP,UDP来运输原始IP报文的,典型的例子就是OpenVPN,BadVPN等。
       事实上,你可以用任何的报文来运输原始IP报文,在上一篇文章《从一个简单的聊天程序SimpleChat看VPN技术》中,我提到你可以用QQ,P2P协议来运输IP报文,并且给出了一个应景的设想。本文将通过简单的实例来实现一下这种设想。

发展阶段

像类似PPPoE,L2TP,IPSec什么的,提出了一个很好的框架和概念,说明IP over XXX的可行性,然后OpenVPN掀开了一次Revolution,说明IP可以over UDP,并且和SSL/TLS进行了深度结合,小众的BadVPN应该是另一次Revolution的,但是作者好像没有把握住机会,和OpenVPN不同的是,BadVPN旨在提供灵活且独立的点对点组网能力。
       在研究了BadVPN之后,我自己写了一个简单的Demo,即SimpleVPN:https://github.com/marywangran/SimpleVPN/
       其实,BadVPN的思路很简单,这个和我们平时的聊天软件的思路完全一致,不同的是聊天软件运输的是我们键盘输入的消息,而BadVPN运输的则是从TAP网卡中读取的IP报文。借着这个思路,我觉得任何的可以进行网络通信的软件都可以运输IP报文,只需要将程序的输入和输出重定向到TAP网卡的输入和输出即可,这非常简单。

准备工作

如果说TAP网卡在文件系统中以一个文件的形式存在,那么事情会非常简单,比如tap0网卡在文件系统中有一个字符设备文件/dev/tap0,那么以scp为例,我便可以通过以下的命令来运输通过tap0传输的IP报文或者以太帧:
scp /dev/tap0 root@192.168.44.129:/dev/tap0
然而自打Linux 2.6.x(8<x<32)起,内核便不再提供这种机制了。至少在2.6.8的内核中,有一个ethertap的模块,它提供了我上述所说的“理想方法”,你只需要以下的命令便可以构建一个tap0文件:
modprobe ethertap
ifconfig tap0 10.10.10.10/24 up
mknod /dev/tap0 c 36 16
...

可是到了2.6.32内核,ethertap模块没有了,只剩下了tun模块,使用tun模块,无法直接建立tap0的设备文件,必须通过ioctl来显式设置它:
modprobe tun
tunctl -u root -t tap0
# 注意,tun模块让你无法通过命令构建一个可以直接读写的tap0设备文件,你必须写一个程序显式执行open/ioctl对,然后操作其文件描述符进行读写。
...

这意味着我不得不写一个程序了,而这令不会编程的我感到悲哀!我把该文件命名为tunio.c,如下所示:
#include <stdlib.h>#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <net/if.h>#include <linux/if_tun.h>#include <sys/select.h>#include <sys/ioctl.h>#include <fcntl.h>struct frame {        unsigned int len;        char data[2000];};// 为了便于重定向操作,我在本程序中只使用了3个文件描述符:TAP网卡字符设备,标准输入,标准输出int main(int argc, char **argv){        int fd = -1;        struct ifreq ifr;        size_t len;        char buf[2004];        struct frame frm;        int i;        fd_set rd_set;        if( (fd = open("/dev/net/tun", O_RDWR)) < 0) {                exit(-1);        }        memset(&ifr, 0, sizeof(ifr));        ifr.ifr_flags |= IFF_NO_PI;        ifr.ifr_flags |= IFF_TAP;        snprintf(ifr.ifr_name, IFNAMSIZ, "%s", "tap0");        ioctl(fd, TUNSETIFF, (void *)&ifr);        while(1) {                int nfds;                int j;                FD_ZERO(&rd_set);                FD_SET(0, &rd_set);                FD_SET(fd, &rd_set);                char *tmp;                nfds = select(1024, &rd_set, NULL, NULL, NULL);                for (j = 0;j < nfds; j++) {                        // 如果标准输入有动静的话                        if(FD_ISSET(0, &rd_set)) {                                unsigned int dlen;                                // 从标准输入中读取数据长度                                len = read(0, &dlen, sizeof(unsigned int));                                // 按照指示的长度读取原始IP报文或者数据帧                                len = read(0, frm.data, dlen);                                // 将IP报文或者数据帧写入TAP设备                                len = write(fd, frm.data, len);                        }                        // 如果虚拟网卡字符设备有动静                        if(FD_ISSET(fd, &rd_set)) {                                // 从网卡读取IP报文或者以太帧                                len = read(fd, buf, sizeof(buf));                                // 为了对端可以区分数据边界,加入了长度头                                frm.len = len;                                memcpy(frm.data, buf, len);                                tmp = (char *)&frm;                                // 将加入长度头的原始数据输出到标准输出                                for (i = 0; i < len+sizeof(unsigned int); i++) {                                        printf("%c", tmp[i]);                                }                        }                        // 刷新输出                        fflush(NULL);                }        }        return 0;}

将以上的代码编译成tunio即可:
gcc tunio.c -o tunio
然后就可以用这个tunio和其它的既有程序进行基于UNIX哲学的组合从而构建出一个看起来像那么回事的VPN了。
       在继续之前,我不得不说一下不能继续使用ethertap的原因。
       一个好好的ethertap为什么就下课了呢?其实很大的原因在于ethertap所依赖的Netlink机制被重构了。在2.4版本以及2.6的早期版本的内核中,Netlink消息是通过字符设备进行读写的,然而后来Netlink便不再采用字符设备了,而是采用socket API进行读写了,而socket并没有导出文件到文件系统这也是众所周知,socket仅仅导出文件到进程。然而这跟ethertap下课有关吗?有的!
       ethertap之所以可以导出文件到文件系统,就是使用的Netlink字符设备,你看看上述的mknod命令,其major设备号36就是Netlink的major设备号:
#define NETLINK_MAJOR        36
在init_netlink中:
if (register_chrdev(NETLINK_MAJOR,"netlink", &netlink_fops)) {...
在ethertap模块代码中,你看不到任何关于字符设备的逻辑,事实上ethertap的字符设备文件的读写逻辑已经被Netlink接管,这个从hard_xmit以及netif_rx这两个协议栈的读写接口中便可获知,它们都是直接与Netlink接口。
       好了,现在Netlink不再采用字符设备的方式了,改由API调用的方式控制了,那么ethertap便面临两个选择,一个是也跟着采用API的方式进行控制,而这个与tun模块功能重复,另一个选择是为ethertap单独注册一个字符设备类型,或者将其注册到misc设备,但这样会使字符设备集合越来越臃肿...再者说了,不还有tun的吗?编个程序封装一下读写控制逻辑有那么难吗?所以说,只能下课了...
       其实,虽然ethertap好用,但其实现方式是有问题的,试想如果大家都为每一个功能注册一个Netlink号码,或者注册一种字符设备,那么几乎可以肯定,这种横向的平坦拓展方式总有一天会让Netlink或者字符设备管理机制不堪重负...之前的ioctl问题就是一个例子。
       下面开始真正有意思的东西。

使用netcat实现一个简单的Demo

小用一下这个被誉为“瑞士军刀”的小巧nc,我来展示一下IP报文是如何通过nc构建的连接运输到另一个任意节点的。
       我把tunio的标准输出重定向到netcat的标准输入,然后把netcat的标准输出重定向到tunio的标准输入,事情就成了,这让整个事情变成了一个环:




是不是很简单呢?
       那么怎么实现呢?虽然逻辑上上图已经足够简单,但是实现上还是需要一个pipe作为过渡的,因此我们先在两台机器上分别创建一个pipe:
mkfifo /tmp/tmp_fifo
接下来的玩法完全取决于你对netcat的熟悉程度,本文不讲netcat,只说VPN,所以直接给出命令:
机器1:
cat /tmp/tmp_fifo | ./tunio 2>&1 | nc -l 1567 >/tmp/tmp_fifo
ifconfig tap0 10.10.10.10/24 up

机器2:
cat /tmp/tmp_fifo | ./tunio 2>&1 | nc 192.168.44.100 1567 >/tmp/tmp_fifo
ifconfig tap0 10.10.10.20/24 up

然后你来试试在机器2上去ping机器1的tap0的地址,绝对通了。这下,我们在物理网络的TCP连接上成功构建了一个承载以太帧的Ethernet over TCP的Overlay,显然这还远远不够,我们来抓包看看。在机器2上执行:
curl 10.10.10.10
同时在机器1上抓包:
tcpdump -i any tcp port 1567 -n -w overlay1.pcap
打开这个overlay1.pcap,我们可见:




好吧,虽然很完美的完成了隧道封装,然而没有实现加密...某种意义上,这并不是VPN,具体为啥我在这里多说无益。技术归技术,我们现在的目标是实现加密传输。这简直太简单了。

       在继续展示加密隧道之前,我必须来点题外话为netcat做点推广。在前几篇文章中,我提到了QQ,P2P,甚至差点连飞鸽传书都扯上,但这些对Linux用户来讲,都是被鄙视的。在Linux上难道不是netcat这把瑞士军刀最靠谱吗?如果两人都使用Linux,最轻便的聊天工具就是netcat,根本不用装别的什么乱七八糟的东西。

实现一个加密隧道:使用ncat

当然,按照UNIX的传统方法,使用netcat和一个加密解密的命令加上输入输出重定向,就能在上节例子的基础上实现加密隧道,比如可以nc...|mcrypt...,然而我没有成功!所以我不得不使用别的方案。
       幸亏有一个好用的ncat可以使用,我直接就用了。其实还有一个cryptcat可用,但我没有用成功,如果用成功的,希望可以告诉我。我本人对bash重定向的掌握一直都是半吊子水平,所以用起来当然没有玩Netfilter,VPN这般得心应手。决定使用ncat,命令如下:
机器1:
cat /tmp/tmp_fifo | ./tunio 2>&1 | ncat -vnl 333 --ssl >/tmp/tmp_fifo

【特意给出屏幕输出,以显示正在初始化加密套件】



ifconfig tap0 10.10.10.10/24 up
机器2:
cat /tmp/tmp_fifo | ./tunio 2>&1 |ncat -nv 192.168.44.100 333 --ssl  >/tmp/tmp_fifo

【特意给出屏幕输出,以显示正在初始化加密套件】



ifconfig tap0 10.10.10.20/24 up
同上面的netcat明文实例一样,依然在机器2上执行curl,在机器1上抓包,得到如下结果:




我不可能去解释这些密文是什么个含义,总之,这就是一条加密的隧道,构建成功了。至于说加密强度如何,不属于本文的范畴。

实现一个加密隧道:使用ssh

其实一开始我是希望使用scp来做tap0字符设备文件的传输的,中间我采用了一个fifo pipe来过渡,但是没有成功,貌似scp的源文件必须是Regular file才可以,不能是pipe。后来我决定放弃,进而寻找别的方案。毕竟,搞清楚scp并不是我的目的,其实我完全可以修改scp的源码使之适应pipe传输的,但是这样做毫无意义。
       放弃了scp后,我转向了直接使用ssh来执行远程命令,ssh执行远程命令要比scp更加通用,而不仅仅只是一个“文件传输”机制。废了大力之后,也算是小成功了,仍然是上述的机制,我的命令如下:
机器1:
mkfifo /tmp/tmp_fifo
./tunio </tmp/tmp_fifo |ssh root@192.168.44.129 "cat >/tmp/tmp_fifo"
ifconfig tap0 10.10.10.10/24

机器2:
mkfifo /tmp/tmp_fifo
./tunio </tmp/tmp_fifo |ssh root@192.168.44.100 "cat >/tmp/tmp_fifo"
ifconfig tap0 10.10.10.20/24

这个更加对称,比使用netcat/ncat的方式更加对称,更加优雅,但是效率去不咋地。抓包我就不给出了,大家自己品鉴吧。

VPN的本质

总的来讲,扯以上这些,我就是想说明一个观点,构建一个加密的隧道其实非常简单,方法更是多种多样,如果有人问你什么是VPN,你给他展示一下用Linux2.4内核ethertap模块+netcat+mcrypt或者快速手写一个tunio,然后配合ncat或者配合ssh构建一条加密隧道,那么你就算彻底理解了VPN的本质,如果你能快速让QQ和tunio这类程序对接,那你就是百折不扣的高手!当然,我达不到这样的水平,我只是指路人,我只是鼓手。当然,如果你真的拿netcat,ncat,ssh和tunio,ethertap这类对接了,很多学院派,科班生会觉得你这根本不是VPN,因为在他们眼里,VPN就是L2TP,PPTP,IPSec,MPLS之流,你这自己搭建的,根本就什么都不是...其实我觉得这种学院派才是什么都不懂。

       VPN在概念上满足两点即可,第一就是V(虚拟),第二是P(专用),这两点分别用Overlay和加解密技术完全可以完美表达。至于是L2TP,IPSec等,它们只是成熟的标准之作罢了,但完全没有做到大道至简。

感谢老一辈程序员使我可以写就本文

这部分本想放在文章最前面的,但是怕喧宾夺主,所以移到了最后。
       说实话,本文根本没有任何技术含量,但是绝对可以引发人们的思考。在我2006年刚刚参加工作的时候,我什么都不懂。但我跟一帮上世纪90年代以及21世纪前5年的程序员一起学到了很多的东西,比如用鼠标线上网,用串口联网,...他们说以前的时候,以太网卡和网线是稀缺的,反而PS/2,RS232是常见的,人们普遍都是使用物理单机,然后依靠软盘,刻录光盘,后来的U盘作为介质来传输数据,这就好比我之前说的用卡车运数据时一样的。
       如果说我在一块没有接线网卡(假设它没有接线依然可以运行)上抓到了一个数据帧,然后把这个帧放到了一个U盘里,将U盘通过卡车,轮船运送到了美国,在美国有人将这个数据帧注入到了一个没有接线的网卡里,那么在协议栈看来,这个主机就跟从网线上收到了这个数据帧是一样的...所以说,网线只是个介质。
       在2010年的时候,我第一次知道了TAP网卡这种东西,发现IP数据报文还可以通过一个字符设备读到用户态应用程序,进而被加密后通过socket传输出去,我觉得这太妙了!这难道不是跟用串口联网一样的道理吗?用串口和用socket唯一的区别就是,前者将原始IP数据写进了串口,而后者将原始IP数据写入socket,在应用程序看来,这没什么不同,都是写入了一个文件而已。之所以可以这么理解,得益于UNIX的两个传统,一个是IO的文件本质,一切皆文件,读写文件即可,第二个是分层的协议栈模型,这使得Overlay成为了可能!
       好吧,我来总结一下,其实没什么好总结的,几乎都一样:
串口联网:IP字符设备fd<--->APP<--->串口字符设备
OpenVPN:IP字符设备fd(tun/tap)<--->OpenVPN<--->socket
本文范例:IP字符设备fd(tun/tap)<--->netcat/ncat/ssh<--->socket

大家都一样,最终大家都回归到了最开始的状态!中间的IPSec之流实乃昙花一现也。
       鉴于此,我将我本文的简单返璞归真的方法自诩为另一种革命,承接于IPSec,OpenVPN,BadVPN。如果你能玩的得心应手,就可以出神入化,随心所欲了,你的VPN数据将不再建立在协议的基础上,它甚至没有自己的协议,完全传输裸数据,因此也就没有任何的可以识别的Pattern,爽爆!
       不管是用USB联网,还是用串口联网,或者用任意APP上网,我们都要记住,这完全得益于一切皆文件以及分层协议栈的馈赠啊。你和想建立VPN连接的主机之间,无非有两种连接方式,第一种就是物理直连,即通过网线,串口线,USB线,无线等连接起来的,另一种就是逻辑连接,即虽然不是物理介质之间连接的,但却是可以通过TCP/IP协议栈连接在一起的,比如用一对socket就能互相通信。不管以上哪一类,VPN连接在乎的是只要能和对端保证TCP/IP可达即可。
       推荐一本书,《全球城市史》,今天周六刚刚看完,虽然篇幅比较短小,但确实很好看,从这本书里思考,你可以看到我本文一样的观点。古代大家自给自足,都待在家里做工,后来大家集中式地先后走向工厂,走向写字楼,走向园区,如今随着交通和通讯技术越来越发达,大家再次回到了家里工作,同样都是在家工作,但层次却完全不同,可能在未来几十年,我们会再次回到男耕女织的生活,但是和古代的男耕女织却完全不同,古代的男耕女织是无组织的个体行为,而未来的男耕女织却是一个整体组织中的一环...以前,没有网线,网卡匮乏,大家用串口,USB,鼠标线等做Overlay,如今网络资源过剩,大家用TCP/IP做Overlay...网络技术在更高的层次上实现了回归。
       想象一下大型机工作站时代,大家通过哑终端接入这些超级计算机,自己的终端可能离计算机很远,终端仅仅提供输入输出功能,根本没有计算功能,慢慢的计算资源逐渐移到了终端,这便开启了个人计算机时代,接下来,随着计算需求的增加,计算资源有一次集中了,这次不叫大型机了,这次叫云端,同样的,云计算也是在更高的层次实现了回归。

后记

大早上起来写完了本篇,终于有空写点题外话了。这次聊点热门话题,徐晓东。
       今日关于徐晓东的话题日渐沸腾,当然如果说他是为了炒作,那么他已经成功了。但我并不管他是不是炒作,打心眼里,我非常挺他,如果他内心在本质上是个好人的话,我倒是感觉我和他挺像。
       我不打假,我没有那个义务去做扫垃圾的工作,但我一直都很质疑权威,比如说我总是会问一些比较极端的问题,“让谭浩强写一个快排能一次成功吗?”,“阿里巴巴最牛逼的网络专家面对一个我曾经碰到的TS问题,能比我更快的速度搞定吗?”,“那些出书的,演讲的,号称某某领域第一人的,真让他设计个框架写段代码,能行吗?”...不要跟我说什么“架构师,技术总监站在更高的层次上是把握全局的,不干这些底层的活儿”之类云云,如果站在更高的层次,那就别天天说自己在底层领域是专家可OK?在我的眼里,我能干的活儿,任何人只要能干,我就服,不能干但是显得能干,我就鄙视,其实我一直都想恶作剧般的将这类所谓的专家揭穿,然而不幸的是,这类人总是有护身大法,仅仅依靠舆论你就近不了他身!人家不跟你打,你能怎样。
       不管是练太极的,唱歌的,演戏的,编程的,炼钢的,任何领域都有那么几顶光环,只要扣在谁头上就难以再摘下来。这有点像《七龙珠》里撒旦的那个光环,我要是孙悟空或者贝吉塔,我会把他打出屎然后让他亲自告诉世界谁是老大,然而,真正有实力的老大往往并不在乎这些所谓的光环,另外,孙悟空还让自己的儿子娶了撒旦的女儿...真正的王者!而我不是这样的王者,我最多只是个鼓手,鼓噪者。徐晓东也不是,他自己承认自己只是高端的业余水平。另外还有一个人,那就是魔岩三杰里的何勇,其实论唱功,也很一般。
       上世纪90年代香港红磡摇滚中国乐势力演唱会的记者采访,何勇说四大天王里除了张学友还算是个唱歌的,其他的都是小丑...把鼻屎抹到了正在拍摄的镜头上,并说自己就是最大的垃圾...我并不会去想何勇有何胆量说这番话,因为换成我的话我也会说,相反我一直在想,那么年代为什么就没有人说何勇炒作,他连几乎所有人都为之疯狂的三大天王都恶损了,为什么没有人站出来为三大天王说话,从而痛斥何勇,没有这样的人,一个都没有,然而进入互联网时代后,再搜何勇的这个话题,很多人都在骂何勇。
       成也互联网,败也互联网,不过何勇早就不在乎。我自打小学的时候就喜欢上了魔岩三杰,一直到现在,那绝对是一个纯粹的年代,没有互联网里充斥的如此多的垃圾!
       如今,徐晓东最开始只是干了一件跟当年何勇一样的事情,不同的是,这次是动了中华的真根!随后的舆论炮火般扫向徐晓东,说什么钓鱼岛,特务,假学历,非法经营...敢问这些跟能不能打有关吗?挑战一个领域的高端就一定先要面对道德绑架吗?展开点说,现在还有大量的人骂杨振宁的,说什么他不爱国,老了来中国圈钱来了,比不上邓稼先...敢问科学无国界这句话分量几何?领域专家专心做好本职,不谈政治,不谈道德,不要被某种文化所蕴含的道德所绑架就好了。
下面说说马云。
马云站出来说什么太极多么多么玄妙,说什么自己在商业上也借鉴了太极的思想,说什么要活得久就不动,要活的好多动,太极慢动,活的久又活的好!马云文字游戏了得啊!人家徐晓东根本就没考虑那么多,他只是质问太极能不能打,就这么简单,扯什么商业,扯什么思想,就是打,谁打赢了算谁的,很简单。人家徐晓东并没有说太极完全无用,在健身,养生方面的功效人家也没谈多少,他只是在谈能不能打的问题,马云这番有点掩饰的含义了,还说什么自己21岁以后就没打过架,就他那体格,要不是有钱有保镖,80%的普通人都能撂翻他。
       这马老板就是嘴皮子太厉害,别人又打不到他才敢狂妄,完全不如BAT里另一个马老板。人家马化腾自身就把自己定位为一个企业家,做好本职,把腾讯搞得越来越好,为员工谋福利,这已经可以征服很多人了,不管是用户还是员工。马化腾会去聊什么太极,打架之类的吗?应该不会,这不是他的范畴。如果哪天马化腾看上徐晓东了,掷出千金聘其为保镖,第二天来上班即可。每个人都有自己专业的领域,马云一个大老板,偏偏什么事情都要掺和一下,以前的环境污染问题,如今连这种民间打架私斗都要掺和,难免让人觉得脆弱。真心希望如此捧太极拳的马云上去跟那个徐晓东去打一场,看看自己的商业思路怎么KO掉对方的肌肉和力量(公平对打,不能以利益相逼相诱)。
-----------------
我也觉得,骗子太多了。绝对有必要揭穿他们。但是徐晓东太少了。就算他是个莽夫,一心博出名,即便他也是个骗子,但他的出发点是好的,以一个虚伪者上位搬倒若干骗子,收益大大的。一个骗子搬倒十个骗子,值了。能不能打是本质话题,别的不谈,不要去扯什么钓鱼岛啥的,就是打,我是希望徐晓东继续打下去并且赢的。在这阵风刮过去后,中医,养生这类就要迎接挑战了。

       说不定哪天,温州老板约战吴恩达,来个AI程序怎么样?于是在吴恩达的光环被摘去后,温州智能皮鞋名扬天下。温州皮鞋,可以监控下雨。

       人在出了名之后,能力会瞬间快速减退,为了保持文明的进步,必然需要草根屌鞭专家这种事情频繁发生!

3 0
原创粉丝点击