Zadanie 3: sterownik urządzenia ZSONet

Data ogłoszenia: 30.04.2024

Termin oddania: 04.06.2024 (ostateczny 18.06.2024)

Materiały dodatkowe

Wprowadzenie

Zadanie polega na napisaniu sterownika do urządzenia ZSONet - prostej karty sieciowej - oraz programu pozwalającego na użycie urządzenia do wysyłania danych do wielu odbiorców z wykorzystaniem io_uring.

Urządzenie powinno być dostępne dla użytkownika jako interfejs sieciowy - w szczególności po jego podłączeniu zgodnie z instrukcjami i załadowaniu modułu system powinien mieć dostęp do internetu.

Interfejs sterownika

Urządzenie ZSONet powinno implementować następujące operacje, o których przeznaczeniu można przeczytać w dokumentacji struktur pci_driver i net_device_ops:

  • pci_driver.probe

  • pci_driver.remove

  • net_device_ops.ndo_open

  • net_device_ops.ndo_stop

  • net_device_ops.ndo_get_stats64: powinno udostępniać następujące statystyki:
    • rx_packets

    • tx_packets

    • rx_bytes

    • tx_bytes

    • rx_dropped

    • tx_dropped

    • rx_missed_errors

  • net_device_ops.ndo_start_xmit

Za zadanie można uzyskać do 10 punktów. Na ocenę zadania składają się dwie części:

  • wynik testów (od 0 do 10 punktów)

  • ocena kodu rozwiązania (od 0 do -10 punktów)

Program io_uring - transmiter

Program ma służyć do wysyłania danych do wielu odbiorców. Program dostaje adresy odbiorców w argumentach w formacie <ip>:<port>, a następnie każdą linię z stdin (wraz ze znakiem nowej linii) wysyła po TCP do każdego z odbiorców za pomocą io_uring (liburing). Jeśli wysyłamy dane do n odbiorców, program powinien na każdą linię wykonywac o(n) syscalli — należy wykorzystać dawaną przez io_uring możliwość przygotowania wielu komend i przekazania ich do wykonania jednym syscallem. Program powinien zakończyć działanie po napotkaniu końca stdin.

Program powinien ponownie nawiązywać zerwane połączenia w następujący sposób: jeśli wysyłanie ostatniej linii X do danego odbiorcy się nie powiodło, po odczytaniu następnej linii Y program jednokrotnie próbuje utworzyć socket, połączyć się z tym odbiorcą oraz wysłać do niego linię Y. Jeśli się to uda, dalsza komunikacja z tym odbiorcą przebiega standardowo; w przeciwnym wypadku kolejna próba połączenia powinna nastąpić przy wysyłaniu kolejnej linii Z.

Tworzenie socketów, nawiązywanie połączenia, wysyłanie danych oraz zamykanie socketów powinno odbywać się poprzez io_uring.

W przypadku błędu przy tworzeniu socketa, nawiązywaniu połączenia, wysyłaniu danych lub zamykaniu socketa program powinien wypisać na stderr następujący komunikat:

<ip>:<port> - <socket|connect|send|close> error: <strerror(kod błędu)>

Program powinien w szczególności działać ze sterownikiem ZSONet.

Forma rozwiązania

Jako rozwiązanie należy dostarczyć paczkę o nazwie ab123456.tar.gz (gdzie ab123456 jest loginem na students). Po rozpakowaniu paczka powinna tworzyć katalog ab123456 z dwoma podkatalogami: zsonet i transmitter.

Sterownik powinien zostać zrealizowany jako moduł jądra Linux w wersji 6.7.6. Moduł zawierający sterownik powinien nazywać się zsonet.ko. W katalogu zsonet powinny znajdować się:

  • źródła modułu

  • pliki Makefile i Kbuild pozwalające na zbudowanie modułu

  • krótki opis rozwiązania

Transmiter należy napisać w C lub C++. Po kompilacji powinien znajdować się w pliku wykonywalnym o nazwie transmitter. Program powinien kompilować się na dostarczonej maszynie wirtualnej. W katalogu transmitter powinny znajdować się:

  • źródła programu

  • plik Makefile kompilujący rozwiązanie

