优化上行带宽主要有两个基本步骤。首先,我们必须找到一种方法来阻止 ADSL 调制解调器排队数据包,因为我们无法控制它如何处理队列。为了做到这一点,我们将限制路由器发送到 eth0 的数据量,使其略低于 ADSL 调制解调器的总上行带宽。这将导致路由器必须排队从本地网络到达的数据包,因为这些数据包到达速度快于路由器允许发送的速度。
第二步是在路由器上设置优先级排队规则。我们将研究一种可以配置为优先处理交互式流量(例如 telnet 和多人游戏)的队列。
最后一步是配置防火墙以使用 fwmark 对数据包进行优先级排序。
尽管路由器和调制解调器之间的连接速度为 10Mbit/s,但调制解调器只能以 128kbit/s 的速度发送数据。任何超过该速率发送的数据都将在调制解调器处排队。因此,从路由器发送的 ping 数据包可能会立即到达调制解调器,但如果调制解调器中的队列中有任何数据包,则可能需要几秒钟才能实际发送到 Internet。不幸的是,大多数 ADSL 调制解调器没有提供任何机制来指定数据包如何出队或队列有多大,因此我们的首要目标是将出站数据包排队的位置移动到我们可以更好地控制队列的地方。
我们将通过使用 HTB 队列来限制我们向 ADSL 调制解调器发送数据包的速率来实现这一点。即使我们的上行带宽可能是 128kbit/s,我们也必须将发送数据包的速率限制为略低于该速率。如果我们想降低延迟,我们必须确保没有一个数据包在调制解调器处排队。通过实验,我发现将出站流量限制在约 90kbit/s 可以让我获得几乎 95% 的带宽,而无需 HTB 速率控制。在此速率下启用 HTB 后,我们已经阻止了 ADSL 调制解调器排队数据包。
此时,我们仍然没有意识到性能的任何变化。我们只是将 FIFO 队列从 ADSL 调制解调器移动到了路由器。事实上,在 Linux 配置为默认队列大小为 100 个数据包的情况下,我们可能已经使我们的问题变得更糟!但这不会持续太久...
HTB 队列中的每个相邻类都可以分配一个优先级。通过将不同类型的流量放入不同的类,然后为这些类分配不同的优先级,我们可以控制数据包出队和发送的顺序。HTB 使这成为可能,同时仍然避免任何一个类别的饿死,因为我们能够为每个类别指定最小保证速率。除此之外,HTB 还允许我们告诉一个类别,它可以从其他类别使用任何未使用的带宽,直到达到某个上限。
一旦我们设置好类,我们就设置过滤器以将流量放入类中。有几种方法可以做到这一点,但本文档中描述的方法使用熟悉的 iptables/ipchains 来标记带有 fwmark 的数据包。过滤器根据数据包的 fwmark 将流量放入 HTB 队列的类中。这样,我们就可以在 iptables 中设置匹配规则,将某些类型的流量发送到某些类。
配置路由器以优先处理交互式流量的最后一步是设置防火墙,以定义应如何对流量进行分类。这是通过设置数据包的 fwmark 字段来完成的。
在不深入太多细节的情况下,以下是出站数据包如何分类为 4 个类的简化描述,其中最高优先级类为 0x00
将所有数据包标记为 0x03。默认情况下,这会将所有数据包放入最低优先级队列。
将 ICMP 数据包标记为 0x00。我们希望 ping 显示最高优先级数据包的延迟。
将所有目标端口为 1024 或更小的数据包标记为 0x01。这为系统服务(如 Telnet 和 SSH)赋予优先级。FTP 的控制端口也将落入此范围,但是 FTP 数据传输发生在较高端口上,并将保留在 0x03 波段中。
将所有目标端口为 25 (SMTP) 的数据包标记为 0x03。如果有人发送带有大型附件的电子邮件,我们不希望它淹没交互式流量。
将所有前往多人游戏服务器的数据包标记为 0x02。这将为游戏玩家提供低延迟,但会阻止他们淹没需要低延迟的系统应用程序。
将任何“小型”数据包标记为 0x02。应及时发送来自入站下载的出站 ACK 数据包,以确保高效下载。这可以使用 iptables length 模块实现。
显然,这可以根据您的需要进行自定义。
您还可以做两件事来改善延迟。首先,您可以将最大传输单元 (mtu) 设置为低于默认值 1500 字节。如果已经发送了一个完整的低优先级数据包,降低此数字将减少您必须等待发送优先级数据包的平均时间。降低此数字也会略微降低您的吞吐量,因为每个数据包都包含至少 40 字节的 IP 和 TCP 标头信息。
您可以做的另一件甚至可以改善低优先级流量延迟的事情是将队列长度从默认值 100 降低,在 ADSL 线路上的默认值 100 在 1500 字节 mtu 的情况下可能需要长达 10 秒才能清空。
通过使用中间排队设备 (IMQ),我们可以像排队出站数据包一样,通过队列运行所有入站数据包。在这种情况下,数据包优先级要简单得多。由于我们只能(尝试)控制入站 TCP 流量,我们将所有非 TCP 流量放入 0x00 类,并将所有 TCP 流量放入 0x01 类。我们还将“小型”TCP 数据包放入 0x00 类,因为这些数据包很可能是已发送出站数据的 ACK 数据包。我们将在 0x00 类上设置标准 FIFO 队列,并在 0x01 类上设置随机早期丢弃 (RED) 队列。RED 比 FIFO(尾部丢弃)队列更适合控制 TCP,因为它会在队列溢出之前丢弃数据包,以尝试减慢看起来即将失控的传输。我们还将两个类别的速率限制为某个最大入站速率,该速率低于您通过 ADSL 调制解调器的真实入站速度。
我们希望限制我们的入站流量,以避免填满 ISP 的队列,有时 ISP 的队列可以缓冲多达 5 秒的数据。问题是,目前限制入站 TCP 流量的唯一方法是丢弃完全正常的数据包。这些数据包已经占据了 ADSL 调制解调器上的一些带宽,但最终会被 Linux 盒子丢弃,以努力减慢未来的数据包。这些丢弃的数据包最终将被重新传输,消耗更多带宽。当我们限制流量时,我们限制的是我们将接受到我们网络中的数据包的速率。由于实际入站数据速率高于此速率,因为我们丢弃了数据包,因此我们实际上必须将下游限制为远低于 ADSL 调制解调器的实际速率,以确保低延迟。在实践中,我必须将我的 1.5mbit/s 下游 ADSL 限制为 700kbit/sec,才能在 5 个并发下载的情况下保持可接受的延迟。您拥有的 TCP 会话越多,您浪费在丢弃数据包上的带宽就越多,并且您必须设置的限制速率就越低。
控制入站 TCP 流量的更好方法是 TCP 窗口操作,但在撰写本文时,Linux 尚无(免费)实现(据我所知...)。