Unix Like, 操作系统, 网络

QoS —— 交互式应用最低延迟,HTTP(S)、IMAP等常用服务优先,迅雷、P2P受限

前言

使用迅雷全速下载时,其余网络应用的体验几乎约等于断网状态。

全速下载时,迅雷并没有占用太多上行带宽,于路由器上观察上行速率,同样正常:

可见全速下载时出现的现象,问题在于下行。

迅雷全网搜索资源,这意味着会有许多来自不同地方的计算机,以高速率往我的计算机发来大量数据包。ISP处的队列早已被这些P2P数据包塞满,而SSH、网页等服务的数据包速率不高,根本不够P2P数据包竞争,这些数据包到达ISP处时,只能由于队列已满而被丢弃。

 

其实这个问题,并没有什么好的解决办法,因为ISP处的队列我们没有权限控制。

我们所能做的,只是控制P2P的速率,避免因ISP处队列满而使得数据包被丢弃。

不过,上行队列我们拥有完全控制权,倒可以自由规划。

蓝图

首先,我的主要目的是,当私有网络内有人使用迅雷等进行全速下载时,这个下载不会影响交互式应用、网页流量等,不受影响终端的包括下载者自己,其次,也要保证下载流的下行速率。

这是一项很有挑战性的任务。

下行流量大致可以分为三类:

  • 最低延迟
  • 优先传送
  • 受限

这里没有普通流量这一类,因为普通流量在P2P软件,尤其是迅雷面前尤其不好区分,我尝试过Deep Packet Inspection,迅雷的流量绝大部分被分类到了Other里面。因此,与其花心思、花系统资源去区分这些“普通流量”,还不如直接使用白名单机制,把最低延迟、优先传送的区分出来。

上行流量:

  • 最低延迟
  • 优先传送
  • 普通
  • 受限

最低延迟

最低延迟的数据包,可能会以IP Header中的TOS来标记,但抓包分析发现,基本上没有使用此字段的数据包,甚至是SSH(但内网的SSH有把TOS设为0x10,表示最低延迟,可能是公网有路由清空了该字段)。所以说,只有KCPTUN之类的会“滥用”该字段,使用该字段区分最低延迟数据包意义不大。

TCP三次握手比较关键的是SYN、SYN & ACK,带有SYN和SYN & ACK标记的我们可以归类为最低延迟,此外ACK的小包(小于64字节),我们也可以归类为最低延迟。

SSH,DNS,远程桌面,QQ以及微信等交互式服务,归类为最低延迟。

优先传送

以下协议归类为优先传送:

  • HTTP
  • HTTPS
  • SMTP
  • IMAP
  • GIT

这里有个问题,如果使用迅雷进行HTTP下载,迅雷也会全网搜索HTTP资源,这里正常的HTTP流量与迅雷的HTTP流量不是很好区分。

一个最大的特征是,迅雷可能会与同一个IP建立多个TCP连接,据此可把迅雷下载用的TCP连接归类到受限。

对于单连接的,这里没有好的办法,有以下几个原因:

根据速率区分:前面已提到迅雷会全网搜索资源,意味着迅雷会构建很多TCP连接,单个TCP连接的速率不会很高。阈值低了,影响正常网页流量,高了,无效。

根据传输流量区分:首先HTTP 1.1支持Keep Alive,这意味着对于正常网页的TCP连接来说,流量达到阈值也是有可能的。

上述二者结合:如果我一个网站同时打开多几个页面,岂不是一样被归类为受限?况且现在一个页面数MB,也很正常吧?

根据内网IP构建的TCP连接数区分:我的目的是不要让迅雷影响正常的应用,如果让某个TCP连接数多的IP归类到受限,岂不是不能实现我这个目的?

普通

本分类仅用于上行,未被归类的均归于普通类。

受限

对于下行,未被归类的,均为受限。

P2P的上行发送的都是大于1350字节的UDP数据包,把这些数据包归类为受限即可。

实现

我使用的是中国电信的带宽

下行:100 Mbps,上行:20 Mbps。

互联网接口:eth0,私有网络接口:switch0。

 

首先设置以下数个变量,方便后面的命令使用:

这里我主要的目的在于不要让P2P把ISP处的队列占满,其次对于正常的流量,并不会占用太多带宽,因此正常流量不作速率限制,仅分配不同的优先级,而P2P则限制速率,设置最低的优先级。

区分P2P上行

把大于1350字节的UDP数据包,都标记为100。

区分迅雷TCP流

P2P的不需要区分,因为下行使用的是白名单机制。

主要问题在于迅雷的HTTP下载,实难区分,这里用了一个不太尽人意的方法,把迅雷的TCP流标记为200:

首先,不能在互联网接口上对TCP流进行归类,因为在互联网接口上,所有TCP流的目的地都是我们互联网接口的IP,这导致的问题就是无法独立统计单个内网IP对单个远程IP的链接数。想一想,如果网内有多个人同时访问某个网站,是不是有可能被归类为受限?

因此这里在私有网络的接口上进行归类。

归类用到了两个模块,分别是connlimit,hashlimit。

connlimit模块仅能统计单端的链接数,hashlimit模块能双端统计,但是只能统计速率,因此配合使用,以接近限制端对端链接数的效果。

这里connlimit用到的参数,表示远程一个/24与本地构建的TCP流超过四的情况;hashlimit的参数表示远程一个/24与本地一个IP突发5MB后,数据传输速率>512kb/s的情况。

这里还用到了connmark模块,这个模块用于记录TCP流的标记,下次遇到相同的TCP流时,取上次的标记即可,无需再使用connlimit与hashlimit模块进行判断,因为这两个模块资源消耗不低。

构建上行规则

这里使用了HTB对速率进行限制,并在每个类下面安置了一个SFQ队列,以公平分配一个队列中各流的带宽:

接下来就把数据包归类,我选择使用u32选择器。

u32选择器强大,同时也难用。

u8, u16都会转换为u32,uX跟着一个值与掩码,at后面是偏移量,偏移从IP Header开始。

掩码将会与数据包偏移位置处进行位于,再与给定的值进行比较。

更多u32选择器的细节,见u32选择器的手册页:Universal 32bit classifier in tc(8) LinuxUniversal 32bit classifier in tc(8)

分类规则:

构建下行规则

队列的构建与上面同理,只是少了一个普通流量的类:

归类:

完整的Script

总结

对P2P的归类效果还是非常好的:

1:1是总的下行,1:100是P2P的类别的下行。

P2P上行也很精确地限制在了1 Mbps:

要对迅雷的全网HTTP资源多线程下载完全正确归类可能做不到,但IP对IP流量大,高数据包速率的都能受限,正常的网页流量,交互式应用受到的影响不会太大。

下行速率的控制会不太准确,毕竟我们无法控制远程计算机给我们发包的速率,需要根据实际情况适当减少rate的值。

对于延迟,就比较难保证了,这依赖于ISP的队列规则,即使我们尽量做到不让ISP处丢包,但大量的数据包排队还是会导致其余应用的延迟增加。我这里把P2P的下行速率限制为80 Mbps,P2P全速下载时,延迟也只是增加了5 – 10 ms,可以接受,ISP处的队列比较合理。