Rozwiązania prosimy nadsyłać na adres w.ciszewski@mimuw.edu.pl z kopią do a.jackowski@mimuw.edu.pl oraz m.matraszek@mimuw.edu.pl. Prosimy o umieszczenie [ZSO] w tytule wiadomości.

QEMU

Do użycia urządzenia ZSONet wymagana jest zmodyfikowana wersja qemu, dostępna w wersji źródłowej.

Aby skompilować zmodyfikowaną wersję qemu, należy:

  1. Sklonować repozytorium https://gitlab.uw.edu.pl/zso/2024z/qemu-zsonet.git

  2. git checkout zsonet

  3. Upewnić się, że są zainstalowane zależności: ncurses, libsdl, curl, a w niektórych dystrybucjach także ncurses-dev, libsdl-dev, curl-dev (nazwy pakietów mogą się nieco różnić w zależności od dystrybucji)

  4. Uruchomić ./configure z opcjami wedle uznania (patrz ./configure --help). Zalecamy flagi:

    --target-list=x86_64-softmmu --enable-virtfs --enable-gtk
    
  5. cd build

  6. Wykonać make (lub ninja, jeśli mamy zainstalowane)

  7. Zainstalować wykonując make install, lub uruchomić bezpośrednio (binarka to build/qemu-system-x86_64).

Aby zmodyfikowane qemu emulowało urządzenie ZSONet, należy przekazać mu opcje -netdev user,id=net_backend -device zsonet,netdev=net_backend.

Warto ponadto użyć do tego zadania innego obrazu maszyny: https://students.mimuw.edu.pl/ZSO/PUBLIC-SO/zso2024_debian12.qcow2.xz. Na obrazie używanym dotychczas jest zainstalowany Debian 11, w którym dostępna jest stara wersja liburing bez możliwości tworzenia socketów.

Wskazówki

Ze względu na charakter komunikacji sieciowej interfejs pliku nie pasuje dobrze do urządzeń sieciowych. Z tego powodu w Linuksie dla urządzeń sieciowych nie tworzy się pliku w /dev.

Urządzenie sieciowe jest reprezentowane przez strukturę net_device. Następujące funkcje i makra mogą się przydać podczas jego inicjalizacji:

  • alloc_etherdev - alokuje strukturę net_device

  • free_netdev - zwalnia strukturę net_device

  • SET_NETDEV_DEV - pozwala powiązać net_device z fizycznym urządzeniem (pci_dev.dev)

  • eth_hw_addr_set - ustawia adres MAC

  • register_netdev - rejestruje urządzenie sieciowe w systemie

  • unregister_netdev - wyrejestrowuje urządzenie sieciowe z systemu

  • warto też ustawić pola min_mtu oraz max_mtu

  • netdev_priv - daje dostęp do prywatnych danych sterownika

Oprócz tego istotne są funkcje:

  • netif_start_queue - informuje system podczas włączania interfejsu, że sterownik może przyjmować ramki do wysyłania

  • netif_stop_queue - wstrzymuje przekazywanie do sterownika ramek do wysłania

  • netif_wake_queue - wznawia przekazywanie do sterownika ramek do wysłania

Podczas wysyłania przydatne mogą być:

  • skb_copy_and_csum_dev - wpisuje do bufora dane ze struktury sk_buff wraz z sumą kontrolną (FCS)

  • dev_kfree_skb_any, dev_consume_skb_any, dev_consume_skb_any_reason - zwalnia wykorzystany sk_buff

  • do sterownika należy zapewnienie, by wysyłane ramki nie miały rozmiaru mniejszego niż minimalny dla Ethernetu (60 bajtów nie licząc) - mniejsze ramki należy uzupełnić zerami

Podczas odbierania przydatne mogą być:

  • netdev_alloc_skb - alokuje sk_buff

  • skb_copy_to_linear_data, skb_copy_to_linear_data_offset - wpisuje do sk_buff dane z bufora (należy tam umieścić również sumę kontrolną)

  • skb_put - oznacza obszar sk_buff zawierający ramkę

  • należy też uzupełnić pole skb.protocol używając eth_type_trans

  • netif_rx - przekazuje ramkę do systemu