Programowanie sieciowe

Po zamianie sal ćwiczenia będą odbywać się w sali G-1-09. Salę mamy zarezerwowaną od 18:00 do 20:00, więc zajęcia zaczynać się będą o 18:00, ale z zastrzeżeniem że pierwsze pół godziny będzie poświęcone wyłącznie na komentowanie zadań domowych, udzielanie indywidualnych porad, itp. W ten sposób ci z Państwa, którzy mają inne zajęcia tuż przed ćwiczeniami z PS będą mogli przychodzić dopiero na 18:30 (czyli na godzinę, o której ćwiczenia miały się oryginalnie zaczynać) i nic nie stracą. Oczywiście jeśli ktoś przychodzi na 18:30, to wychodzi dopiero o 20:00.

Listę ocen cząstkowych można znaleźć w pliku pub.txt.

Można mieć cztery nieusprawiedliwione nieobecności bez ponoszenia konsekwencji. Przy większej ich liczbie proszę się spodziewać dodatkowego przepytywania, dodatkowych zadań, itp. rzeczy sprawdzających czy na pewno znacie wszystkie zagadnienia poruszane na zajęciach.

Literatura

  1. W. Richard Stevens, „UNIX Network Programming” (2 tomy), wydane po polsku jako „UNIX: programowanie usług sieciowych” przez WNT w 2000 i 2001 roku.

    Gniazdka sieciowe BSD omówione są w tomie 1.

  2. Douglas E. Comer, David L. Stevens, „Internetworking with TCP/IP, Vol. III”, wydane po polsku jako „Sieci komputerowe TCP/IP, tom 3: Programowanie w trybie klient-serwer: wersja BSD”, WNT 1997.

  3. Standardy POSIX oraz SUS (Single UNIX Specification).

    W chwili obecnej jest to jeden i ten sam standard pod dwiema różnymi nazwami. Jest czymś w rodzaju wspólnego mianownika dla uniksopodobnych systemów operacyjnych. Specyfikuje zbiór plików nagłówkowych oraz funkcji dostępnych w bibliotece standardowej języka C — kod korzystający tylko z tych funkcji ma dużą szansę skompilować się i poprawnie działać m.in. na Linuksie, OS X, iOS i Androidzie.

    Specyfikacja POSIX.1-2008 / SUSv4 dostępna jest online, i obejmuje funkcje potrzebne do obsługi gniazdek (socket, bind, itd.).

  4. Manual systemowy.

    Zbiór stron opisujących różne aspekty danego systemu operacyjnego: polecenia wydawane z linii komend, funkcje jądra, funkcje biblioteki standardowej, itp. Dostęp poprzez polecenie man: man socket, man bind, itd. Odstępstwa w zachowaniu funkcji od tego, co mówi POSIX powinny być wyraźnie odnotowane. Podobnie wyraźnie oznaczone powinny być specyficzne dla danego systemu funkcje rozszerzające POSIX (dla Linuksa patrz np. man epoll_create).

    Manual podzielony jest na sekcje, i może się zdarzyć że w dwóch różnych sekcjach są strony o tej samej nazwie. Poleceniu man można podać dodatkowy argument wskazujący o którą sekcję chodzi: man 2 socket, man 7 socket.

    Najnowsza wersja linuksowego manuala jest dostępna online.

  5. Dokumentacja glibc.

    W systemach linuksowych standardową biblioteką jest przeważnie GNU C Library. Ma ona swoją własną dokumentację (dostępna online), tworzoną niezależnie od manuala systemowego. Jeden z rozdziałów podręcznika jest poświęcony gniazdkom.

  6. Dokumenty RFC.

    Definicje protokołów, w oparciu o które działa Internet. Dostępne online na witrynie https://www.rfc-editor.org/.

Środowisko programistyczne

Oficjalnym środowiskiem są narzędzia zainstalowane na linuksowych komputerach w pracowniach studenckich oraz na serwerze spk-ssh.if.uj.edu.pl (można się na niego zalogować z domu). Oddawany kod musi być uruchamialny przy ich pomocy.

W szczególności kod w języku C musi się kompilować bez ostrzeżeń za pomocą gcc -std=c99 -pedantic -Wall. Obowiązuje standard języka C z 1999, bo SUSv4 zakłada dostępność kompilatora z nim zgodnego.

