Instrukcja do laboratorium 5 Implementacja podstawowych reguł
Transkrypt
Instrukcja do laboratorium 5 Implementacja podstawowych reguł
PRZEDMIOT: PROWADZĄCY: ROK STUDIÓW : SEMESTR: KIERUNEK: Sieci Sterowane Programowo mgr inż. Grzegorz Rzym ([email protected]) I (2 stopień) zimowy Teleinformatyka Instrukcja do laboratorium 5 Implementacja podstawowych reguł z wykorzystaniem kontrolera Floodlight. przepłwywów. protokołu Reaktywna OpenFlow instalacja 1. Cel ćwiczenia Celem ćwiczenia jest implementacja własnego modułu sterownika Floodlight. Moduł ten nasłuchiwał będzie wiadomości protokołu OpenFlow typu Packet_In, rozpakowywał je, a następnie, zgodnie z zadaną logiką, instalował potrzebne przepływy w przełącznikach OpenFlow. 2. Przygotowanie środowiska 2.1 Instalacja wymaganych zależności Przed zbudowaniem i uruchomieniem środowiska należy zainstalować potrzebne repozytoria poleceniem: $ sudo apt-get update $ sudo apt-get install build-essential ant maven python-dev eclipse 2.2 Pobieranie i budowanie kontrolera Proszę pobrać kontroler ze strony: http://www.kt.agh.edu.pl/~rzym/lectures/TI-SDN/floodlight-1.2lab5.zip a następnie rozpakować go. Kontroler zawiera już kilka klas w paczce pl.edu.agh.kt, m.in. moduł SdnLabListener, klasę PacketExtractor, oraz nową klasę Flows.java, która będzie wykorzystywana podczas dzisiejszych zajęć. 2.3 Import projektu do środowiska Eclipse IDE Proszę uruchomić środowisko Eclipse i utworzyć nowe Workspace. W celu importu projektu proszę wybrać kolejno: • File -> Import -> General -> Existing Projects into Workspace • Kliknąć na przycisk Browse obok Select root directory i odnaleźć katalog, do którego został pobrany wcześniej projekt Floodlight • W oknie Projects zaznaczyć Floodlight • Kliknąć przycisk Finish. Po tym procesie cały projekt powinien zostać zaimportowany. W lewej części środowiska Eclipse w oknie Package Explorer powinno pojawić się drzewo katalogów projektu Floodlight. 2.4 Budowanie i uruchamianie projektu w środowsku Eclipse Aby zbudować projekt (np. po zmianach w kodzie źródłowym) należy utworzyć cel: • Wybrać Run->Run Configurations • Prawym przyciskiem myszy wybrać Java Application->New • W polu nazwy wpisać FloodlightLaunch • W polu Project wybrać Floodlight • W polu Main Class wybrać net.floodlightcontroller.core.Main • Kliknąć przycisk Apply 1 PRZEDMIOT: PROWADZĄCY: ROK STUDIÓW : SEMESTR: KIERUNEK: Sieci Sterowane Programowo mgr inż. Grzegorz Rzym ([email protected]) I (2 stopień) zimowy Teleinformatyka 3. Implementacja wiadomości PacketOut Wiadomość PacketOut może zostać użyta w celu wstrzyknięcia pakietu z kontrolera do płaszczyzny danych przełącznika i przesłania go dalej (wysłania pakietu z kontrolera, z płaszczyzny sterowania, który pojawi się w sieci, w płaszczyźnie danych). Taka sytuacja może mieć miejsce np. gdy kontroler jest też odpowiedzialny za przyznawanie adresów IP w sieci (moduł serwera DHCP), rozwiązywanie nazw domenowych (moduł DNS) itp. Uwaga: Wiadomość PacketOut zwykle jest odpowiedzią na wiadomość PacketIn i nie instaluje ona przepływu w tablicy przepływów przełączników OpenFlow. Służy tylko do wstrzyknięcia pakietów bezpośrednio z kontrolera lub do nadania akcji pakietom, które zostały zapisane w buforze przełącznika. Proces przygotowania pakietu i wstrzyknięcia go do sieci następuje w następującej kolejności: • Przygotowanie nagłówków kolejnych warstw, ustawienie ich pól, • Ustawienie jako ładunku danych danej warstwy nagłówka kolejnej warstwy (np. ładunkiem dla warstwy drugiej jest nagłówek IP, itd.), • Serializacja, • Utworzenie wiadomości PacketOut, w której jako dane znajdują się zserializowane nagłówki oraz dane aplikacyjne, • Ustawienie portów wejściowych i wyjściowych przełącznika, • Wysłanie pakietu do przełącznika. warstw zdefiniowana jest w paczce Uwaga: Większość nagłówków kolejnych net.floodlightcontroller.packet, przygotowane klasy oraz ich metody w łatwy sposób pozwalają na komponowanie własnych pakietów. Uwaga2: Wiadomość PacketOut bardzo często budowana jest z ładunku wiadomości PacketIn, np. dla pierwszego pakietu danego przepływu, który do czasu podjęcia decyzji przez kontroler o utworzeniu nowego przepływu w tablicy przełącznika specyfikuje, co zrobić z buforowanym pakietem. W ramach tego ćwiczenia przygotowany zostanie (od podstaw) datagram UDP wraz z kolejnymi (niższymi) warstwami oraz losowymi danymi, a następnie wysłany zostanie jako pakiet do przełącznika, który z kolei wyśle je do wszystkich podłączonych hostów - jako port wyjściowy zostanie ustawiony interfejs FLOOD, co oznacza, że pakiet zostanie wysłany na wszystkie interfejsy oprócz tego, z którego przyszedł. W ramach ćwiczenia rozbudowywane będzie ciało metody sendPacketOut() klasy Flows.java 3.1 Warstwa druga Zadanie rozpoczęte zostanie od utworzenia nowego nagłówka ethernetowego oraz ustawienia kilku jego pól. Proszę zwrócić uwagę na EtherType ustawiony na IPv4, co oznacza że ramka niosła będzie pakiet IP w wersji 4. // Ethernet Ethernet l2 = new Ethernet(); l2.setSourceMACAddress(MacAddress.of("00:00:00:00:00:01")); l2.setDestinationMACAddress(MacAddress.BROADCAST); l2.setEtherType(EthType.IPv4); 2 PRZEDMIOT: PROWADZĄCY: ROK STUDIÓW : SEMESTR: KIERUNEK: Sieci Sterowane Programowo mgr inż. Grzegorz Rzym ([email protected]) I (2 stopień) zimowy Teleinformatyka 3.2 Warstwa trzecia Nowy pakiet IPv4 jest utworzony jako nośnik datagramów UDP (IpProtocol.UDP). Nadane mu zostają adresy IP źródła i celu oraz ustawiona zostaje domyślna wartość pola TTL. Można spróbować ustawić także inne pola nagłówka IP. // IP IPv4 l3 = new IPv4(); l3.setSourceAddress(IPv4Address.of("192.168.1.1")); l3.setDestinationAddress(IPv4Address.of("192.168.1.255")); l3.setTtl((byte) 64); l3.setProtocol(IpProtocol.UDP); 3.3 Warstwa czwarta Następnie tworzony jest nowy nagłówek UDP wraz z ustawieniem portów źródłowego i docelowego (w tym przykładzie port usługi DNS). // UDP UDP l4 = new UDP(); l4.setSourcePort(TransportPort.of(65003)); l4.setDestinationPort(TransportPort.of(53)); 3.4 Warstwa siódma (dane) Jeśli datagram UDP powinien zawierać kolejne nagłówki, powinny one zostać dodane tak jak w powyższych przykładach. Na potrzeby zajęć załóżmy, że to już tyle nagłówków (sam kontroler nie dostarcza także klasy z nagłówkiem DNS, można znaleźć zewnętrzne biblioteki pozwalające na łatwą integrację). W tworzonym przykładzie utworzymy 1kB losowych danych, służących jako ostateczny payload pakietu. Oczywiście każdy rodzaj danych/payloadu może zostać utworzony za pomocą klasy net.floodlightcontroller.packet.Data lub manualnie. // Layer 7 data Data l7 = new Data(); l7.setData(new byte[1000]); 3.5 Ustawianie ładunków i serializacja danych Do tej pory każdy z nagłówków został stworzony jako osobny obiekt. Kolejnym krokiem jest ustawienie kolejnych nagłówków (ewentualnie danych końcowych) jako ładunek warstwy niższej. // set the payloads of each layer l2.setPayload(l3); l3.setPayload(l4); l4.setPayload(l7); A następnie serializcja całego nagłówka warstwy drugiej (już wraz z kolejnymi nagłówkami i losowymi danymi). // serialize byte[] serializedData = l2.serialize(); 3 PRZEDMIOT: PROWADZĄCY: ROK STUDIÓW : SEMESTR: KIERUNEK: Sieci Sterowane Programowo mgr inż. Grzegorz Rzym ([email protected]) I (2 stopień) zimowy Teleinformatyka 3.6 Utworzenie pakietu typu PacketOut i wysłanie go do przełącznika Końcowym etapem jest utworzenie wiadomości PacketOut i wysłanie go do przełącznika. Proces ten polega na: • ustawieniu jako payloadu wiadomości PacketOut danych, którego do tej pory zostały stworzone, • wyspecyfikowaniu porty wyjściowego spośród dostępnych portów i przypisanie mu akcji, • wyspecyfikowaniu portu wejściowego (w tym wypadku portu łączącego kontroler z przełącznikiem), • zbudowaniu obiektu, • wysłaniu pakietu do przełącznika. // Create Packet-Out and Write to Switch OFPacketOut po = sw.getOFFactory() .buildPacketOut() .setData(serializedData) .setActions(Collections.singletonList((OFAction)sw.getOFFactory().actions().output( OFPort.FLOOD, 0xffFFffFF))) .setInPort(OFPort.CONTROLLER).build(); sw.write(po); 3.7 Specjalizacja warunków wysłania wiadomości W tym etapie należy wyspecyfikować, kiedy przygotowany przez nas pakiet ma zostać wysłany do przełącznika. Może on być wysyłany tylko raz podczas startu kontrolera, albo np. za każdym razem, gdy pojawi się specyficzny pakiet typu PacketIn (np. po sprawdzeniu, że było to nowe zapytanie o DNS w naszym module). Do sprawdzenia tych przypadków może posłużyć klasa PacketExtractor budowana podczas zajęć nr 4. Na potrzeby naszych zajęć uprzednio utworzoną wiadomością reagować będziemy na każdy nowy PacketIn. W tym celu metodzie receive() klasy pl.edu.agh.kt.SdnLabListener należy doimplementować poniższy kod. Spowoduje to, że każdy nowy PacketIn wyzwoli utworzenie i wysłanie przygotowanej wiadomości PacketOut. //sending PacketOut Flows.sendPacketOut(sw); Zadanie1: Proszę uruchomić topologię minimalną poleceniem: $ sudo mn --controller=remote,ip=<controller_ip>,port=6653 a następnie przetestować działanie sieci (zaobserwować w niej utworzony pakiet). Proszę dokonać modyfikacji pakietu (wybranych pól wybranych nagłówków) i sprawdzić, czy otrzymywane pakiety zawierają zmiany. Podpowiedź: W tym celu można uruchomić konsolę xmterm jednego z hostów, a na niej uruchomić program tcpdump nasłuchujący datagramów UDP. Uwaga: W przygotowanym projekcie wyłączony został moduł net.floodlightcontroller.forwarding.Forwarding odpowiedzialny za tworzenie przepływów w przełączniku. Dlatego też, każdy pakiet wyzwala wiadomość PacketIn. 4 PRZEDMIOT: PROWADZĄCY: ROK STUDIÓW : SEMESTR: KIERUNEK: Sieci Sterowane Programowo mgr inż. Grzegorz Rzym ([email protected]) I (2 stopień) zimowy Teleinformatyka 4. Implementacja wiadomości FlowAdd Wiadomość FlowAdd służy do dodawania wpisów to tablicy przepływów przełączników OpenFlow. Oprócz tej wiadomości dostępne są m.in.: FlowDelete, FlowDeleteStrict, FlowModify, FlowModifyStrict, FlowStatsEntry, FlowStatsRequest, FlowStatsReplay. Ponieważ znaczenia tych wiadomości są zawarte w ich nazwie (i proste do rozszyfrowania), dalsza część instrukcji skupi się tylko na wiadomości FlowAdd. Kontroler Floodlight wspiera protokół OpenFlow w wersjach 1.0-1.5 (z tym, że ostatnia wersja 1.5, która jest obecna w trakcie pisania tej instrukcji, zaimplementowana została dopiero w kodzie developerskim, który można pobrać bezpośrednio z GitHuba). W kodzie kontrolera istnieje uniwersalne API pozwalające budować wsparcie dla wielu różnych wersji protokołu jednocześnie. W tej części pokazane zostanie jak dodać prosty moduł obsługujący minimalną topologię sieci środowiska Mininet (tj. jeden przełącznik i dwa hosty). Moduł ten sprawdzał będzie, z którego interfejsu przyszedł nowy pakiet i instalował odpowiedni przepływ przekierowywujący każdy pakiet z danego interfejsu na drugi dostępny interfejs, tj. jeśli pakiet przyszedł z interfejsu nr 1 zostanie wysłany na interfejs nr 2 i odwrotnie. Implementacja dokonana zostanie w metodzie simpleAdd() klasy pl.edu.agh.kt.Flows. Uwaga: Na potrzeby laboratorium instrukcja została zbudowana zgodnie z wersją 1.0.0 protokołu OpenFlow. 4.1 Przygotowanie pakietu FlowMod Na początku przygotowujemy sobie builder pakietu FlowMod korzystając z dostępnego API kontrolera. Do niego wpisywane następnie zostaną: dopasowanie oraz lista akcji. // FlowModBuilder OFFlowMod.Builder fmb = sw.getOFFactory().buildFlowAdd(); 4.2 Przygotowywanie dopasowania Korzystając z dostępnego API tworzone jest nowe dopasowanie (match). W poniższym kodzie wyciągana jest wartość portu wejściowego, na którym pokazał się pakiet typu PacketIn i wpisywany jest do tworzonego dopasowania. Takie dopasowanie spowoduje, że wszystkim pakietom, które pojawią sie na danym porcie przełącznika zostanie przypisana akcja, która zostanie utworzona w kolejnym punkcie instrukcji. Uwaga: Dostępne API samo tworzy odpowiednią maskę do zadanego dopasowania. Istnieje możliwość ręcznej ingerencji i ustawienie własnej maski. // match Match.Builder mb = sw.getOFFactory().buildMatch(); mb.setExact(MatchField.IN_PORT, pin.getInPort()); Match m = mb.build(); 4.3 Tworzenie listy akcji Ponieważ do danego przepływu może zostać przypisana więcej niż jedna akcja (np. zmień docelowy adres IP na 192.168.6.57 i wyślij pakiet na port nr 2), tworzona jest lista akcji. W naszym przykładzie 5 PRZEDMIOT: PROWADZĄCY: ROK STUDIÓW : SEMESTR: KIERUNEK: Sieci Sterowane Programowo mgr inż. Grzegorz Rzym ([email protected]) I (2 stopień) zimowy Teleinformatyka będziemy chcieli wysłać pakiet pasujący do danego dopasowania na odpowiedni port (argument outPort tworzonej metody). // actions OFActionOutput.Builder aob = sw.getOFFactory().actions().buildOutput(); List<OFAction> actions = new ArrayList<OFAction>(); aob.setPort(outPort); aob.setMaxLen(Integer.MAX_VALUE); actions.add(aob.build()); 4.4 Wpisanie dopasowania i listy akcji do buildera pakietu FlowAdd Następnie należy wpisać do buildera pakietu FlowAdd utworzone dopasowania oraz listę akcji. Ponadto należy także ustawić takie wartości jak wartości liczników Hard oraz Idle Timeout, ID buffora, w którym znajduje się kopia pakietu, który został wysłany do kontrolera (jemu też trzeba nadać odpowiednie akcje - w tym wypadku wysłać go na odpowiedni port wyjściowy) oraz priorytet wpisywanego przepływu. Całość w kodzie jak poniżej. fmb.setMatch(m) .setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT) .setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT) .setBufferId(pin.getBufferId()) .setOutPort(outPort) .setPriority(FLOWMOD_DEFAULT_PRIORITY); fmb.setActions(actions); 4.5 Wysłanie wiadomości do przełącznika Ostatnim etapem jest wysłanie stworzonej wiadomości do przełącznika. W poniższym przykładzie stosowana jest przechwytywanie wyjątków, tzn. w razie niepowodzenia na ekran zostanie wyświetlony wyjątek z kodem błędu. Powodzenie wpisania przepływu skutkuje wyświetleniem informacji o dodanym przepływie. // write flow to switch try { sw.write(fmb.build()); logger.info("Flow from port {} forwarded to port {}; match: {}", new Object[] { pin.getInPort().getPortNumber(), outPort.getPortNumber(), m.toString() }); } catch (Exception e) { logger.error("error {}", e); } 4.6 Modyfikacja metody receive() klasy pl.edu.agh.kt.SdnLabListener Ostatnim etapem jest modyfikacja metody receive() klasy pl.edu.agh.kt.SdnLabListener, w której należy dodać wywołanie dodania przepływu w następstwie pojawienia się wiadomości PacketIn. Ponieważ jednym z parametrów metody simpleAdd() jest port wyjściowy, na który zostanie wysłany 6 PRZEDMIOT: PROWADZĄCY: ROK STUDIÓW : SEMESTR: KIERUNEK: Sieci Sterowane Programowo mgr inż. Grzegorz Rzym ([email protected]) I (2 stopień) zimowy Teleinformatyka odebrany pakiet stworzona zostanie prosta konstrukcja warunkowa powodująca, że jeżeli pakiet został odebrany na porcie nr 1 to zostanie wysłany na port nr 2, i vice versa. Port wyjściowy pierwotnie został ustawiony na wartość równą 0, aby zapobiec przypadkowemu błędowi. Należy dodać następujący kod: OFPacketIn pin = (OFPacketIn) msg; OFPort outPort=OFPort.of(0); if (pin.getInPort() == OFPort.of(1)){ outPort=OFPort.of(2); } else outPort=OFPort.of(1); Flows.simpleAdd(sw, pin, cntx, outPort); Zadanie 2: Proszę przetestować działanie kodu. Jeśli nie ma żadnych błędów ping pomiędzy hostami h1 a h2 w konfiguracji minimalnej powinien działać. Zaobserwowane powinno zostać także w logach dodawanie odpowiednich przepływów. Zadanie 3: Proszę wylistować zainstalowane przepływy z poziomu przełącznika. Uwaga: Ponieważ idle timeout ustawiony jest w stałych klasy na wartość równą 5, przepływy te czyszczone są po 5 sekundach od zaobserwowania ostatniego pakietu z danego przepływu. Zadanie4: Korzystając z dostarczonej metody createMatchFromPacket() klasy pl.edu.agh.kt.Flows proszę tak zmienić implementację metody simpleAdd(), aby dopasowywanie następowało nie tylko na podstawie portu wejściowego, ale także zawartości pól kolejnych nagłówków modelu OSI-ISO. 7