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 сообщение.

void OFTransaction::response(SwitchConnectionPtr conn, std::shared_ptr<OFMsgUnion> reply)

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.

Для каждого коммутатора описывается отдельная функция обработчик. Поэтому программисту необходимо описать не одну функцию обработчик, а фабрику, которая будет создавать для каждого коммутатора свою функцию обработчик.

_images/PacketMissFactory.png

В контроллере может быть одновременно зарегистрировано множество обработчиков, они все выстраиваются в конвеер и выполняются один за другим. Порядок определяется файлом настроек network-settings.json.

Обработчик PacketMissHandler – это callable объект, который имеет следующую сигнатуру :

Decision PacketMissHandler(Packet &pkt, FlowPtr flow, Decision decision)

На вход подается пакет, который пришел как PacketIn.

Параметр decision – это решение обработчиков, которые выполнялись до вашего обработчика.

Возвращается действие над пакетом.

Фабрика 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

    Прочитать поле пакета.

  • bool Packet::test(oxm::field<> need) const

    Проверить состояния поля пакета на заданное значение.

Для представления полей используется библиотека 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 – отправить пакет на контроллер:

    Decision Decision::inspect(uint16_t bytes) const

    параметр bytes количество байт заголовка пакета, которые будут отправлены на контроллер.

  • Drop – отбросить пакет.

    Decision Decision::drop() const
  • Unicast – отправить пакет на определенный порт.

    Decision Decision::unicast(uint32_t port) const

    Параметр : port – номер порта, на который отправить данный пакет.

  • Multicast – отправить пакет на множество портов.

    Decision Decision::multicast(Multicast::PortSet ports) const

    параметр : ports – список портов

  • Broadcast – широковещательная рассылка.

    Decision Decision::broadcast() const

    политика отправки определяется зарегистрированной функцией широковещательной рассылки . По умолчанию пакет отправляется на все физические порты, кроме входного.

Внимание

методы 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 для работы со временем :

  • Decision Decision::idle_timeout(durantion seconds) const

    Установить тайм-аут простоя.

    Правило удаляется, если в течении seconds времени ни один пакет не прошел по этому правилу

  • Decision Decision::hard_timeout(duration seconds) const

    Установить тайм-аут.

    Правило удаляется после по истечении seconds времени.

Примечание

Каждое приложение может выбирать свое время тайм-аута. В этом случае выбирается меньший из всех.

Пример:

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:

Decision Decision::return_() const

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, с реализаций широковещания
      });
}