了解 Linux 系统中用于流量管理的 libnl 库

大家好!我是大聪明-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 库来实现这些命令。为此,您需要:

  1. 创建根基纪律(htb)

  2. 创建一个继承自根学科的父类(htb),并设置速度均衡阈值。

  3. 创建两个子类,一个用于由过滤器分类的数据包,另一个用于未分类的数据包。

  4. 创建过滤器以对数据包进行分类。

为了加强纪律,我们实施了以下功能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 管理标记后的数据包。

相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩3 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言