1.将熟知的P2P协议硬编码到系统进行识别;
2.使用一种统一的模糊识别方式来识别所有的P2P流量。
被应用的最多的是第一种方式,然而技术痴迷者却深爱着第二种方式。如果说就是想研究一种技术,那么第一种方式简直太没有技术含量了,如果说要想做一款产品,那么第一种方式无疑是最快的。
不要忘了,如今的互联网世界是一个内容的世界,技术黑客时代早就过去了。因此上述的第二种方式除了作为一种技术储备在各大研究机构比如大学,研究院,大企业(或许还有大量的刚毕业不到两年的程序员群体之间)风靡之外,已经没有多少“市场”了。因此最终,八成以上的研发机构会使用第一种方式。也许你会说,如果我用一种自定义的协议,也就是说它不是熟知的(比如BT)协议的话,那岂不是识别不了了吗?是的,是识别不了,但是别忘了识别P2P流量的目的,重申一遍,这不是一个仅以技术为支撑的世界。
之所以要限制P2P,是因为这些流量占据了大量的可用带宽,之所以能占据大量而不是少量的带宽,是因为用的人太多了的缘故,大家都知道,P2P网络在线的人越多,下载速度越快,这正和C/S模型的网络是相反的。如果你使用的是自定义的协议,那么其知名度肯定不高,使用的人也不会太多,自然也就不会占据大量带宽,那么此等流量放过又何妨,如果有一天,你的协议被世界所认可,那么它也就是成了熟知的协议,那么它自然会被加入到上述第一类P2P流量识别方式的识别库当中,就是这个看似矛盾的事实印证了第一种方式的可行性,对于这一点更深层次的含义,可以读一读关于“小世界”网络的一些资料,此处不再赘述。
内容左右技术的世界,我们需要做到的仅仅是90%,剩下的10%的缺失无伤大雅,正如不能因为一两次空难就把整架飞机完全按照黑匣子的标准来制造一样-那样飞机过重起飞及其耗能,任何领域都不是仅由技术支撑,技术只是手段,而非目的。
如果非要实现第二种方式,那么可能涉及到的就不仅仅是IT技术了,还需要深入到数学,统计学,逻辑学等学科,在所有的P2P协议中取一个交集,并且这个交集还不能过小,因此这个交集只能是统计意义上的,而不能是完全意义上的。
现行的中小型上网行为管理系统,无疑使用Linux的居多,在Liunx上,最快的做法就是使用iptables,那么对于P2P流量的识别,iptables又是怎么做的呢?毫无疑问,我们需要把熟知P2P协议库载入到内核中,而这些被包含在一个叫做ipp2p(可在http://www.ipp2p.org/downloads_en.html下载)的内核Netfilter补丁当中,打开此补丁,我们发现有一个ipt_ipp2p.c的文件,根据Netfilter的命名规则,我们知道这就是包含有match回调函数的文件,其中的match函数如下所示:
static int match(const struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const void *matchinfo, int offset, int *hotdrop) { ... switch (ip->protocol){ case IPPROTO_TCP: /*what to do with a TCP packet*/ { struct tcphdr *tcph = (void *) ip + ip->ihl * 4; ... haystack += tcph->doff * 4; /*get TCP-Header-Size*/ hlen -= tcph->doff * 4; //最关键的就是matchlist这个链表 while (matchlist[i].command) { if ((((info->cmd & matchlist[i].command) == matchlist[i].command) || ((info->cmd & matchlist[i].short_hand) == matchlist[i].short_hand)) && (hlen > matchlist[i].packet_len)) { p2p_result = matchlist[i].function_name(haystack, hlen); if (p2p_result) { return p2p_result; } } i++; } return p2p_result; } case IPPROTO_UDP: /*what to do with an UDP packet*/ { struct udphdr *udph = (void *) ip + ip->ihl * 4; //最关键的就是udp_list这个链表 while (udp_list[i].command){ if ((((info->cmd & udp_list[i].command) == udp_list[i].command) || ((info->cmd & udp_list[i].short_hand) == udp_list[i].short_hand)) && (hlen > udp_list[i].packet_len)) { p2p_result = udp_list[i].function_name(haystack, hlen); return p2p_result; } } i++; } return p2p_result; } default: return 0; } }我们看到了match函数遍历了两个链表,以第一个链表matchlist为例,它包含了所有的使用tcp协议的熟知的P2P协议过滤规则:
static struct { int command; __u8 short_hand; /*for fucntions included in short hands*/ int packet_len; int (*function_name) (const unsigned char *, const u16); } matchlist[] = { {IPP2P_EDK,SHORT_HAND_IPP2P,20, &search_all_edk}, {IPP2P_DC,SHORT_HAND_IPP2P,5, search_all_dc}, {IPP2P_GNU,SHORT_HAND_IPP2P,5, &search_all_gnu}, {IPP2P_KAZAA,SHORT_HAND_IPP2P,5, &search_all_kazaa}, {IPP2P_BIT,SHORT_HAND_IPP2P,20, &search_bittorrent}, {IPP2P_APPLE,SHORT_HAND_IPP2P,5, &search_apple}, {IPP2P_SOUL,SHORT_HAND_IPP2P,5, &search_soul}, {IPP2P_WINMX,SHORT_HAND_IPP2P,2, &search_winmx}, {IPP2P_ARES,SHORT_HAND_IPP2P,5, &search_ares}, {IPP2P_MUTE,SHORT_HAND_NONE,200, &search_mute}, {IPP2P_WASTE,SHORT_HAND_NONE,5, &search_waste}, {IPP2P_XDCC,SHORT_HAND_NONE,5, &search_xdcc}, {0,0,0,NULL} };其中的最后一个参数function_name为一个回调函数,其中识别了特定的P2P协议,因此,如果有一天,你需要增加一个以往不那么被人熟知但是现在却风靡的P2P协议的话,你只需要往matchlist增加一个元素即可。
以上只是内核中的规则,至于如何应用这些,那就是iptables和Netfilter的接口的事情了,太简单了,不再赘述。最终如果想禁止BT流量,那么:
iptables -A FORWARD -m p2p --p2p-protocol bittorrent -j DROP
即可。
对于P2P流量的识别,底层技术实际上很简单,然而作为一款产品,决不会是如此简单的,其复杂之处在于这些底层规则的“前端配置”,只要你实现一个足够灵活足够可用的前端配置程序,那么也就相当于完成了一个产品的雏形,正如游戏的复杂性不在渲染,不在如何驱使主体位移,而在前端交互一样的道理。
曾几何时,技术主导一切,那是黑客的天下,后来,应为为王,那是应用程序的天下,如今,内容成了最最主要的,我们不能忽略这个事实,数据采集的意义非凡,某种意义上,数据就是一切,它涵盖了上网行为,客户偏好,甚至从一个帐号的不同登录地点的数据采集都可以跟踪一个人的旅游信息。在这种背景下,再复杂,再智能的算法都敌不过数据本身,当然还需要深厚的统计学功底以及数据可视化技术。
最后,我们来看一下如何针对P2P流量进行分类
1.针对每一个进来的包,将连接跟踪的mark打到包上
iptables -t mangle -A PREROUTING -p tcp -j CONNMARK --restore-mark
2.如果发现包的mark不为0,则结束此表,通过,这一步可以使P2P包尽快通过mangle表,仅对一个流的头包进行ipp2p匹配
iptables -t mangle -A PREROUTING -p tcp -m mark ! --mark 0 -j ACCEPT
3.如果发现包的mark为0,说明要么这是一个流的头包,要么是不感兴趣包,则匹配是否是P2P流量,若是,打上mark 11
iptables -t mangle -A PREROUTING -p tcp -m ipp2p --ipp2p --bit --apple -j MARK --set-mark 11
4.如果发现包的mark不为0,说明第3步命中了,则将包的mark复制到连接跟踪的mark中
iptables -t mangle -A PREROUTING -p tcp -m mark ! --mark 0 -j CONNMARK --save-mark
5.在包离开本机时,根据包的mark进行分类
iptables -t mangle -A POSTROUTING -o eth0 -m mark --mark 11 -j CLASSIFY --set-class 1:11
流量已经分类了,至于做什么,由tc来控制吧。虽然上述的命令行很简单,然而想要做成一个普通管理员都可方便使用灵活配置的带界面的东西,可得下一番功夫。