Разбираемся с библиотекой libnl в для управлением трафиком в Linux

—

от автора

В статье рассмотрим как реализовать команды утилиты traffic control  с помощью библиотеки libnl на с/c++. У libnl есть неплохая документация так же есть небольшое количество тестов. Которые помогают разобраться как работать с библиотекой.

Traffic control — утилита которая нужна для управления входящем и исходящим трафиком. С помощью tc возможно реализовывать политики QoS (Quality of Service), а именно выполнять SHAPING (выравнивание), SCHEDULING (приоритизация), POLICING (ограничение) и DROPPING (отбрасывание) трафика.

Рассмотрим следующие команды:

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 (шейпер) для выравнивания скорости передачи данных через интерфейс loopback (default 20 означает, что весь не классифицированный трафик будет отнесён к классу с меткой 20). Два класса с разными приоритетами, которые наследуются от корневого шейпера.  Фильтр который относит весь трафик у которого ip адресс назначения соответствует 127.0.0.1/32 к первому классу. Пример визуализации команд ниже, создано с помощью tcviz.

Рассмотрим как реализовать эти команды с помощью библиотеки 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); } 

аргументы функции аналогичны за исключением, что возможно установить минимальную гарантированную скорость для класса, максимальную полосу пропускания, приоритет, quantum который используется в DWRR

Для того чтобы добавить фильтр реализуем функцию 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 содержит 4 поля, по которым происходит классификация пакетов. Рассмотрим на примере команды 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 это маска для offset.

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++, то фильтрацию по vlan можно сделать через фильтр tc flower. К сожалению фильтр tc flower в библиотеке реализован не полностью. Поэтому можно маркировать пакеты фаерволом (например nftables, работает с vlan) и далее уже управлять маркированными пакетами через tc (пример).

Больше примеров использования библиотеки можно найти в моём репозитории на гитхабе. Ещё больше интересного в телеграм канале.


ссылка на оригинал статьи https://habr.com/ru/articles/877514/