Compare commits

...

10 Commits

Author SHA1 Message Date
Сергей Маринкевич fcdfc8d0c9 qosd: разбил слишком длинную строку 2025-10-08 18:51:16 +07:00
Сергей Маринкевич 14bb7bb9cf qosd: удалил специфичные параметры GRED
Т.к. абстрактные узлы всё-таки не задуманы быть полной копией Linux/TC,
то из WRED я удалил специфичные параметры Linux GRED, которые и так
можно узнать из дерева:

+ Кол-во узлов мы сможем посчитать.
+ Одередь по умолчанию мы получим из фильтров.
2025-10-08 18:51:16 +07:00
Сергей Маринкевич d3269cadd8 qosd: уточнил комментарии к параметрам узлов 2025-10-08 18:51:16 +07:00
Сергей Маринкевич b6ff5b79f9 qosd: прокинут kind до BaseNode
Теперь конкретные узлы не сами устанавливают себе `kind_`, а делают это
через `BaseNode` — единую точку входа.
2025-10-08 18:51:16 +07:00
Сергей Маринкевич 9b5996abb5 qosd: добавлены атрибуты узлов
Мне показалось, будет удобно использовать вложенную структуру в качестве
аргумента конструктора.
2025-10-08 18:51:16 +07:00
Сергей Маринкевич 9c32fb89b8 qosd: добавлены базовые дисциплины Complex QoS ESR
А именно:

+ [B|P]FIFO;
+ HTB;
+ RED;
+ WRED (a.k.a. `gred` в Linux/TC и нашем CLI);
+ SFQ.

Два замечания:

1. Вместо GRED и RED теперь WRED и RED: RED — как был, так и есть, а
   WRED — специальный узел, который несёт глобальные настройки GRED, но
   параметры RED на VQ задаются через подключение дочерних узлов RED.
2. Параметры RED для SFQ также были оптимизированы: в узле SFQ их не
   будет, но зато к SFQ можно подключить один дочерний узел. Если
   подключить туда RED, то настройки RED будут применяться для per-flow
   RED дисциплины SFQ.
2025-10-08 18:51:16 +07:00
Сергей Маринкевич d112a44c2c qosd: исправлен комментарий 2025-10-08 18:51:16 +07:00
Сергей Маринкевич e645fc3a28 qosd: обезопашен обход дерева с модификацией
Примеров итератора с доступом на чтение я много оставил, а пример
модификации дерева только один. И тот я сразу забыл перевести на
итераторы (range-based for loop).

Т.к. проход теперь не по вектору (он давал экземпляр умного указателя),
а по обычному указателю (собственному прокси, если точнее), то узел
разрушался уже в процессе его отключения от дерева. Добавил удержание
ссылки в сам `unlinkParent()` (ну, нам действительно может быть нужно
только безвозвратное удаление поддерева) и пример в `main.cpp` расширил
и прокомментировал.
2025-10-08 18:51:16 +07:00
Сергей Маринкевич 20a32885ff qosd: в миксины добавлен оператор конвертации в умный указатель
В отличие от метода `getNode()`, оператор конвертации будет публичным.
А ещё он позволяет преобразовывать объект неявно.
2025-10-08 18:51:16 +07:00
Сергей Маринкевич 4203ad712c qosd: создание узлов переведено на фабричный метод
Решил обойтись простым ~~советским~~ статическим методом:

	template <typename T>
	class FabricMixin {
	public:
		template <typename... Args>
		static std::shared_ptr<T> create(Args&&... args) {
			return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
		}
	};

Ну ладно, он не так просто выглядит на первый взгляд. Но, по сути, всё,
что он делает: параметризует метод типом возвращаемого указателя, и
передаёт все аргументы как есть в конструктор заданного типа. Решил
сделать так, чтобы не копипастить тело конструктора. Ну, вдруг я,
например, трассировку туда добавить захочу. Правда, есть недостаток у
такого решения:

	class SimpleNode : ...,
			   public FabricMixin<SimpleNode> {
		friend class FabricMixin<SimpleNode>;

Не очень удобное подключение: а) нужно внести по крайней мере две
строчки; б) автоматически самого себя параметром шаблона передавать
нельзя.

