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

相关推荐
食咗未2 小时前
Linux USB HOST EXTERNAL VIRTUAL COM PORT
linux·驱动开发
没有啥的昵称2 小时前
linux下用QLibrary载入动态库
linux·qt
飞Link2 小时前
【CentOS】Linux(CentOS7)安装教程
linux·运维·服务器·centos
知识分享小能手2 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04中的过滤器知识点详解(13)
linux·学习·ubuntu
牛奔3 小时前
Linux 的日志分析命令
linux·运维·服务器·python·excel
飞Link3 小时前
【Linux】Linux(CentOS7)配置SSH免密登录
linux·运维·服务器
飞Link3 小时前
【Java】Linux(CentOS7)下安装JDK8(Java)教程
java·linux·运维·服务器
努力的小帅3 小时前
Linux_进程信号(Linux入门到精通)
linux·信号处理·信号捕捉·进程控制·linux入门
秋4273 小时前
ansible剧本
linux·服务器·ansible