Proszę w miarę możności korzystać tylko z funkcji bibliotecznych ujętych w SUSv4. Gniazdka początkowo pojawiły się w systemach z rodziny BSD, i wiele przykładów w podręcznikach i na witrynach korzysta z funkcji udostępnianych przez bibliotekę standardową BSD, ale nieobecnych w standardzie POSIX. Pamiętajcie że zazwyczaj można je łatwo zastąpić POSIX-owymi odpowiednikami (zamiast bzero użyć memset, itp.).

Zadania

Aby zaliczyć zajęcia musicie Państwo oddać serię zadań programistycznych. Przy każdym z nich podany jest dzień, w którym najpóźniej należy je oddać. Zachęcam jednak Państwa do implementowania i wysyłania ich wcześniej — daje to Wam czas na poprawienie ewentualnych błędów.

Z uwagi na liczbę osób w grupie niemożliwe jest odbieranie zadań podczas zajęć, nie wystarczy na to czasu. Będziecie mi je więc wysyłać, a ja raz na tydzień, w niedzielę albo w poniedziałek rano, będę je przeglądał. Jeśli zauważę jakąś często występującą usterkę, to omówię ją podczas wieczornych zajęć. Podczas zajęć będzie też można zwrócić się do mnie o indywidualny komentarz.

Z powyższego wynika że nie należy zwlekać z wysłaniem gotowego zadania do poniedziałku. Jeśli wyślecie je w poniedziałek, to przejrzę i ocenię je dopiero za tydzień. A jeśli wyślecie w sobotę, to już za dwa dni będziecie wiedzieli czy zadanie było poprawnie zaimplementowane, i w razie usterek będziecie mogli je szybko usunąć i wysłać poprawione rozwiązanie.

Poczta czasem nie dochodzi, zadania proszę więc mi wysyłać przy pomocy specjalnego programiku. Na przykład, jeśli na rozwiązanie zadania składają się dwa pliki o nazwach client.c i server.c, to po zalogowaniu się do Linuksa należy przejść do katalogu je zawierającego i wydać polecenie

/home/palacz/kolokwia/submit-files client.c server.c

Wysłanie zadania po terminie skutkuje obniżeniem oceny o stopień za każdy rozpoczęty tydzień spóźnienia. Zwolnienia lekarskie itp. są akceptowane, ale pod warunkiem że są ciągłe. Jeśli ktoś pokaże mi trzy jednodniowe zwolnienia na trzy kolejne poniedziałki, to i tak obniżę ocenę do ndst — zadanie można było przecież wysłać w któryś inny dzień tygodnia.

Rzeczą, na którą bardzo zwracam uwagę przy ocenianiu zadań jest obsługa błędów i sytuacji wyjątkowych. Jeśli jakaś funkcja może zwrócić kod statusu oznaczający niepowodzenie, to musi być to obsługiwane. Jeśli spodziewaliśmy się łańcucha tekstowego zawierającego dwie liczby rozdzielone spacją, a od procesu na drugim końcu połączenia dostaliśmy jakieś binarne śmieci, to trzeba to zauważyć i odpowiednio zareagować.

1. Wizytówki serwerów

Termin oddania finalnej wersji rozwiązania: 2017-03-20. Obowiązuje język C.

Wiele protokołów sieciowych korzysta z czegoś w rodzaju wizytówki: po nawiązaniu połączenia między klientem a serwerem serwer, nie czekając na żadne dane od klienta, wysyła linię tekstu. Przez „linię tekstu” należy rozumieć ciąg drukowalnych znaków ASCII zakończony dwuznakiem \r\n (choć czasem linia zakończona jest tylko znakiem \n). Informacje zawarte w tej linii przeważnie pozwalają klientowi zorientować się którą wersję protokołu obsługuje serwer, jakie oprogramowanie działa na serwerze, itp.

Napisz program-klienta, który będzie łączył się (użyj socket i connect) z usługą wskazaną argumentami podanymi w linii komend (adres IPv4 w argv[1], numer portu TCP w argv[2]), drukował na ekranie wizytówkę zwróconą przez serwer, i zamykał połączenie. Pamiętaj o zasadzie ograniczonego zaufania i weryfikuj czy odebrane bajty to na pewno drukowalne znaki zanim je prześlesz na stdout.

