大家好!我是大聪明-PLUS!
本文将介绍如何使用 C/C++ 中的 libnl 库实现交通控制实用程序命令。
让我们来看以下命令:
`tc qdisc add dev lo root handle `1`: htb default `20`
tc class add dev lo parent `1`: classid `1`:1 htb rate 10240kbit
tc class add dev lo parent `1`:1 classid `1`:10 htb rate 100kbit ceil 10240kbit prio `1`
tc class add dev lo parent `1`:1 classid `1`:20 htb rate 100kbit ceil 10240kbit prio `2`
tc filter add dev lo parent `1`: protocol ip prio `1` u32 match ip dst `127`.0.0.1/32 flowid `1`:10`
这些命令创建了一个根 HTB 规则(整形器),用于均衡通过环回接口的数据传输速率(默认值 20 表示所有未分类流量都将被分配到 20 类)。两个具有不同优先级的类别将从根整形器继承。一个过滤器会将目标 IP 地址对应于 127.0.0.1/32 的所有流量分配到第一个类别。

我们来看看如何使用 libnl 库来实现这些命令。为此,您需要:
-
创建根基纪律(htb)
-
创建一个继承自根学科的父类(htb),并设置速度均衡阈值。
-
创建两个子类,一个用于由过滤器分类的数据包,另一个用于未分类的数据包。
-
创建过滤器以对数据包进行分类。
为了加强纪律,我们实施了以下功能add_qdisc_htb
void` `add_qdisc_htb`(`struct` `rtnl_link` `*link`, `uint32_t` `parent_handle`, `uint32_t` `handle`, `struct` `nl_sock*` `sock`, `uint32_t` `default_class`) {
`int` `err`;
`struct` `rtnl_qdisc` `*qdisc`;
`qdisc` `=` `rtnl_qdisc_alloc`();
`rtnl_tc_set_link`(`TC_CAST`(`qdisc`), `link`);
`rtnl_tc_set_parent`(`TC_CAST`(`qdisc`), `parent_handle`);
`rtnl_tc_set_handle`(`TC_CAST`(`qdisc`), `handle`);
`err` `=` `rtnl_tc_set_kind`(`TC_CAST`(`qdisc`), `"htb"`);
`throw_err`(`err`);
`err` `=` `rtnl_htb_set_defcls`(`qdisc`, `default_class`);
`throw_err`(`err`);
`err` `=` `rtnl_qdisc_add`(`sock`, `qdisc`, `NLM_F_CREATE`);
`throw_err`(`err`);
`rtnl_qdisc_put`(`qdisc`);
}`
其中rtnl_link *link,是绑定到接口的结构,uint32_t parent_handle是父类,uint32_t handle是当前类(tc 实用程序命令中的所有类标签均以十六进制指定),struct nl_sock* sock是 Linux 内核路由子系统要使用的套接字,uint32_t default_class是未分类数据包将发送到的默认类。
为了添加类,我们实现了该函数。add_htb_class
void` `add_htb_class`(`struct` `rtnl_link` `*link`, `uint32_t` `parent_handle`, `uint32_t` `handle`, `struct` `nl_sock*` `sock`,
`uint32_t` `rate`, `uint32_t` `ceil` `=` `0`, `uint32_t` `prio` `=` `0`, `uint32_t` `quantum` `=` `0`) {
`int` `err`;
`struct` `rtnl_class` `*cl`;
`cl` `=` `rtnl_class_alloc`();
`rtnl_tc_set_link`(`TC_CAST`(`cl`), `link`);
`rtnl_tc_set_parent`(`TC_CAST`(`cl`), `parent_handle`);
`rtnl_tc_set_handle`(`TC_CAST`(`cl`), `handle`);
`err` `=` `rtnl_tc_set_kind`(`TC_CAST`(`cl`), `"htb"`);
`throw_err`(`err`);
`err` `=` `rtnl_htb_set_rate`(`cl`, `rate`);
`throw_err`(`err`);
`if` (`ceil`) {
`err` `=` `rtnl_htb_set_ceil`(`cl`, `ceil`);
`throw_err`(`err`);
}
`if` (`prio`) {
`err` `=` `rtnl_htb_set_prio`(`cl`, `prio`);
`throw_err`(`err`);
}
`if` (`quantum`) {
`err` `=` `rtnl_htb_set_quantum`(`cl`, `quantum`);
`throw_err`(`err`);
}
`err` `=` `rtnl_class_add`(`sock`, `cl`, `NLM_F_CREATE`);
`throw_err`(`err`);
`rtnl_class_put`(`cl`);
} `
为了添加过滤器,我们实现该函数add_u32_filter_32key
void` `add_u32_filter_32key`(`struct` `rtnl_link` `*link`, `uint32_t` `handle`, `uint32_t` `flowid`, `struct` `nl_sock*` `sock`, `int` `prio`, `u32key` `key`) {
`struct` `rtnl_cls` `*filter`;
`int` `err`;
`filter` `=` `rtnl_cls_alloc`();
`rtnl_tc_set_link`(`TC_CAST`(`filter`), `link`);
`rtnl_tc_set_parent`(`TC_CAST`(`filter`), `handle`);
`err` `=` `rtnl_tc_set_kind`(`TC_CAST`(`filter`), `"u32"`);
`throw_err`(`err`);
`rtnl_cls_set_prio`(`filter`, `prio`);
`rtnl_cls_set_protocol`(`filter`, `ETH_P_IP`);
`err` `=` `rtnl_u32_add_key_uint32`(`filter`, `key`.`value`, `key`.`mask`, `key`.`offset`, `key`.`keyoffmask`); `// правило для совпадение пакета`
`throw_err`(`err`);
`err` `=` `rtnl_u32_set_classid`(`filter`, `flowid`);
`throw_err`(`err`);
`err` `=` `rtnl_u32_set_cls_terminal`(`filter`);
`throw_err`(`err`);
`err` `=` `rtnl_cls_add`(`sock`, `filter`, `NLM_F_CREATE`);
`throw_err`(`err`);
`rtnl_cls_put`(`filter`);
}`
u32key 结构包含四个字段,用于对数据包进行分类。我们以 tc 命令为例:tc filtertc filter add dev lo parent 1: protocol ip prio 1 u32 match ip dst 127.0.0.1/32 flowid 1:10 value 字段对应于 IP 地址,mask 字段对应于 IP 地址掩码,offset 字段对应于相对于 IP 数据包起始位置的偏移量(因为协议是 IP),在本例中即为方向,keyoffmask 字段是偏移量的掩码。
struct` `u32key` {
`uint32_t` `value`;
`uint32_t` `mask`;
`int` `offset`;
`int` `keyoffmask`;
};`
实现一个main使用上述所有函数的函数。
int` `main`() {
`struct` `nl_cache` `*cache`;
`struct` `rtnl_link` `*link`;
`struct` `nl_sock` `*sock`;
`int` `if_index`;
`sock` `=` `nl_socket_alloc`();
`nl_connect`(`sock`, `NETLINK_ROUTE`);
`rtnl_link_alloc_cache`(`sock`, `AF_UNSPEC`, `&cache`);
`link` `=` `rtnl_link_get_by_name`(`cache`, `"lo"`);
`struct` `rtnl_qdisc` `*qdisc`;
`if` (`!`(`qdisc` `=` `rtnl_qdisc_alloc`())) {
`std::runtime_error`(`"Can not allocate Qdisc"`);
}
`rtnl_tc_set_link`(`TC_CAST`(`qdisc`), `link`);
`rtnl_tc_set_parent`(`TC_CAST`(`qdisc`), `TC_H_ROOT`);
`//Delete current qdisc`
`rtnl_qdisc_delete`(`sock`, `qdisc`);
`free`(`qdisc`);
`//tc qdisc add dev eth1 root handle 1: htb default 20`
`add_qdisc_htb`(`link`, `TC_H_ROOT`, `TC_HANDLE`(`0x1`, `0`), `sock`, `TC_HANDLE`(`0`, `0x20`));
`//tc class add dev eth1 parent 1: classid 1:1 htb rate 10240kbit`
`add_htb_class`(`link`, `TC_HANDLE`(`0x1`, `0`), `TC_HANDLE`(`0x1`, `0x1`), `sock`, `1250000`);
`//tc class add dev eth1 parent 1:1 classid 1:10 htb rate 7168kbit`
`add_htb_class`(`link`, `TC_HANDLE`(`0x1`, `0x1`), `TC_HANDLE`(`0x1`, `0x10`), `sock`, `12500`, `1250000`, `1`);
`//tc class add dev eth1 parent 1:1 classid 1:20 htb rate 3072kbit ceil 10240kbit`
`add_htb_class`(`link`, `TC_HANDLE`(`0x1`, `0x1`), `TC_HANDLE`(`0x1`, `0x20`), `sock`, `12500`, `1250000`, `2`);
`u32key` `key`;
`inet_pton`(`AF_INET`, `"127.0.0.1"`, `&`(`key`.`value`));
`inet_pton`(`AF_INET`, `"255.255.255.255"`, `&`(`key`.`mask`));
`key`.`value` `=` `ntohl`(`key`.`value`);
`key`.`mask` `=` `ntohl`(`key`.`mask`);
`key`.`offset` `=` `16`; `// OFFSET_DESTINATION`
`int` `prio` `=` `1`;
`//tc filter add dev lo parent 1: protocol ip prio 1 u32 match ip dst 127.0.0.1/32 flowid 1:10`
`add_u32_filter_32key`(`link`, `TC_HANDLE`(`0x1`, `0`), `TC_HANDLE`(`0x1`, `0x10`), `sock`, `prio`, `key`);
`return` `0`;
}`
在使用库的过程中,我注意到以下几点:按 vlan_id 过滤需要 802q1 协议;u32 过滤器不支持按 802q1 帧字段进行流量分类。如果您直接使用 tc 库而不使用 C++,则可以使用 tc flower 过滤器来实现 VLAN 过滤。遗憾的是,tc flower 过滤器在库中并未完全实现。因此,您可以使用防火墙(例如,支持 VLAN 的 nftables)标记数据包,然后通过 tc 管理标记后的数据包。