5. Работа с Controller¶
-
class
Controller
¶ Объявлен в файле
Controller.hh
Controller принимает на себя низкоуровневые обязанности по установке и поддержке соединения с коммутаторами, а так же обмену OpenFlow сообщениями.
Так как контроллер это основное приложение, то для полезной работы вам придется прямо или косвенно взаимодействовать с контроллером.
Примечание
Controller
активно использует библиотеку LibFluid.
5.1. Сигналы¶
Controller
инициирует некоторые сигналы о событиях, которые произошли в сети:
- void
Controller::
switchUp
(SwitchConnectionPtr conn, of13::FeaturesReply fr)¶К контроллеру подключился коммутатор.
- void
Controller::
portStatus
(SwitchConnectionPtr ofconn, of13::PortStatus ps)¶Коммутатор сообщил об изменении состояния портов.
- void
Controller::
switchDown
(SwitchConnectionPtr ofconn)¶Соединение с коммутатором прервалось.
- void
Controller::
flowRemoved
(SwitchConnectionPtr ofconn, of13::FlowRemoved &fr)¶Коммутатор сообщил об удалении потока.
Пример:
#include "YourApp.hh"
#include "Controller.hh"
#include "Common.hh"
REGISTER_APPLICATION(YourApp, {"controller", ""})
void YourApp::init(Loader* loader, const Config&)
{
Controller *ctrl = Controller::get(loader);
QObject::connect(ctrl, Controller::switchUp,
[](SwitchConnectionPtr conn, of13::FeatireReply fr){
LOG(INFO) << "Connected switch : " << conn->dpid();
});
QObject::connect(ctrl, Controller::portStatus,
[](SwitchConnectionPtr conn, of13::PortStatus ps){
LOG(INFO) << "port states chages on switch : " << conn->dpid();
});
QObject::connect(ctrl, Controller::switchDown, [](SwitchConnectionPtr conn){
LOG(INFO) << "swith disconnected : " << conn->dpid();
});
QObject::connect(ctrl Controller::flowRemoved,
[](SwitchConnectionPtr conn, of13::FlowRemoved& fr){
std::string reason;
switch (fr.reason){
case of13::OFPRR_DELETE:
reason = "controller message";
break;
case of13::OFPRR_IDLE_TIMEOUT:
reason = "idle timeout";
break;
case of13::OFPRR_HARD_TIMEOUT:
reason = "hard timeout";
break;
}
LOG(INFO) << "from switch " << conn->dpid() << " was deleted by " << reason
<< "flow with cookie " << fr.cookie();
});
}
5.2. StaticTransaction¶
5.2.1. Registering static transaction¶
Controller
предоставляет возможность напрямую отправлять OpenFlow сообщения на коммутаторы, и принимать от них ответы.
-
class
OFTransaction
¶ Класс, который предоставляет инструменты для общения с коммутаторами.
-
OFTransaction *
Controller::
registerStaticTransaction
(Application *caller)¶ Регистрация вашего приложения для статического обмена пакетами с коммутаторами.
Данный метод создаст для вас объект класса
OFTransaction
, через который вы можете отправлять пакеты.Примечание
Controller
рарезервирует за вами xid. Все сообщения от вас будут помечаться данным xid. И наоборот,:cpp:class::OFTransaction будет доставлять вашему приложению, только сообщения с данным xid.
5.2.2. OFTransaction¶
-
void
OFTransaction::
request
(SwitchConnectionPtr conn, OFMsg &msg)¶ Отправить OpenFlow сообщение на коммутатор.
SwitchConnectionPtr conn – соединение с коммутатором, на который надо отправить сообщение.
OFMsg& msg – OpenFlow сообщение.
Qt сигнал с ответом на OpenFlow запрос.
SwitchConnectionPtr conn – соединение с коммутатором, от которого пришло сообщение.
std::shared_ptr<OFMsgUnion> reply – сообщение.
Пример:
void YourApp::init(Loader* loader, const Config&)
{
Controller* ctrl = Controller::get(loader);
OFTransaction oftran = ctrl.registerStaticTransaction(this);
QObject::connect(ctrl, Controller::switchUp,
[oftran](SwitchConnectionPtr conn, of13::FeatireReply)
{
of13::MultipartRequestFlow mprf;
mprf.table_id(of13::OFPTT_ALL);
mprf.out_port(of13::OFPP_ANY);
mprf.out_group(of13::OFPG_ANY);
mprf.cookie(0x0);
mprf.cookie_mask(0x0);
mprf.flags(0);
oftran->request(conn, mprf);
});
QObject::connect(oftran, OFTransaction::response,
[](SwitchConnectionPtr conn, std::shared_ptr<OFMsgUnion> reply)
{
LOG(INFO) << "Switch responsed ! ";
});
}
5.3. Резервирование таблицы потоков¶
Если вашему приложению нужна своя таблица потоков вы можете запросить ее у контроллера.
-
uint8_t
Controller::
reserveTable
()¶ Контроллер выделит вам таблицу потоков, в которую вы можете добавлять свои правила. Таблицы выделяются в стадии инициализации контроллера, и их число не может быть изменено далее в runtime.
Опасно
RUNOS не ограничивает вас в добавлении правил в другие таблицы потоков. Однако это не гарантирует корректную работу вашего и других сервисов.
Примечание
Отдельно стоит упомянуть собственную таблицу Maple: для Вас она read-only. А если Вам все-таки удастся ее модифицировать(DELETE OFPTT_ALL, например), Maple восстанет состояние своей таблицы по первому же PacketIn. Так что не советуем ее модифицировать.
По умолчанию в запуске RuNOS 0.6.1 инстанциирует на switch’е следующую конфигурацию таблиц:
$sudo ovs-ofctl -O OpenFlow13 dump-flows s1 OFPST_FLOW reply (OF1.3) (xid=0x2): cookie=0x0, duration=394.247s, table=0, n_packets=0, n_bytes=0, send_flow_rem check_overlap priority=0 actions=goto_table:1 cookie=0x0, duration=394.247s, table=1, n_packets=0, n_bytes=0, send_flow_rem check_overlap priority=0 actions=goto_table:2 cookie=0x0, duration=394.247s, table=2, n_packets=0, n_bytes=0, send_flow_rem check_overlap priority=0 actions=CONTROLLER:0
Примечание
Для работы контроллера в минимальной конфигурации достаточно одной таблицы. Она будет управляться ядром (`Maple) <refMaple>` контроллера, и все асинхронно созданные в ней правила будут вскоре удаляться. Если создано более одной таблицы, Maple будет управлять таблицей с наибольшим номером.
Пример:
void ExampleFireWall::init(Loader* loader, const Config&) { Controller* ctrl = Controller::get(loader); uint8_t table_no = ctrl->reservTable(); QObject::connect(ctrl, Controller::SwitchUp, [table_no](SwitchConnectionPtr conn, of13::FeatireReply) { of13::FlowMod fm; fm.table_id(table_no); fm.match(new EthSrc("ff:ff:ff:ff:ff:ff")); conn->send(fm); }); }
5.4. PacketMissHandler¶
5.4.1. Введние¶
Задача модуля обработчика PacketIn’ов абстрагировать вас от низкоуровневых OpenFlow команд и пакетов и тем самым повысить удобство программирования.
Программист приложений для контроллера может писать данный обработчик пакетов сосредоточившись только на основной логике своей программы. Ему не надо будет самому формировать FlowMod, за него это сделает ядро RUNOS, Все что нужно программисту, это прочитать необходимые ему поля пакета, и на основании этого выбрать действие с пакетом.
5.4.2. Структура¶
Функция обработчик : функция которая определяет действие с пакетом для коммутатора, она вызывается на каждый PacketIn.
Для каждого коммутатора описывается отдельная функция обработчик. Поэтому программисту необходимо описать не одну функцию обработчик, а фабрику, которая будет создавать для каждого коммутатора свою функцию обработчик.
В контроллере может быть одновременно зарегистрировано множество обработчиков, они все выстраиваются в конвеер и выполняются один за другим.
Порядок определяется файлом настроек network-settings.json
.
Обработчик PacketMissHandler – это callable объект, который имеет следующую сигнатуру :
Фабрика PacketMissHandlerFactory – это callable объект, который имеет следующую сигнатуру :
- PacketMissHandler
PacketMissHandlerFactory
(SwitchConnectionPtr conn)¶На вход ей подается соединение с коммутатором, а она генерирует функцию обработчик.
5.4.3. Регистрация¶
Регистрировать фабрику обработчиков необходимо во время инициализации вашего приложения.
-
void
Controller::
registerHandler
(const char *name, PacketMissHandlerFactory factory)¶ Вам необходимо зарегистрировать фабрику обработчиков и имя обработчика.
Имя обработчика надо указывать в файле
network-settings.json
, чтобыController
запускал ее в pipeline.
Вам необходимо указать ваш обработчик в списке обработчиков в network-settings.json
в "controller" : { "pipeline" : [ ... , "example", ... ] }
.
Обработчики выполняются в порядке, указанном в данном списке.
Пример:
void Exmaple::init(Loader* loader, const Config& )
{
Controller* ctrl = Controller::get(loader);
ctrl->registerHandler( "exmaple",
[](SwitchConnectionPtr conn)
{
return [](Packet& pkt, FlowPtr , Decision decision)
{
LOG(INFO) << "Packet In !";
return decision;
};
});
}
5.4.4. Packet¶
Вы можете читать необходимые поля из пакета, для этого существуют специальные функции :
-
oxm::field<>
Packet::
load
(oxm::mask<> mask) const¶ Прочитать поле пакета.
-
oxm::field<>
-
bool
Packet::
test
(oxm::field<> need) const¶ Проверить состояния поля пакета на заданное значение.
-
bool
Для представления полей используется библиотека RUNOS::oxm.
Данная библиотека кодирует в себе значения поля. Существуют три вида кодирования :
oxm::value
– значение в поле.oxm::mask
– маска поля.oxm::field
– значение и маска.
Пример:
void Example::init(Loader* loader, const Config& )
{
Controller* ctrl = Controller::get(loader);
ctrl->registerHandler("exmaple",
[](SwitchConnectionPtr conn)
{
const auto ofb_eth_type = oxm::eth_type(); // for optimization
const auto ofb_ipv4_dst = oxm::ipv4_dst();
return [](Packet& pkt, FlowPtr, Decision decision)
{
if (pkt.test(ofb_eth_type == 0x0800)) // ip
{
uint32_t dst_ip = pkt.load(ofb_ipv4_dst);
LOG(INFO) << dst_ip; // TODO : pretty print
}
return decision;
};
});
}
Поддерживается маскирование с помощью оператора &
для некоторых полей.
Пример:
void Example::init(Loader* loader, const Config& )
{
Controller* ctrl = Controller::get(loader);
ctrl->registerHandler("exmaple",
[](SwitchConnectionPtr conn)
{
const auto ofb_eth_src = oxm::eth_src();
return [=](Packet& pkt, FlowPtr, Decision decision)
{
ethaddr src_mac = pkt.load(ofb_eth_src & "ff:ff:ff:00:00:00");
LOG(INFO) << src_mac;
return decision;
};
});
}
Вы так же можете изменять поля пакета :
- void
Packet::
modify
(oxm::field<> need)¶
Пример:
void Exmaple::init(Loader* loader, const Config& )
{
Controller* ctrl = Controller::get(loader);
ctrl->registerHandler("exmaple",
[](SwitchConnectionPtr conn)
{
const auto ofb_eth_dst = oxm::eth_dst();
return [=](Packet& pkt, FlowPtr, Decision decision)
{
pkt.modify(ofb_eth_dst == "11:22:33:44:55:66");
return decision;
};
});
}
RUNOS::OXM поддерживает следующие типы :
Поле Название Базовый тип Поддержка маски in_port входящий порт пакета uint8_t нет eth_type тип ethernet uint16_t нет eth_src ethernet адрес отправителя ethernet да eth_dst ethernt адрес получателя ethernet да ip_proto протокол IP uint8_t нет ipv4_src IP адрес отправителя версии 4 uint32_t да ipv4_dst IP адрес получателя версии 4 uint32_t да tcp_src TCP порт источника uint16_t нет tcp_dst TCP порт назначения uint16_t нет udp_src UDP порт источника uint16_t нет udp_dst UDP порт назначения uint16_t нет arp_spa логический адрес отправителя uint32_t да arp_tpa логический адрес получателя uint32_t да arp_sha физический адрес отправителя uint32_t да arp_tha физический адрес получателя uint32_t да arp_op Код операции uint16_t нет vlan_vid Идентификатор vlan uint16_t нет ipv6_src IP адрес отправителя версии 6 IPv6Addr да ipv6_dst IP адрес получателя версии 6 IPv6Addr да icmp_type Тип ICMP сообщения uint8_t нет icmp_code Код ICMP сообщения uint8_t нет
Предупреждение
Вызов функций Packet::load и Packet::test должен определятся только входящим пакетом и не зависеть от внешних состояний.
5.4.5. Decision¶
5.4.5.1. Действия¶
Функция обработчик возвращает свое решения о действии с пакетом.
-
class
Decision
¶
Существую следующие типы действий.
Inspect – отправить пакет на контроллер:
Drop – отбросить пакет.
Unicast – отправить пакет на определенный порт.
Multicast – отправить пакет на множество портов.
Broadcast – широковещательная рассылка.
Внимание
методы Decision
возвращают новый экземпляр класса, но не меняют старый.
Пример:
void Example::init(Loader* loader, const Config& )
{
Controller* ctrl = Controller::get(loader);
ctrl->registerHandler("example", [](SwitchConnectionPtr conn)
{
const auto ofb_eth_dst = oxm::eth_dst();
return [](Packet& pkt, FlowPtr, Decision decision)
{
if (pkt.test(ofb_eth_dst == "ff:ff:ff:ff:ff:ff"))
return decision.broadcast();
else
return decision;
};
});
}
5.4.5.2. Time¶
Decision позволяет настраивать время установленного правила:
- type std::chrono::duration<uint32_t>
Decision::
duration
¶Время жизни правила. По умолчанию бесконечное время жизни.
std::chrono::seconds::zero()
– Применить правило только к данному пакету.
Методы Decision
для работы со временем :
Примечание
Каждое приложение может выбирать свое время тайм-аута. В этом случае выбирается меньший из всех.
Пример:
void ExampleLearningSwitch::init(Loader *loader, const Config &)
{
Controller* ctrl = Controller::get(loader);
ctrl->registerHandler("exmaple-forwarding", [](SwitchConnectionPtr) {
// MAC -> port mapping for EVERY switch
std::unordered_map<ethaddr, uint32_t> seen_port;
const auto ofb_in_port = oxm::in_port();
const auto ofb_eth_src = oxm::eth_src();
const auto ofb_eth_dst = oxm::eth_dst();
return [=](Packet& pkt, FlowPtr, Decision decision) mutable {
// Learn on packet data
ethaddr src_mac = pkt.load(ofb_eth_src);
// Forward by packet destination
ethaddr dst_mac = pkt.load(ofb_eth_dst);
seen_port[src_mac] = pkt.load(ofb_in_port);
// forward
auto it = seen_port.find(dst_mac);
if (it != seen_port.end()) {
return decision.unicast(it->second)
.idle_timeout(std::chrono::seconds(60))
.hard_timeout(std::chrono::minutes(30));
} else {
auto ret = decision.broadcast();
if (not is_broadcast(dst_mac)) {
LOG(INFO) << "Flooding for unknown address " << dst_mac;
ret.hard_timeout(std::chrono::seconds::zero());
}
return ret;
}
};
});
}
5.4.5.3. Остановка конвеера¶
Ваше приложение способно остановить выполнение pipeline:
Пример:
void ExmapleFireWall::init(Loader *loader, const Config &)
{
Controller* ctrl = Controller::get(loader);
ctrl->registerHandler("firewall"", [](SwitchConnectionPtr) {
const auto ofb_eth_src = oxm::eth_src();
return [=](Packet& pkt, FlowPtr, Decision decision) mutable {
if (pkt.test(ofb_eth_src == "ff:ff:ff:ff:ff:ff")){
return decision.drop().return_();
}
return decision;
};
});
}
5.4.6. Состояния потока¶
Поток, который установится в результате работы обработчиков, может находиться в некоторых состояниях:
Flow::State::Egg
– поток только создается и еще не был установлен на коммутатор.Flow::State::Active
– поток установлен на коммутатор.Flow::State::Evicted
– поток удален с коммутатора по некоторым причинам.Например не хватает места в таблице потоков.
Flow::State::Idle
– поток удален по idle timeout.Flow::State::Expired
– поток удален по hard timeout.
Вы можете следить за состояниям потока с помощью сигнала:
- void
Flow::
stateChanged
(Flow::State new_state, uint64_t cookie)¶
Пример:
void Example::init(Loader *loader, const Config &)
{
Controller* ctrl = Controller::get(loader);
ctrl->registerHandler("firewall"", [](SwitchConnectionPtr) {
const auto ofb_eth_src = oxm::eth_src();
return [=](Packet& pkt, FlowPtr fptr, Decision decision) mutable {
connect(fptr.get(), &Flow::stateChanged,
[](Flow::State new_state, uint64_t)
{
LOG(INFO) << "New state of flow";
});
return decision;
};
});
}
5.5. Реализация рассылки широковещательного сообщения¶
RUNOS предоставляет вам возможность реализовать свою низкоуровневую реализацию Broadcast-правила.
-
type std::function<Action*(uint64_t dpid)>
FloodImplementation
¶ Функция, которая реализует рассылку широковещательных сообщений.
-
void
Controller::
registerFlood
(FloodImplementation flood)¶ Функция, которая регистрирует реализацию рассылки широковещательных сообщений.
По-умолчанию, сообщения рассылаются на все порты.
Вызывать необходимо в методе
init
вашего приложения.Примечание
одновременно в контроллере не может быть зарегистрировано больше одной реализации рассылки широковещательных сообщений.
Пример:
void ExmapleSTP::init(Loader* loader, const Config&)
{
Controller* ctrl = Controller::get(loader);
ctrl->registerFlood("exmaple-stp", [](uint64_t dpid)
{
return new of13::GroupAction(FLOOD_GROUP); // Указать Group table, с реализаций широковещания
});
}