Przetestuj program łącząc się z następującymi usługami:

W tym ostatnim przypadku klient powinien się zawiesić, bo serwery HTTP nie wysyłają wizytówek. Trzeba będzie ręcznie przerwać jego nieskończone oczekiwanie naciskając kombinację klawiszy Ctrl-C.

Uwaga: nie próbuj testować klienta uruchamiając go na serwerze spk-ssh.if.uj.edu.pl, bo nie zadziała. Ten serwer dostępowy dla bezpieczeństwa jest obwarowany firewallami blokującymi próby otwierania połączeń wychodzących do większości usług (z powyższej listy przepuszcza tylko HTTP).

Napisz drugi program, który będzie atrapą serwera. Powinien stworzyć sobie gniazdko TCP nasłuchujące na porcie o numerze podanym jako argv[1] (użyj socket, bind i listen), następnie w pętli czekać na przychodzące połączenia (accept), wysyłać ciąg "Hello, world!\r\n" jako swoją wizytówkę, zamykać odebrane połączenie, i wracać na początek pętli. Program nie kończy działania z własnej inicjatywy, trzeba go przerwać przez Ctrl-C.

Sprawdź czy oba programy prawidłowo ze sobą współpracują. Uruchom je na tym samym komputerze, tak aby jako adres IPv4 można było podać 127.0.0.1 (to zawsze oznacza lokalną maszynę).

2. Porównanie połączeń strumieniowych i datagramowych

Termin oddania finalnej wersji rozwiązania: 2017-03-27 2017-04-03. Obowiązuje język C.

Zaimplementuj parę współdziałających programów. Klient wysyła porcje danych, serwer je odbiera, oba mają w kodzie wstawione sztuczne opóźnienia (sleep) pomiędzy kolejnymi wywołaniami funkcji write/read. Oba programy muszą wypisywać na stderr komunikaty sygnalizujące momenty, w których rozpoczyna i kończy się wykonywanie funkcji przesyłających dane, oraz raportować wyniki przez nie zwracane.

Sprawdź eksperymentalnie co się będzie działo gdy pauzy w działaniu serwera będą kilkukrotnie dłuższe niż w kliencie, i co się będzie działo gdy sytuacja będzie odwrotna. Przetestuj również co się dzieje gdy zmieniasz liczbę bajtów transferowanych jednym wywołaniem write/read; sprawdź działanie np. dla porcji danych rozmiaru 512 B, 16 KiB i 128 KiB (rozmiar zmieniaj niezależnie w kliencie i serwerze). Dla ułatwienia sobie eksperymentów możesz, ale nie musisz, zaimplementować specyfikowanie tych parametrów z linii komend.

Przerób programy tak aby korzystały z gniazdek UDP (zamiast write/read użyj sendto/recvfrom), powtórz eksperymenty.

Sporządź w formie zwykłego pliku tekstowego notatkę podsumowującą Twoje obserwacje i dołącz ją do oddawanych programów.

Nieobowiązkowy problem dla zainteresowanych: czy i kiedy można użyć read lub write z gniazdkiem UDP? A recvfrom i sendto z gniazdkiem TCP? I do czego można użyć recv oraz send?

3. Wysyłanie listów do serwera SMTP

Termin oddania finalnej wersji rozwiązania: 2017-04-03 2017-04-10. Dowolny język programowania.

Poczta elektroniczna jest jedną z najstarszych usług. Jest też dobrym przykładem dydaktycznym, bo i listy, i protokół do przesyłania ich z serwera na serwer są czysto tekstowe. Program korzystający z SMTP nie musi więc kodować ani dekodować danych binarnych, wystarczą mu funkcje przetwarzające łańcuchy tekstowe.

Tekstowy charakter protokołu pozwala też łatwo z nim eksperymentować. Linuksowe dystrybucje zazwyczaj zawierają program netcat (może być też dostępny pod nazwą nc lub ncat). Pozwala on nawiązać połączenie ze wskazanym serwerem, a następnie wysyła do niego znaki wpisane z klawiatury; odpowiedzi zwracane przez serwer są drukowane na ekranie. Identyczna funkcjonalność wbudowana jest w windowsowy program PuTTY, którego część z Państwa używa do łączenia się z spk-ssh.if.uj.edu.pl. Wystarczy jako typ połączenia wybrać „Raw” zamiast „SSH”.

Proszę zapoznać się z dokumentami RFC opisującymi protokół transferu poczty oraz format wiadomości pocztowych. Można skorzystać z pierwszych ich wersji z 1982 roku: RFC 821 i RFC 822. Te stare wersje są o kilkanaście stron krótsze od współczesnych, podstawowe rzeczy w nich opisane się nie zmieniły, i będziecie mieli przyjemność zobaczyć w druku nazwę „ARPANET” (to z tej sieci wyrósł znany nam Internet).

Następnie proszę spróbować ręcznie wysłać do samego siebie jednozdaniowy list. Jeśli Wasz adres pocztowy ma końcówkę „@uj.edu.pl” proszę połączyć się z adresem 149.156.73.222 lub 149.156.81.153, a jeśli „@student.uj.edu.pl” to proszę łączyć się z 213.199.154.42 lub 213.199.180.138. Połączenie TCP, numer portu 25 (oficjalnie przydzielony dla SMTP).

Przypominam że spk-ssh.if.uj.edu.pl ma firewall blokujący połączenia wychodzące, i nie pozwala łączyć się z serwerami SMTP.

Na koniec proszę napisać program, który wysyła taki sam list. Proszę zwrócić uwagę że w przypadku ręcznego wysyłania ewentualne błędy na poziomie połączenia TCP obsługiwałby netcat, a błędy na poziomie SMTP — człowiek (przerwalibyście wydawanie kolejnych komend gdyby serwer na którąś z nich odpowiedział komunikatem błędu, prawda?). Pisząc program trzeba będzie w jego kodzie umieścić obsługę obu kategorii błędów.

4. Korzystanie z usługi DNS

Termin oddania finalnej wersji rozwiązania: 2017-04-24. Dowolny język programowania. Nawiązuje do poprzedniego zadania, więc część z Państwa pewnie zmodyfikuje poprzednio napisane programy — jest to jak najbardziej OK.

Numeryczne adresy serwerów DNS użyte w poprzednim zadaniu nie wzięły się z powietrza, lecz zostały wyszukane w DNS. Przechowywanie odwzorowań z nazwy serwera na jego numeryczny adres i z adresu na nazwę jest najszerzej znanym zastosowaniem DNS, ale nie jedynym. DNS potrafi przechowywać wiele innych typów informacji. Uruchom następujące polecenie aby zobaczyć wszystkie dane związane z nazwą „uj.edu.pl”:

dig uj.edu.pl any

Wśród wyświetlonych wyników powinny być dwa rekordy typu MX (mail exchange). Zawierają one nazwy serwerów odbierających pocztę przychodzącą do tej domeny — to z którymś z nich należy się połączyć gdy chcemy wysłać list na adres kończący się na „@uj.edu.pl”. Aby móc to zrobić trzeba w DNS wyszukać rekordy typu A związane z nazwami tych serwerów, tam znajdziemy potrzebne nam adresy IPv4.

W poprzednim zadaniu poznaliście Państwo zasady prowadzenia dialogu z serwerem SMTP. Jednym z używanych poleceń było „RCPT TO”, serwer odpowiada na nie kodem 250 jeśli jest gotów zaakceptować list przeznaczony dla danego odbiorcy. Serwer może też odpowiedzieć kodem błędu, jeśli np. w nazwie odbiorcy jest literówka lub konto pocztowe zostało skasowane. Można to wykorzystać do weryfikacji poprawności adresów pocztowych.

Proszę napisać program, któremu jako argumenty podaje się pewną liczbę adresów e-mailowych. Program dla każdego z nich powinien znajdować serwer pocztowy odpowiedzialny za daną domenę, łączyć się z nim, i rozpoczynać procedurę wysyłki listu ale tylko do momentu uzyskania odpowiedzi na „RCPT TO”, następnie zamiast „DATA” należy wysłać „QUIT” i zamknąć połączenie. W zależności od zwróconego kodu należy wypisać na ekran odpowiedni komunikat, oznajmiający że adres wydaje się być OK albo że adres został odrzucony.

