Autor wpisu: Śpiechu, dodany: 16.05.2012 19:26, tagi: php
Od tygodnia na poważnie wziąłem się za rozpracowywanie protokołów sieciowych. Dzisiaj schodzimy na poziom najniższy jaki się da w programowaniu, czyli zer i jedynek. Ja jako osoba za 2 miesiące rozpoczynająca zawodowo przygodę z programowaniem (ojej, wydało się ;-) ) muszę mieć dosyć dobre pojęcie jak to się dzieje, że wpisuję adres www w pasku adresu przeglądarki, naciskam enter i pokazuje się Fejsbuk. Oczywiście zwykli użytkownicy internetu nie muszą wiedzieć jak to dokładnie działa w myśl zasady „nie muszę być mechanikiem samochodowym żeby jeździć autem”.
Wobec tego na warsztat wziąłem na początek rzecz dosyć prostą: ICMP, a nawet tylko jego wycinek pod nazwą Echo request/Echo reply. Komunikaty ICMP stanowią podstawę działania internetu. Każda maszyna podłączona do sieci potrafi zadawać pytania i otrzymywać odpowiedzi w postaci ICMP. Tak naprawdę zdubluję funkcjonalność wbudowaną w każdy system operacyjny pod powszechnie znaną nazwą ping.
Ręczne tworzenie pakietów w PHP nie ma oczywiście większego sensu poza edukacyjnym (no chyba, że tworzymy coś nietypowego lub dostosowujemy się do już istniejącego protokołu). Jeśli potrzebujemy coś „pingnąć”, wywołujemy polecenie ping shella z poziomu skryptu PHP i tyle.
W Gist zamieściłem klasę gotową do użytku. Na Linuksie będzie problem z jej odpaleniem, gdyż bez roota nie wywołamy funkcji socket_create()
. Ten temat jest na tyle ciekawy, że zostawię sobie na następny wpis.
Sama struktura protokołu jest dosyć prosta. Mamy 6 elementów:
- 8 bitów typ żądania (
0x08
w przypadku echo request,0x00
w przypadku echo reply), - 8 bitów kod żądania (
0x00
), - 16 bitów suma kontrolna (tzw. internet checksum, o którym trochę niżej),
- 16 bitów identyfikator (losowa liczba z zakresu
0x000 - 0xFFFF
), - 16 bitów nr sekwencyjny (
0x00
), - 8– bitów dane (znaki ASCII).
Operując niskopoziomowo musimy zaprzyjaźnić się z funkcjami pack()
i unpack()
w celu stworzenia i odczytu reprezentacji bitowej ciągu. W przypadku ICMP będzie to wyglądało tak:
const PACKET_REQUEST_TEMPLATE = 'CCnnnA*'; const PACKET_RESPOND_TEMPLATE = 'Ctype/Ccode/nchecksum/nuid/nseq/A*message';
Widać, że 1 bajt możemy odzwierciedlić w postaci typu unsigned char, a 2 bajty w postaci unsigned short.
Teraz może nasunąć się pytanie jak wypełnić pole checksum, skoro jest gdzieś w środku? Odpowiedź jest prosta: w pole wpisujemy tymczasowo 0x00
, obliczamy sumę kontrolną dla całości, a następnie zastępujemy wynikiem. Jest to dokładnie 3 i 4 bajt. Jak obliczyć sumę kontrolną? Jest do tego RFC z 1988 r. pod nazwą Computing the Internet Checksum. Jednak zanim zniechęcicie się suchym żargonem informatycznym wytłumaczę po ludzku:
- Cały pakiet ćwiartujemy na kawałki po 16 bitów,
- sumujemy wszystko,
- jeśli w wyniku powstaje nam jakaś nadwyżka w postaci 17 i więcej bitu, odcinamy ją, a następnie dodajemy do pozostałych 16tu,
- ponownie sprawdzamy czy nie powstała nam nadwyżka,
- odwracamy wynik.
Zapis w PHP:
protected function computeChecksum($packet) { // treat the whole packet as 16 bits unsigned short integers $seqPer16bits = unpack('n*', $packet); $sum = array_sum($seqPer16bits); // if there is a carry above 16 bit, add it at the beginning $sum = ($sum >> 16) + ($sum & 0xFFFF); // double check if there is no new carry after previous addition $sum += ($sum >> 16); // return 16 bits negate return pack('n', ~$sum); }
Jak „wkleić” wynik w dobre miejsce? Mała sztuczka: