Category Archives: projekty

Dawne czasy – kernel

Na dziś dwie sprawy.
Pierwszakernel.
Przeglądając stos płyt leżących w jednym z kartonów za biurkiem, przypomniałem sobie o moim zajęciu sprzed kilku lat… chyba 3 lub 4 jeśli mnie pamięć nie myli. Wtedy z młodzieńczą pasją zabrałem się za pisanie własnego systemu operacyjnego. Oczywiście nie miałem o tym zielonego pojęcia. Jedyne co posiadałem to znajomość podstaw C++. Dopiero pisząc kernel tak naprawdę nauczyłem się dopiero C. Ale po kolei…
Po dzięki wędrówkom po kawiarenkach internetowych i szkolnych pracowniach (w tamtych czasach to były jedyne sposoby dostępne dla mnie, aby skorzystać z internetu) udało mi się zebrać trochę tutoriali i materiałów. W większości były to powtarzające się, absolutne podstawy, a ja chciałem czegoś więcej. Natchnąłem się na perełkę – stronę autorstwa bodajże Christophera Giesea – Operating System Development. Niestety już nie jest dostępną w sieci (kiedyś była pod adresem: http://my.execpc.com/~geezer). Zawierała kody źródłowe i dokumentacje, która była dla mnie wtedy idealna. No mniejsza o szczegóły. Wiadomo – były i upadki i wzloty. Kilka razy od zaczynałem początku. Finalnie udało mi się napisaćć kernel, który działał w 32 bitach na procesorach i386, obsługiwał tryb tekstowy VGA, wielowątkowość z wywłaszczaniem, dynamiczną alokacje pamięci, obsługę wyjątków i przerwań sprzętowych i modułów kernela (ELF, COFF i a.out). Część kodu napisałem własnoręcznie, część zaczerpnąłem z tutoriala (funkcja printf,obsługa plików coff, elf a.out, funkcje napisane w assemblerze i kilka innych).
Kernel, który znalazłem był ostatni raz kompilowany 15 sierpnia 2005, a więc chwilę temu. W kodzie jedynie zmieniłem kilka rzeczy, które ewidentnie „gryzły” w oczy. Udostępniam kod na nowej licencji BSD, więc kto chce niechaj pobiera. (Uwaga! Pliki spakowałem jak leci, więc jest tam niezły b.. nieporządek).
Druga – w sumie to już temat na osobną notkę.
P.S. Udało mi się znaleźć mirror stronki Giesea: http://geezer.osdevbrasil.net/osd/index.htm

Cap – metoda działania

Na dzień dobry musimy się przedstawić w sieci. Wysyłamy pakiet ARP_REPLY na adres FF:FF:FF:FF:FF:FF, uzupełniamy „nasz” adres sprzętowy i IP. Oczywiście mówię o adresie wirtualnego interfejsu, który tworzymy. Do pełni szczęścia potrzebujemy jeszcze adresu sprzętowego naszej bramy sieciowej. Ja wykrywałem go poprzez drobną sztuczkę, którą znalazłem na stronie z przykładami użycia Jpcap (a dokładnie tu: http://netresearch.ics.uci.edu/kfujii/jpcap/sample/Traceroute.java ). Polega ona na tym, że najpierw ustawiamy przechwytywanie pakietów TCP na wybrany przez nas adres, a następnie otwieramy tam połączenie poprzez standardowe API (w tym wypadku: new URL(”http://www.microsoft.com”).openStream().close(); ). Captor przechwyci pakiet, który będzie miał uzupełniony adres bramy.

Dla tego przykładu przyjmijmy:
Nasz wirtualny interfejs:
IP: 192.168.0.123
MAC: AB:CD:EF:FE:DC:BA
Syn: 100

Host, z którym chcemy się połączyć:
IP: 85.17.7.51
MAC – nie będzie nam potrzebny

Brama sieciowa:
MAC: 12:34:56:78:9A:BC
Syn: 300

Posiadając ten minimalny zasób wiedzy o otaczającym nas środowisku, możemy rozpocząć połączenie. Wysyłamy pakiet TCP z flagą SYN i wybranym przez nas numerem sekwencyjnym (np. 100). Jako adres fizyczny adresata podajemy MAC bramy.
Zapewne zaraz po wysłaniu naszego pakietu brama odpyta komputery w sieci, który z nich ma adres 192.168.0.123 wysyłając na adres rozgłoszeniowy pakiet ARP REQUEST. Odpowiadamy na niego poprzez APR REPLY na adres sprzętowy bramy. Po tym zabiegu brama powinna już pozwolić na połączenie.
Oczekujemy na pakiet od hosta. Powinien mieć numer odpowiedzi równy 101, oraz flagi SYN i ACK. Odpowiadamy na niego pakietem ACK z numerem odpowiedzi równym 301 (w naszym konkretnym przypadku).
Od tego momentu połączenie zostało ustanowione. Możemy już wysyłać pakiety z danymi, pamiętając o tym, że numer odpowiedzi jest równy numerowi sekwencyjnemu którego spodziewamy się (właściwiej byłoby powiedzieć: jesteśmy gotowi) otrzymać w następnym pakiecie. Analogicznie numer odpowiedzi który otrzymamy od hosta to sekwencja, którą on jest gotowy otrzymać. Numer sekwencyjny otrzymujemy dodając za każdym razem ilość przesyłanych danych w bajtach. Pierwszy numer sekwencyjny jest zazwyczaj wartością losową.
Jak widać ustanowienie połączenia nie jest wcale sprawą trudną, a daje wielkie możliwości. Omijanie limitów transferu jest chyba najprostszą metodą jego wykorzystania. Co więcej możemy podszyć się bez problemu pod każdy komputer w sieci lokalnej. Niby mało niebezpieczne – to przecież sieć lokalna, ale gdy to już jest sieć firmowa ? Wystarczy wpiąć się w jej dowolny punkt i… tą historię dokończcie sami. Do usłyszenia.

Cap – transfer bypasser

Blog się już zdążył trochę zakurzyć, więc postanowiłem dodać nowy wpis o programie, którego miałem czelność spłodzić jakiś miesiąc temu. Sam pomysł ma już rok. Zastanawiałem się wtedy nad możliwościami obejścia limitów transferu w sieci lokalnej. Często potrzebowałem zciągnąć duży plik z serwera HTTP w jak najszybszym czasie (np. obraz ISO), ale ograniczenie transferu czas ten niemiłosiernie wydłużało. Wiedząc, że sztywne ograniczenie pasma jest przydzielane do konkretnego interfejsu sieciowego łatwo możemy dojść od wniosku, że powielając interfejsy sieciowe i poprzez każdy z nich zaciągając innych fragment danego pliku (HTTP 1.1 pozwala na to) jednocześnie skracamy czas pobrania, a więc i sumaryczny transfer.

Do realizacji zabrałem się dopiero po dłuższym czasie. Wziąłem w obroty bibliotekę Jpcap. Prosta i wygodna biblioteka do przechwytywania i generowania pakietów TCP/IP/Ethernet itp. w języku Java. Wykorzystuje do tego sławna bibliotekę WinPcap/libpcap. Więcej informacji na stronie projektu: http://netresearch.ics.uci.edu/kfujii/jpcap/doc/ .
Potrzebowałem napisać kilka klas do obsługi ruchu TCP. Przez to że samodzielnie generowałem pakiety i wysyłałem je „surowo” w sieć, cały ruch musiał być obsłużony przez moją aplikacje. Jpcap był tylko interfejsem pozwalającym przyjmować i wysyłać pakiety. Po kilku godzinach przegryzania się przez dokumenty RCF zabrałem się do pisania. Okazało się, że obsłużyć ruch TCP/IP na minimalnym potrzebnym mi poziomie wcale tak trudno nie jest: 3-way handshake jak i odpowiedzi na ruch ARP nie sprawiły żadnego problemu. Trochę więcej czasu spędziłem na wymyśleniu w miarę wydajnego układania pakietów w buforze (niestety to ja musiałem dbać o ich kolejność), obsłudze duplikacji pakietów i próśb o ponowne przesłanie tych które nie dotarły. Pomocna przydała się lektura kodu systemu Jnode (http://jnode.org). W pewnym momencie zastanawiałem się nawet nad przeportowaniu jego stosu TCP/IP do mojego projektu, ale po kilkugodzinnej jego lekturze stwierdziłem, że potrzebnych będzie mi niewiele tych funkcjonalności, które są tam zawarte, a poznanie architektury i śledzenie błędów w takim przeportowanym stosie pochłonie więcej pracy niż napisanie stosu „po swojemu”.
Po napisaniu obsługi ruchu TCP/IP oraz ARP najtrudniejszy fragment projektu był już za mną. Później powstały klasy obsługujące HTTP (też w minimalnej, absolutnie minimalnej postaci), klasa zajmująca się zarządzaniem wątkami pobierania i na końcu prosty interfejs. Sam program uruchamiamy z linii poleceń poprzez „java –jar Cap.jar [parametry] URL”, gdzie URL to adres zasobu a parametry to:

–help [-h] pomoc
–version [-v] informacje o wersji
–file FILE [-f FILE] plik wyjściowy o nazwie FILE
–dump-interfaces wylistuj dostępne interfejsy sieciowe
–config FILE [-c FILE] ścieżka do pliku konfiguracyjnego
–verbosity-level LEVEL stopień “rozgadania” programu
[-vl LEVEL] od 0 do 6

Konfiguracja interfejsów sieciowych umieszczona jest w pliku cfg.ini (domyślnie). Oto przykładowa zawartość:

cap.version=0interfaces.count=3
interfaces.device=0

interface.0.name=komp_tomka
interface.0.mac=00:a0:d2:1a:74:a0
interface.0.ip=192.168.0.200

interface.1.name=komp_kasi
interface.1.mac=00:a0:d2:1a:74:a1
interface.1.ip=192.168.0.201

interface.2.name=komp_radka
interface.2.mac=00:a0:d2:1a:74:a2
interface.2.ip=192.168.0.202

interface.3.name=komp_elzbietki
interface.3.mac=00:a0:d2:1a:74:a3
interface.3.ip=192.168.0.203

Objaśnienie:
cap.version wersja programu dla którego przeznaczona jest konfiguracja
interfaces.count ilość wirtualnych interfejsów to utworzenia
interfaces.device numer fizycznego urządzenia, z którego będziemy korzystali

interface.X.name nazwa dla interfejsu X
interface.X.mac MAC interfejsu X
interface.X.ip IP interfejsu X

Samego programu nie mogę nawet nazwać alphą. Ma wiele błędów, ale zamieszczam go w szeroko pojętych celach edukacyjnych. Ufam, że kiedyś może się komuś przydać. Publikuje go na nowej licencji BSD, więc użycie mojego kodu w waszych projektach nie powinno stanowić problemu. Tutaj zamieszczam archiwum z programem. W pliku Cap.jar są też źródła. Miłej lektury.