Przykładową implementację procedury wyszukującej w DNS potrzebne informacje znajdziecie Państwo w plikach find_mx.h, find_mx.c i demo_mx.cpp. Jest dość obszernie okomentowana, zapraszam do jej przestudiowania. Możecie wykorzystać ją w swoim programie, albo bezpośrednio odwołując się do funkcji find_mail_exchanges, albo (jeśli piszecie w czymś innym niż C/C++, np. w Perlu bądź Pythonie) wywołując program demo_mx z odpowiednim argumentem i przechwytując wyniki, które normalnie zostałyby wypisane na ekran.

Można też oczywiście napisać swój własny kod znajdujący MX-y. W sieci dostępnych jest wiele bibliotek wspomagających komunikację z DNS, od bardzo prostych do bardzo skomplikowanych. Możecie wybrać którąś z nich, choć zwracam uwagę że raczej nie będzie ona zainstalowana na komputerach w pracowni i trzeba będzie samodzielnie ją ściągnąć oraz w jakiś sposób dołączyć do programu.

Dokształcanie się w tematach związanych z WWW

W maju będziemy mówić o WWW, więc przez długi majowy weekend proponuję podszkolić się z tych tematów — przyda się gdy zaczniemy robić zadania z tym związane.

Warte uwagi tematy:

5. Pobieranie stron WWW

Termin oddania finalnej wersji rozwiązania: 2017-05-22. Dowolny język programowania (polecam Python, w nim to łatwo napisać).

Zadanie jest dość proste. Chcemy napisać programik, który sprawdzałby czy dana witryna działa poprawnie. Należy pobrać stronę spod podanego adresu (powiedzmy spod http://www.fais.uj.edu.pl/dla-studentow/ogloszenia), nie zapominając o zweryfikowaniu czy na pewno udało się ją poprawnie pobrać (status 200) i czy to jest strona HTML (typ text/html). Następnie sprawdzamy czy rzeczywiście jest to spodziewana strona z ogłoszeniami, a nie np. komunikat o wewnętrznym błędzie portalu UJ — to można zweryfikować sprawdzając czy w pobranej treści znajduje się określone słowo bądź zdanie. Programik musi zwracać z main wynik odzwierciedlający wynik sprawdzenia.

6. Analiza webAPI któregoś z popularnych serwisów

Termin oddania raportu: 2017-06-05.

Załóżmy że chcesz napisać program, który regularnie sprawdza czy na stronie określonego użytkownika nie pojawiły się nowe wiadomości. Jeśli tak, należy je pobrać i zapisać do plików (jedna wiadomiość – jeden plik). Użytkownik ma swoją stronę na Facebooku/Twitterze/innym serwisie społecznościowym.

Nie chcemy ściągać i analizować strony HTML, bo takie podejście jest bardzo kruche (Facebook może niespodziewanie zmienić szablon, wg którego generuje strony, i nasz program przestanie działać). Zamiast tego chcemy użyć oficjalnego API oferowanego przez ten serwis społecznościowy.

Zadanie: znajdź w dokumentacji Twojego ulubionego serwisu społecznościowego opis ich webAPI, zapoznaj się z nim i zaproponuj sposób zaimplementowania naszego programu. Zwróć uwagę że czasem biblioteki implementujące dane webAPI są oferowane tylko dla niektórych języków programowania, że niektóre serwisy wymagają logowania się lub wcześniejszego zarejestrowania programu, itp.

7. Przymiarki do napisania własnego serwera webAPI

Termin oddania finalnej wersji rozwiązania: 2017-06-12.

To zadanie wymaga zaznajomienia się z językiem PHP, i sprawdzenia jak w nim obsłużyć rozmaite rodzaje zapytań HTTP. Ostatecznym celem jest napisanie serwera webAPI, więc trzeba sprawdzić w jaki sposób PHP udostępnia dane o rodzaju zapytania (GET, PUT, POST czy może coś jeszcze innego?), jak dla GET zwrócić wynik a dla PUT odebrać treść przesłaną przez przeglądarkę, i gdzie można znaleźć informacje o tym jak wyglądał URL.