Вносить этот метод в `BaseNode` (или около) не хотел, чтобы не
пробрасывать оконечный тип по всей иерархии. Да и от указанных выше
проблем он не избавляет. Зато можно будет относительно безболезненно
выпилить этот класс, если ему подвернётся достойная замена.
2025-10-08 18:51:16 +07:00
23 changed files with 642 additions and 121 deletions
-1
View File
@@ -1,7 +1,6 @@
#pragma once
#include <memory>
#include <vector>
#include "ifaces/ILinkMixin.h"
/// \brief Интерфейс для классов-связей между элементами.
/// \tparam TElem Тип элемента, между которыми устанавливается связь.
+1
View File
@@ -11,6 +11,7 @@ public:
using ElemPtr = std::shared_ptr<TElem>;
virtual ~ILinkMixin() = default;
virtual operator std::shared_ptr<TElem>() = 0;
virtual void linkChild(const ElemPtr& child) = 0;
virtual void unlinkParent() = 0;
virtual const std::vector<ElemPtr>& children() = 0;
+3 -2
View File
@@ -3,9 +3,10 @@
#include <stdexcept>
/// \brief Связь для листового узла, не допускающая дочерних элементов.
class LeafLink : public BaseLink {
template <class TElem>
class LeafLink : public BaseLink<TElem> {
public:
using BaseLink::BaseLink;
using BaseLink<TElem>::BaseLink;
void addChild(const NodePtr&) override {
throw std::logic_error("LeafLink cannot have children");
}
+2 -12
View File
@@ -1,17 +1,7 @@
#pragma once
#include "links/BaseLink.h"
/// \brief Связь "один-ко-многим" между элементами одного типа.
/// Каждый дочерний элемент должен быть того же типа, что и родитель.
/// \brief Связь "один-ко-многим" между элементами любого типа.
/// \tparam TElem Тип элемента.
template <class TElem>
class OneToManyLink : public BaseLink<TElem> {
public:
OneToManyLink(std::shared_ptr<TElem> e) : BaseLink<TElem>(e) {}
void addChild(const std::shared_ptr<TElem>& child) override {
/* Each child must be exactly the same type as parent */
if (typeid(*child) != typeid(*this->owner_node_.lock()))
throw std::logic_error("Foundling child");
BaseLink<TElem>::addChild(child);
}
};
using OneToManyLink = BaseLink<TElem>;
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <stdexcept>
/// \brief Обёртка для связей, чтобы сделать их типизированными.
/// Типизированная связь позволяет подключать только узлы заданного типа.
/// \tparam TElem Тип элемента.
/// \tparam TBase Базовый Link. После проверки типа, управление передаётся этому классу.
/// \tparam TExpected Опциональный ожидаемый конкретный тип детей.
/// Если не задан, сравниваем с родителем.
template <class TElem, class TBase, class TExpected = void>
class TypedLink : public TBase {
public:
using ElemPtr = std::shared_ptr<TElem>;
TypedLink(ElemPtr e) : TBase(e) {}
void addChild(const ElemPtr& child) override {
/* Validate type according to policy */
if constexpr (std::is_void_v<TExpected>) {
/* Default behavior: child must be exactly the same type as parent */
if (typeid(*child) != typeid(*this->owner_node_.lock()))
throw std::logic_error("Foundling child");
} else {
/* Explicit expected child type */
if (typeid(*child) != typeid(TExpected))
throw std::logic_error("Unexpected child type");
}
TBase::addChild(child);
}
};
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "links/TypedLink.h"
#include "links/OneToManyLink.h"
#include <type_traits>
#include <typeinfo>
/// \brief Связь "один-ко-многим" между элементами одного типа.
/// Каждый дочерний элемент должен быть того же типа, что и родитель.
/// \tparam TElem Тип элемента.
/// \tparam TExpected Опциональный ожидаемый конкретный тип детей.
template <class TElem, class TExpected = void>
using TypedOneToManyLink = TypedLink<TElem, OneToManyLink<TElem>, TExpected>;
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "links/BaseLink.h"
#include <stdexcept>
/// \brief Связь "один-к-одному" между элементами заданного типа.
/// Позволяет добавить только одного дочернего элемента.
/// \tparam TElem Тип элемента.
/// \tparam TExpected Опциональный ожидаемый конкретный тип детей.
/// Если не задан, сравниваем с родителем.
template <class TElem, class TExpected = void>
using TypedOneToOneLink = TypedLink<TElem, OneToOneLink<TElem>, TExpected>;
+13 -1
View File
@@ -17,6 +17,10 @@ public:
Logger::get("ConDes").dbg("--- Destructor called for: BaseLinkMixin");
}
operator std::shared_ptr<INode>() override {
return this->getNode();
}
void linkChild(const ElemPtr& child) override {
getLink()->addChild(child);
@@ -33,7 +37,15 @@ public:
auto parentLink = parent->getLink();
parentLink->removeChild(getNode());
/* NOTE:
*
* Keep a reference to the node we gonna to unlink.
* Otherwise, we'll disappear between `removeChild` and
* `setParent`. Do not rearrange these calls, because
* we want to modify the tree top down.
*/
auto node = getNode();
parentLink->removeChild(node);
getLink()->setParent(nullptr);
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <memory>
template <typename T>
class FabricMixin {
public:
template <typename... Args>
static std::shared_ptr<T> create(Args&&... args) {
return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}
};
+3 -3
View File
@@ -2,7 +2,7 @@
#include <iostream>
#include "mixins/LazyLinkMixin.h"
#include <memory>
#include "links/OneToManyLink.h"
#include "links/TypedOneToManyLink.h"
#include "links/OneToOneLink.h"
#include "Logger.h"
@@ -34,10 +34,10 @@ protected:
if (typeid(*child) == typeid(*this)) {
Logger::get("Mixin").dbg("--- Mutate to OneToMany");
newLink = std::make_shared<OneToManyLink<TElem>>(this->getNode());
newLink = std::make_shared<TypedOneToManyLink<TElem>>(*this);
} else {
Logger::get("Mixin").dbg("--- Mutate to OneToOne");
newLink = std::make_shared<OneToOneLink<TElem>>(this->getNode());
newLink = std::make_shared<OneToOneLink<TElem>>(*this);
}
if (newLink && this->link_)
+1 -2
View File
@@ -31,8 +31,7 @@ public:
protected:
void lazyInit() {
if (!link_) {
link_ = std::make_shared<TLink>(
BaseLinkMixin::getNode());
link_ = std::make_shared<TLink>(*this);
}
}
LinkPtr link_;
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <cstdint>
#include "nodes/LeafNode.h"
#include "mixins/FabricMixin.h"
#include "Logger.h"
/// \brief Узел дисциплины BFIFO. Лист.
class BFIFONode : public LeafNode,
public FabricMixin<BFIFONode> {
public:
struct Config {
/// Размер очереди в байтах.
std::uint64_t limit = 0;
};
~BFIFONode() {
Logger::get("ConDes").dbg(std::string("--- BFIFO destructor called for: ") + name_);
}
const Config& config() const { return config_; }
private:
friend class FabricMixin<BFIFONode>;
BFIFONode(std::string&& name) : LeafNode(std::move(name), "BFIFO") {
Logger::get("ConDes").dbg(std::string("--- BFIFO constructor called for: ") + name_);
}
BFIFONode(std::string&& name, Config&& config) : LeafNode(std::move(name), "BFIFO"),
config_(std::move(config)) {
Logger::get("ConDes").dbg(std::string("--- BFIFO constructor called for: ") + name_);
}
Config config_{};
};
+5 -1
View File
@@ -7,9 +7,13 @@
/// Содержит имя и тип узла, реализует интерфейс INode.
class BaseNode : public virtual INode {
public:
BaseNode(std::string name) : name_(std::move(name)) {
/// \brief Конструктор узла.
/// \param name Неуникальное имя узла.
BaseNode(std::string name, std::string kind) :
name_(std::move(name)), kind_(std::move(kind)) {
Logger::get("ConDes").dbg(std::string("--- Base constructor called for: ") + name_);
}
/// \brief Неуникальное имя узла.
const std::string& name() const override { return name_; }
const std::string& kind() const override { return kind_; }
~BaseNode() {
+7 -3
View File
@@ -2,17 +2,21 @@
#include "nodes/BaseNode.h"
#include "mixins/HierarchicalLinkMixin.h"
#include "mixins/FabricMixin.h"
#include "Logger.h"
/// \brief Класс сложного (составного) узла дерева.
/// Может содержать несколько дочерних ComplexNode и один SimpleNode.
/// Может содержать несколько дочерних узлов такого же типа или один узел
/// отличного типа.
class ComplexNode : public BaseNode,
virtual public HierarchicalLinkMixin<INode> {
virtual public HierarchicalLinkMixin<INode> {
public:
~ComplexNode() {
Logger::get("ConDes").dbg(std::string("--- Complex destructor called for: ") + name_);
}
ComplexNode(std::string name) : BaseNode(std::move(name)) {
protected:
ComplexNode(std::string name, std::string kind) :
BaseNode(std::move(name), std::move(kind)) {
Logger::get("ConDes").dbg(std::string("--- Complex constructor called for: ") + name_);
}
};
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include <cstdint>
#include <string>
#include "nodes/ComplexNode.h"
#include "mixins/FabricMixin.h"
#include "Logger.h"
/// \brief Узел дисциплины HTB. Составной, допускает нескольких детей.
class HTBNode : public ComplexNode,
public FabricMixin<HTBNode> {
public:
struct Config {
std::uint64_t cir = 0; ///< Разрешённая полоса, CIR, бит/с.
std::uint64_t cburst = 0; ///< Токены для CIR, байты.
std::uint64_t pir = 0; ///< Допустимая полоса, PIR, бит/с.
std::uint64_t pburst = 0; ///< Токены для PIR, байты.
std::uint32_t prio = 0; ///< Приоритет класса.
std::uint32_t quantum = 0; ///< Квант DRR, байты.
/// \brief Поправка размера пакета, байты.
///
/// \note Вообще, этот параметр исторически взят с Linux/TC HTB.
/// И в таком контексте: речь **не** про STAB, а именно про
/// параметр класса HTB. Он, в отличие от STAB, не влияет на сам
/// размер пакета, а используется только для поправки шейпера
/// конкретно этого класса. Впрочем, в BC2 есть такая же местная
/// поправка для шейперов, см. FS 31.5.2.3 Packet Length Offset.
std::int32_t overhead = 0;
};
~HTBNode() {
Logger::get("ConDes").dbg(std::string("--- HTB destructor called for: ") + name_);
}
const Config& config() const { return config_; }
private:
friend class FabricMixin<HTBNode>;
HTBNode(std::string&& name) : ComplexNode(std::move(name), "HTB") {
Logger::get("ConDes").dbg(std::string("--- HTB constructor called for: ") + name_);
}
HTBNode(std::string&& name, Config&& config) : ComplexNode(std::move(name), "HTB"),
config_(std::move(config)) {
Logger::get("ConDes").dbg(std::string("--- HTB constructor called for: ") + name_);
}
Config config_{};
};
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include "nodes/BaseNode.h"
#include "mixins/LazyLinkMixin.h"
#include "mixins/FabricMixin.h"
#include "links/LeafLink.h"
#include "Logger.h"
/// \brief Базовый класс для краевых (листовых) узлов дисциплин.
/// Не допускает дочерних элементов.
class LeafNode : public BaseNode,
virtual public LazyLinkMixin<LeafLink<INode>> {
public:
~LeafNode() {
Logger::get("ConDes").dbg(
std::string("--- Leaf destructor called for: ") + name_
);
}
protected:
LeafNode(std::string name, std::string kind) :
BaseNode(std::move(name), std::move(kind)) {
Logger::get("ConDes").dbg(
std::string("--- Leaf constructor called for: ") +
name_ + ", kind=" + kind_
);
}
};
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <cstdint>
#include "nodes/LeafNode.h"
#include "mixins/FabricMixin.h"
#include "Logger.h"
/// \brief Узел дисциплины PFIFO. Лист.
class PFIFONode : public LeafNode,
public FabricMixin<PFIFONode> {
public:
struct Config {
/// \brief Кол-во пакетов.
std::uint32_t limit;
};
~PFIFONode() {
Logger::get("ConDes").dbg(std::string("--- PFIFO destructor called for: ") + name_);
}
const Config& config() const { return config_; }
private:
friend class FabricMixin<PFIFONode>;
PFIFONode(std::string&& name) : LeafNode(std::move(name), "PFIFO") {
Logger::get("ConDes").dbg(std::string("--- PFIFO constructor called for: ") + name_);
}
PFIFONode(std::string&& name, Config&& config) : LeafNode(std::move(name), "PFIFO"),
config_(std::move(config)) {
Logger::get("ConDes").dbg(std::string("--- PFIFO constructor called for: ") + name_);
}
Config config_{};
};
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include <cstdint>
#include "nodes/LeafNode.h"
#include "mixins/FabricMixin.h"
#include "Logger.h"
/// \brief Узел дисциплины RED. Лист; не допускает дочерних элементов.
///
/// \note Может быть подключен к:
/// + WRED для настройки VQ;
/// + SFQ для настройки per-flow RED.
class REDNode : public LeafNode,
public FabricMixin<REDNode> {
public:
struct Config {
std::uint64_t limit = 0; ///< Размер буфера, байты (обязателен).
std::uint64_t min = 0; ///< Нижняя граница для разметки, байты.
std::uint64_t max = 0; ///< Верхняя граница для разметки, байты.
std::uint32_t avpkt = 0; ///< Средний размер пакета, байты (обязателен).
std::uint64_t burst = 0; ///< Всплеск, байты.
double probability = 0.0; ///< Максимальная вероятность, 0..1.
std::uint64_t bandwidth = 0; ///< Скорость интерфейса, бит/с.
bool ecn = false; ///< Включить ECN-разметку вместо дропа.
bool harddrop = false; ///< Жёсткий дроп при превышении max.
bool nodrop = false; ///< Не дропать non-ECN-пакеты.
};
~REDNode() {
Logger::get("ConDes").dbg(std::string("--- RED destructor called for: ") + name_);
}
const Config& config() const { return config_; }
private:
friend class FabricMixin<REDNode>;
REDNode(std::string&& name)
: LeafNode(std::move(name), "RED") {
Logger::get("ConDes").dbg(std::string("--- RED constructor called for: ") + name_);
}
REDNode(std::string&& name, Config&& config)
: LeafNode(std::move(name), "RED"), config_(std::move(config)) {
Logger::get("ConDes").dbg(std::string("--- RED constructor called for: ") + name_);
}
Config config_{};
};
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <cstdint>
#include "nodes/BaseNode.h"
#include "mixins/LazyLinkMixin.h"
#include "mixins/FabricMixin.h"
#include "links/TypedOneToOneLink.h"
#include "nodes/REDNode.h"
#include "Logger.h"
/// \brief Узел дисциплины SFQ. Допускает одного ребёнка типа REDNode для настройки per-flow RED.
class SFQNode : public BaseNode,
virtual public LazyLinkMixin<TypedOneToOneLink<INode, REDNode>>,
public FabricMixin<SFQNode> {
public:
/// \note Параметры RED per-flow задаются через дочерний REDNode.
struct Config {
std::uint32_t limit = 0; ///< Суммарный лимит по всем потокам, пакеты
std::uint32_t depth = 0; ///< Лимит на один поток, пакеты
std::uint32_t divisor = 1024; ///< Размер хэш-таблицы (степень двойки)
std::uint32_t perturb = 0; ///< Период пертурбации, секунды
std::uint32_t flows = 0; ///< Число потоков
std::uint32_t quantum = 0; ///< Порция RR, байты
bool headdrop = false; ///< Отбрасывать из головы очереди
};
~SFQNode() {
Logger::get("ConDes").dbg(std::string("--- SFQ destructor called for: ") + name_);
}
const Config& config() const { return config_; }
private:
friend class FabricMixin<SFQNode>;
SFQNode(std::string&& name) : BaseNode(std::move(name), "SFQ") {
Logger::get("ConDes").dbg(std::string("--- SFQ constructor called for: ") + name_);
}
SFQNode(std::string&& name, Config&& config) : BaseNode(std::move(name), "SFQ"),
config_(std::move(config)) {
Logger::get("ConDes").dbg(std::string("--- SFQ constructor called for: ") + name_);
}
Config config_{};
};
+3 -1
View File
@@ -2,17 +2,19 @@
#include "nodes/BaseNode.h"
#include "mixins/LazyLinkMixin.h"
#include "mixins/FabricMixin.h"
#include "links/OneToOneLink.h"
#include "Logger.h"
/// \brief Класс простого (листового) узла дерева.
/// Может содержать только одного дочернего ComplexNode.
class SimpleNode : public BaseNode,
virtual public LazyLinkMixin<OneToOneLink<INode>> {
virtual public LazyLinkMixin<OneToOneLink<INode>>{
public:
~SimpleNode() {
Logger::get("ConDes").dbg(std::string("--- Simple destructor called for: ") + name_);
}
protected:
SimpleNode(std::string name) : BaseNode(std::move(name)) {
Logger::get("ConDes").dbg(std::string("--- Simple constructor called for: ") + name_);
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <cstdint>
#include <string>
#include "nodes/BaseNode.h"
#include "mixins/LazyLinkMixin.h"
#include "mixins/FabricMixin.h"
#include "links/TypedOneToManyLink.h"
#include "nodes/REDNode.h"
#include "Logger.h"
class WREDNode : public BaseNode,
virtual public LazyLinkMixin<TypedOneToManyLink<INode, REDNode>>,
public FabricMixin<WREDNode> {
public:
struct Config {
bool grio = false; ///< Наследование буферов (GRIO)
std::uint64_t limit = 0; ///< Глобальный лимит буфера, байты (необязателен)
bool ecn = false; ///< Включить ECN на дисциплине
bool harddrop = false; ///< Жёсткий дроп при превышении max
};
~WREDNode() {
Logger::get("ConDes").dbg(std::string("--- WRED destructor called for: ") + name_);
}
const Config& config() const { return config_; }
private:
friend class FabricMixin<WREDNode>;
WREDNode(std::string&& name) : BaseNode(std::move(name), "WRED") {
Logger::get("ConDes").dbg(std::string("--- WRED constructor called for: ") + name_);
}
WREDNode(std::string&& name, Config&& config) : BaseNode(std::move(name), "WRED"),
config_(std::move(config)) {
Logger::get("ConDes").dbg(std::string("--- WRED constructor called for: ") + name_);
}
Config config_{};
};
+116
View File
@@ -0,0 +1,116 @@
# Классы внутреннего представления
1. [[#HTB]];
2. [[#FIFO]] (хотя бы одну);
3. [[#RED]];
4. [[#GRED]];
5. [[#SFQ]].
## HTB
Иерархичный TBF. Имеет классы, к которым могут подключаться другие дисциплины. Т.е. к ней могут быть подключены несколько других узлов.
Из дисциплины:
+ `default` — Минорная часть ID класса по умолчанию. Dunno, надо ли нам такое вообще. Я об этом не думал. Сейчас у нас всё на фильтры `u32` завязано.
+ `r2q` — Кванты DRR (computed as rate in Bps/r2q). КМК, нам это не надо, мы этим никогда не пользовались. Единственное: мы раньше не пользовались DRR, а сейчас я хочу на этом построить Basic QoS. — **UPD:** Это можно в глобальной конфигурации задать.
+ `debug` — Какой-то "string of 16 numbers each 0-3", в душе не знаю, что это.
+ `direct_qlen` — Лимит прямой очереди в штуках пакетов. Мы этим вряд ли воспользуемся, т.к. у нас нет такого сценария, что какой-то пакет нужной пропускать мимо QoS. Ну, я таких сценариев не знаю. — **UPD:** В целом, можно вписать в мою архитектуру, если явно отобразить такой узел в дереве: будет класс, который матчит весь трафик (как в Complex QoS `class-default`), и в качестве дочернего узла у него некий DirectNode.
+ `offload` — Включение аппаратного QoS. У нас сейчас точно нигде поддержки нет. Вроде бы в новом SDK появилась какая-то поддержка на каких-то платформах Marvell. Для абстрактного дерева эта опция не подходит.
Из классов:
+ `rate` — Разрешённая полоса (можно заимствовать выше этой полосы). (обязательный)
+ `burst` — Кол-во токенов (байтов) TBF для этого класса.
+ `mpu` — "minimum packet size used in rate computations" — Мне кажется, что этот параметр вообще давно не работает: RTAB'ы померли, `iproute2` заполняет их только для обратной совместимости (актуальные ядра сразу делают на них `qdisc_put_rtab()`).
+ `overhead` — Поправка размера пакета для вычислений. Не путать со STAB'ом: здесь это атрибут класса HTB, и он не влияет размер пакета за пределами класса. По сути, это поправка к введённым `rate` и `ceil`.
+ `linklay` — Протокол передачи. Кроме Ethenet по умолчанию знаю только "e.g. atm". Бесполезно.
+ `ceil` — Допустимая полоса (заимствовать нельзя).
+ `cburst` — Аналог `burst` для `ceil`.
+ `mtu` — В дисциплине TBF это что-то вроде микро-burst, используемы для `peakrate`. Но здесь это такая же бесполезная вещь, как и `mpu`. По тем же причинам.
+ `prio` — Приоритет. Определённо надо.
+ `quantum` — Позволяет настроить *жадность* опустошения очередей в процессе DRR. По дефолту `r2q`. В целом, наверно, надо. Сейчас мы это не настраиваем, но, как я писал выше, для Basic QoS может пригодиться.
У этой дисциплины может быть лишь одна дочерняя дисциплина.
## FIFO
FIFO как настоящая очередь может быть только краевым узлом, то есть не имеет дочерних дисциплин и классов.
### PFIFO
Очередь с ограничением в штуках пакетов. Дисциплина по умолчанию для классов HTB. Параметры дисциплины:
+ `limit` — Размер очереди, кол-во пакетов (по умолчанию[^1] — `qlen` с интерфейса).
### BFIFO
Очередь с ограничением в байтах. Дисциплина по умолчанию для TBF. Параметры дисциплины:
+ `limit` — Размер очереди, байты (по умолчанию[^1] — `limit` с TBF).
[^1]: Вообще, для FIFO по умолчанию параметр `limit` равен `qlen` или `qlen * MTU`. Т.е., HTB вообще никакой параметр туда не передаёт, а TBF передаёт свой же `limit`, который проецирует на дочернюю дисциплину, если это FIFO (даже если руками новую FIFO создать).
## RED
Random Early Detection.
Может быть только краевым узлом.
+ `limit` — Размер буфера, байты. (обязательный, используем)
+ `min`, `max` — Границы для разметки пакетов: до минимума вообще не метим пакеты; после максимума вероятность метки максимальная. Заполненность очереди, байты. (используем)
+ `avpkt` — Средний размер пакета. Байты. Используется для каких-то хитрых вычислений. (обязательный, используем)
+ `burst` — Всплеск. Смысл такой же, как и везде, но как именно он работает — мне неясно. Факт в том, что чем выше `burst`, тем дольше до RED доходит, что нужно маркировать трафик. Байты. (используем)
+ `adaptive` — Какая-то умная шняга для автоматической подгонки параметров под текущее поведение трафика. Мы это не используем, кстати. Гля [[adaptiveRed.pdf]].
+ `probability` — Максимальная вероятность *помаркать* пакет. Число с плавающей точкой, континуум 0-1. (используем)
+ `bandwidth` — Не про шейпинг, но про скорость интерфейса, используется для расчёта средней длины очереди. Биты в секунду.
+ `ecn` — Включить именно маркировку пакетов, вместо отбрасывания, для сдерживания полосы потока. Пакет всё же будет отброшен, если длина очереди для этого потока превысит `limit`. Рекомендуемо, но мы этим не пользуемся.
+ `harddrop` — С этим параметром пакет будет отброшен, если длина очереди превысит `max`.
+ `nodrop` — С этим параметром дисциплина не будет отбрасывать пакеты, которые не может маркировать ("not ECN-capable"). По умолчанию такие пакеты дропаются.
Параметры, связанные с QEVENTS (`man 8 tc`), рассматривать точно не будем.
## GRED
А это, типа, "Generalized RED". Что бы это не значило. Мы это как WRED используем. Литературы по нему мало.
Тут вводится понятие "виртуальной очереди" (VQ). И к самому Linux TC оно отношения не имеет. Оно даже к очередям отношение имеет опосредованное: никакой очереди фактически не выделяется, поэтому она и *виртуальная*. И настройка этих очередей довольно виктимная: нужно послать `CHANGE` на уже созданную дисциплину и в качестве параметра передать номер очереди, настройки которой мы хотим изменить.
В этом проекте присутствует как узел WRED. Узел WRED несёт в себе только глобальные настройки дисциплины. Для конфигурирования VQ к узлу WRED прикрепляются узлы RED.
+ `vqs` — Кол-во виртуальных очередей. (обязательно) — **UPD:** Вообще, нам это не нужно: мы и так можем посчитать, сколько VQ мы определили, по самим дочерним узлам.
+ `default` — Виртуальная очередь по умолчанию. (обязательно) — **UPD:** Это тоже не нужно, т.к. мы фильтрами на самой очереди скажем, что она дефолтная.
+ `grio` — Вкл. наследование буферов. Условно, приоритетный трафик затрагивает, в том числе, счётчики менее приоритетного трафика.
+ `limit` — Размер буфера, байты. Здесь, в отличие от RED и VQ, является необязательным. Ограничивает очередь *мимо* VQ, а также задаёт верхнюю планку для каждой VQ (больше этого `limit` нельзя задавать `limit` для VQ).
+ `ecn` — Возможность включить маркировку пакетов на всю дисциплину, либо включение только для отдельной VQ. Вроде бы, и там, и тут включить нельзя. Кроме того, на 4.14, например, эти флаги на VQ вообще не используются.
+ `harddrop` — С этим параметром пакет будет отброшен, если длина очереди превысит `max`. Аналогично `ecn`.
Ну и далее для VQ аналогично RED:
+ `limit` — Размер буфера, байты. (обязательный, используем)
+ `min`, `max` — Границы для разметки пакетов: до минимума вообще не метим пакеты; после максимума вероятность метки максимальная. Заполненность очереди, байты. (используем)
+ `avpkt` — Средний размер пакета. Байты. Используется для каких-то хитрых вычислений. (обязательный, используем)
+ `burst` — Всплеск. Смысл такой же, как и везде, но как именно он работает — мне неясно. Факт в том, что чем выше `burst`, тем дольше до RED доходит, что нужно маркировать трафик. Байты. (используем)
+ `probability` — Максимальная вероятность *помаркать* пакет. Число с плавающей точкой, континуум 0-1. (используем)
+ `bandwidth` — Не про шейпинг, но про скорость интерфейса, используется для расчёта средней длины очереди. Биты в секунду.
## SFQ
Также может быть только краевым узлом.
Дисциплина работает по принципу "отобрать и поделить". Т.е. поток трафика на исходящем интерфейсе распределяется по множеству динамически создаваемых очередей на основании хэша пакета. Эти динамические очереди обслуживаются условным RR. За счёт такого подхода, гипотетически, никакая сессия не может монополизировать линк. Грубо говоря, социалистическая революция в рамках дисциплины на интерфейсе.
Несмотря на то, что у этой дисциплины при создании нет обязательных параметров, этих параметров всё же очень много:
+ `limit` — Кол-во пакетов, задерживаемое дисциплиной. Это суммарное ограничение по всем потокам.
+ `depth` — Кол-во пакетов, задерживаемое одним потоком.
+ `divisor` — Размер хэш-таблицы. Чем больше потоков дозволено, тем медленнее будет происходить поиск по ним, но увеличение размера таблицы сокращает время поиска. Должно быть степенью двойки, по умолчанию 1024.
+ `perturb` — Кол-во секунд для периода пертурбации, т.е. как часто изменять хэш-функцию (за счёт соли).
+ `flows` — Кол-во потоков. (единственное, что мы настраиваем в CLI; хотя `brasd` SFQ тоже пользуется и настраивает больше параметров!)
+ `quantum` — Порция трафика для одного раунда RR-процесса. Байты.
+ `headdrop` — Флаг для отбрасывания трафика из головы очереди, а не с хвоста. Гипотетически, так отклик TCP лучше.
+ `redflowlimit` — Ограничение в байтах на очередь **потока**. Заодно включает сам RED на каждом потоке SFQ. Да, но мы этим тоже не пользуемся.
+ Параметры RED третий раз перечислять не очень-то хочется. Но тут поддерживаются все параметры из [[#RED]], кроме `adaptive`.
Параметры RED на SFQ конфигурируются аналогично WRED и VQ: к SFQ подключается RED, с которого и будет считываться конфигурация per-flow RED.
+134 -95
View File
@@ -2,8 +2,12 @@
#include "Logger.h"
#include "nodes/SimpleNode.h"
#include "nodes/ComplexNode.h"
#include "nodes/HTBNode.h"
#include "nodes/PFIFONode.h"
#include "nodes/BFIFONode.h"
#include "nodes/REDNode.h"
#include "nodes/WREDNode.h"
#include "nodes/SFQNode.h"
#include "iterators/Traversal.h"
@@ -27,121 +31,156 @@ void printTreeLadder(INode& node) {
}
int main() {
Logger::setMinSeverity("MAIN", Logger::Severity::Debug);
Logger::setMinSeverity("MAIN", Logger::Severity::Warning);
Logger::suppressCategory("Node");
Logger::suppressCategory("Link");
Logger::suppressCategory("Mixin");
Logger::suppressCategory("ConDes");
Logger::setMinSeverity("ConDes", Logger::Severity::Info);
Logger::setMinSeverity("ConDes", Logger::Severity::Warning);
auto& logger = Logger::get("MAIN");
logger.info("Entering main scope...");
int negative_fails_count = 0;
{
auto root = std::make_shared<ComplexNode>("ComplexRoot");
auto child1 = std::make_shared<ComplexNode>("ComplexChild1");
// Строим дерево HTB с несколькими уровнями и разными краевыми дисциплинами
auto root = HTBNode::create("HTB_root");
root->linkChild(child1);
auto htbA = HTBNode::create("HTB_A");
auto htbB = HTBNode::create("HTB_B");
root->linkChild(htbA);
root->linkChild(htbB);
std::cout << "\nInit tree:\n";
// Ветка A: PFIFO, RED, BFIFO на краях
auto htbA1 = HTBNode::create("HTB_A1");
auto htbA2 = HTBNode::create("HTB_A2");
auto htbA3 = HTBNode::create("HTB_A3");
htbA->linkChild(htbA1);
htbA->linkChild(htbA2);
htbA->linkChild(htbA3);
auto pfifoLeaf = PFIFONode::create("PFIFO_leaf");
htbA1->linkChild(pfifoLeaf);
htbA2->linkChild(REDNode::create("RED_leaf"));
htbA3->linkChild(BFIFONode::create("BFIFO_leaf"));
// Ветка B: WRED (с RED детьми) и SFQ (с одним RED)
auto htbB1 = HTBNode::create("HTB_B1");
auto htbB2 = HTBNode::create("HTB_B2");
htbB->linkChild(htbB1);
htbB->linkChild(htbB2);
// WRED как лист с несколькими RED
auto wred = WREDNode::create("WRED_leaf");
wred->linkChild(REDNode::create("RED_vq1"));
wred->linkChild(REDNode::create("RED_vq2"));
htbB1->linkChild(wred);
// SFQ с одним RED
auto sfq = SFQNode::create("SFQ_leaf");
htbB2->linkChild(sfq);
// Негативный сценарий: SFQ должен дозволять подключать к себе
// только узлы RED.
logger.info("[Negative] Попытка добавить не-RED в SFQ (должно упасть)");
try {
sfq->linkChild(HTBNode::create("nonRED_on_SFQ"));
logger.err("Удалось подключить HTB к SFQ!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
// И всё-таки подключим SFQ
sfq->linkChild(REDNode::create("RED_on_SFQ"));
}
// Другие негативные сценарии
logger.info("[Negative] Попытка подключить не-RED к SFQ (должно упасть)");
try {
pfifoLeaf->linkChild(HTBNode::create("ShouldFailOnSFQ"));
logger.err("Удалось подключить ребёнка к SFQ!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
logger.info("[Negative] Попытка подключить что-то к PFIFO (должно упасть)");
try {
pfifoLeaf->linkChild(REDNode::create("ShouldFailOnPFIFO"));
logger.err("Удалось подключить ребёнка к PFIFO!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
logger.info("[Negative] Попытка подключить второй не-HTB узел к HTB_A1 (должно упасть)");
try {
htbA1->linkChild(BFIFONode::create("SecondLeafOnHTB_A1"));
logger.err("Удалось подключить второй лист к HTB_A1!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
logger.info("[Negative] Попытка подключить не-HTB узел к HTB_A (должно упасть)");
try {
htbA->linkChild(REDNode::create("AnotherLeafOnHTB_A"));
logger.err("Удалось подключить второй лист к HTB_A1!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
logger.info("[Negative] Попытка добавить не-RED в WRED (должно упасть)");
try {
wred->linkChild(PFIFONode::create("NotRED_for_WRED"));
logger.err("Удалось подключить не-RED к WRED!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
logger.info("[Negative] Попытка добавить второго ребёнка в SFQ (должно упасть)");
try {
sfq->linkChild(REDNode::create("SecondRED_on_SFQ"));
logger.err("Удалось подключить второй ребёнок к SFQ!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
logger.info("[Negative] Попытка RED->RED (должно упасть, RED — лист)");
try {
auto redLeaf = REDNode::create("RED_alone");
redLeaf->linkChild(REDNode::create("NestedRED"));
logger.err("Удалось подключить RED к RED!");
negative_fails_count++;
} catch (const std::logic_error& e) {
logger.info(std::string("[Ожидаемое исключение] ") + e.what());
}
// Покрасуемся получившимся деревом:
std::cout << "\nHTB tree:\n";
printTreeLadder(*root);
std::cout << "\n";
{
auto child2 = std::make_shared<ComplexNode>("ComplexChild2");
root->linkChild(child2);
auto subchild2 = std::make_shared<SimpleNode>("SimpleSubChild2");
child2->linkChild(subchild2);
auto child3 = std::make_shared<ComplexNode>("ComplexChild3");
root->linkChild(child3);
auto subchild3 = std::make_shared<SimpleNode>("SimpleSubChild3");
child3->linkChild(subchild3);
{
// Негативный сценарий 1: попытка добавить второй SimpleNode к ComplexNode
try {
auto anotherSimple = std::make_shared<SimpleNode>("ShouldFail");
child2->linkChild(anotherSimple);
logger.err("[ERROR] Не должно было получиться добавить второй SimpleNode к ComplexNode!");
} catch (const std::logic_error& e) {
logger.warn(std::string("[Ожидаемое исключение] ") + e.what());
}
// Негативный сценарий 2: попытка добавить ComplexNode к SimpleNode
// Это допустимо: SimpleNode может иметь ComplexNode в качестве единственного ребёнка
auto goodComplex = std::make_shared<ComplexNode>("GoodComplex");
subchild2->linkChild(goodComplex);
logger.info("[OK] ComplexNode успешно добавлен к SimpleNode как единственный ребёнок.");
// Негативный сценарий: попытка добавить второго ребёнка к SimpleNode
try {
auto anotherSimple = std::make_shared<SimpleNode>("ShouldFail2");
subchild2->linkChild(anotherSimple);
logger.err("[ERROR] Не должно было получиться добавить второго ребёнка к SimpleNode!");
} catch (const std::logic_error& e) {
logger.warn(std::string("[Ожидаемое исключение] ") + e.what());
}
// Негативный сценарий: попытка добавить SimpleNode в ComplexNode, который уже содержит несколько ComplexNode-дочерних
try {
auto badSimple = std::make_shared<SimpleNode>("BadSimple");
root->linkChild(badSimple);
logger.err("[ERROR] Не должно было получиться добавить SimpleNode в ComplexNode с несколькими ComplexNode-дочерними!");
} catch (const std::logic_error& e) {
logger.warn(std::string("[Ожидаемое исключение] ") + e.what());
}
}
std::cout << "\nAnother tree:\n";
printTreeLadder(*root);
std::cout << "\n";
std::cout << "\nList:\n";
printTreeList(*root);
std::cout << "\n";
std::cout << "\nBFS:\n";
printTreeBFS(*root);
std::cout << "\n";
logger.info("Unlinking ComplexChild2...\n");
child2->unlinkParent();
std::cout << "\nTree after unlink:\n";
printTreeLadder(*root);
std::cout << "\n";
logger.info("Put refs of ComplexChild2, ComplexChild3 and its children");
}
std::cout << "\nTree after scope out:\n";
printTreeLadder(*root);
// Обход в глубину:
std::cout << "\nDFS list:\n";
printTreeList(*root);
std::cout << "\n";
logger.info("Unlinking ComplexChild1 and ComplexChild3...\n");
child1->unlinkParent();
for (auto child : root->getLink()->getChildren()) {
NodePtr childNode = std::dynamic_pointer_cast<INode>(child);
if (childNode->name() == "ComplexChild3") {
childNode->unlinkParent();
break;
}
}
auto child4 = std::make_shared<SimpleNode>("SimpleChild4");
root->linkChild(child4);
std::cout << "\nTree flush and link SimpleChild4:\n";
printTreeLadder(*root);
// Обход в ширину:
std::cout << "\nBFS list:\n";
printTreeBFS(*root);
std::cout << "\n";
}
logger.info("Exited main scope. All smart pointers destroyed.");
logger.info("(It don't? Check ConDes logger)");
if (negative_fails_count)
logger.err(std::string("Negative fails: ") + std::to_string(negative_fails_count));
return 0;
}