了解 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 管理标记后的数据包。

相关推荐
物理与数学19 小时前
Linux 内核 TLB 优化
linux·linux内核
啟明起鸣19 小时前
【Linux 项目管理工具】GDB 调试是现成 C/C++ 项目的 “造影剂”,用来分析项目的架构原理
linux·c语言·c++
物理与数学19 小时前
linux 交换分区(Swap)
linux·linux内核
南工孙冬梅19 小时前
【久久派】Linux 文件系统制作配置 基于buildroot
linux
宴之敖者、19 小时前
Linux——指令(下)
linux
抠脚学代码19 小时前
Qt与Linux
linux·数据库·qt
Code Warrior19 小时前
【Linux】多路转接poll、epoll
linux·服务器
跃渊Yuey19 小时前
【Linux】Linux进程信号产生和保存
linux·c语言·c++·vscode
CaspianSea20 小时前
清理 Ubuntu里不需要的文件
linux·运维·ubuntu
c++逐梦人20 小时前
命令⾏参数和环境变量
linux·操作系统·进程