Laborator 10: Exerciții

Pregătirea laboratorului

Pentru rezolvarea laboratorului, vom lucra în același director din care pornim mașina virtuală (~/so2/linux/tools/labs).

Pașii de rezolvare sunt următorii:

  • pregătirea scheletului de laborator
  • compilarea modulelor de Kernel
  • copierea modulelor pe mașina virtuală
  • pornirea mașinii virtuale și testarea modulelor

Pregătirea scheletului de laborator

Pentru rezolvarea laboratorului trebuie sa activam suportul de netfilter din kernel. In meniul deschis cu make menuconfig activati optiunea Networking support/Networking options/Network packet filtering framework (Netfilter).

Scheletul de laborator este generat din sursele din directorul tools/labs/templates. Putem genera scheletele pentru toate laboratoarele folosind următoarea comanda:

tools/labs $ make skels

Pentru a genera scheletul pentru un singur laborator, vom folosi variabila de mediu LABS:

tools/labs $ make clean
tools/labs $ LABS=<lab name> make skels

Numele laboratorului curent este networking.

Similar, putem genera și scheletul pentru un singur exercițiu, atribuind valoarea <lab_name>/<task_name> variabilei LABS.

Scheletul este generat în directorul tools/labs/skels.

Compilarea modulelor

Comanda make build compilează toate modulele din directorul skels.

student@eg106:~/so2/linux/tools/labs$ make build
echo "# autogenerated, do not edit " > skels/Kbuild
echo "ccflags-y += -Wno-unused-function -Wno-unused-label -Wno-unused-variable " >> skels/Kbuild
for i in ./networking/1-2-netfilter/kernel ./networking/3-4-tcp-sock ./networking/5-udp-sock; do echo "obj-m += $i/" >> skels/Kbuild; done
...

Copierea modulelor pe mașina virtuală

Putem copia modulele generate pe mașina virtuală folosind target-ul copy al comenzii make, atunci când mașina virtuală este oprită.

student@eg106:~/so2/linux/tools/labs$ make copy
student@eg106:~/so2/linux/tools/labs$ make boot

Alternativ, putem copia fișierele prin scp, pentru e evita repornirea mașinii virtuale. Pentru detalii despre folosirea interacțiunea prin rețea cu mașina virtuală citiți Interacțiunea cu mașina virtuală.

Testarea modulelor

Modulele generate sunt copiate pe mașina virtuală în directorul /home/root/skels/<lab_name>.

root@qemux86:~/skels/networking# ls
1-2-netfilter  3-4-tcp-sock  5-udp-sock
root@qemux86:~/skels/networking# ls 1-2-netfilter/kernel/
filter.ko

După pornirea mașinii virtuale QEMU vom putea folosi comenzi în fereastra QEMU (sau în minicom) pentru a încărca și descărca modulul de kernel:

root@qemux86:~# insmod skels/<lab_name>/<task_name>/<module_name>.ko
root@qemux86:~# rmmod skels/<lab_name>/<task_name>/<module_name>.ko

Pentru dezvoltarea laboratorului, este recomandat să folosim trei terminale sau, mai bine, trei tab-uri de terminal. Pentru a deschide un nou tab de terminal folosim combinația de taste Ctrl+Shift+t. Cele trei tab-uri de terminal îndeplinesc următoarele roluri:

  1. În primul tab de terminal dezvoltăm modulul de kernel: editare, compilare, copiere în directorul dedicat pentru mașina virtuală QEMU. Lucrăm în directorul aferent rezultat în urma decomprimării arhivei de sarcini a laboratorului.
  2. În al doilea tab de terminal pornim mașina virtuală QEMU și apoi testăm modulul de kernel: încărcare/descărcare modul, rulare teste. Lucrăm în directorul aferent mașinii virtuale: ~/so2/linux/tools/labs.
  3. În al treilea tab de terminal accesăm directorul ~/so2/linux/ cu sursele nucleului unde putem folosi Vim și cscope pentru parcurgerea codului sursă.
    student@eg106-pc:~$ netcat -lup 6666

Exerciții

Înainte de începerea rezolvării laboratorului, rulați comanda git pull --rebase in directorul ~/so2/linux, pentru a obține ultima versiune a scheletului de laborator.

De asemenea, trebuie să vă asigurați că este activat suportul pentru netfilter în kernel, prin opțiunea CONFIG_NETFILTER. Pentru aceasta, rulați make menuconfig în directorul linux și verificați opțiunea Network packet filtering framework (Netfilter) din Networking support → Networking options. În cazul în care nu era activată, activați-o (ca builtin, nu modul extern – trebuie să fie marcată cu *).

În cazul în care ați activat-o, trebuie să forțati recompilarea kernel-ului înainte de pornirea mașinii virtuale. Pentru aceasta, ștergeți fisierul linux/arch/x86/boot/bzImage. După acest pas, la pornirea mașinii virtuale se va declanșa automat recompilarea.

1. [2p] Afișare pachete în kernel space

Creați un modul de kernel care afișează adresa și portul sursă pentru pachetele TCP care inițiază o conexiune spre exterior. Porniți de la scheletul din 1-2-netfilter/ și completați zonele marcate cu TODO 1, având în vedere observațiile de mai jos.

Va trebui să înregistrați un hook netfilter de tipul NF_INET_LOCAL_OUT așa cum este explicat în secțiunea netfilter.

Structura sk_buff dă acces la antetele unui pachet. Antetul IP se obține într-o structură struct iphdr folosind funcția ip_hdr, iar antetul TCP se obține într-o structură struct tcphdr folosind funcția tcp_hdr.

Diagrama explică cum se realizează o conexiune TCP. Pachetul de inițiere a unei conexiuni are flagul SYN din atentul TCP activ, iar flagul ACK inactiv.

Pentru afișarea adresei IP sursă folosiți formatul %pI4 al funcției printk. Detalii găsiți în documentația nucleului (secțiunea IPv4 addresses). Un exemplu de snippet de cod care să folosească %pI4 este:

printk("IP address is %pI4\n", &iph->saddr);

Atunci când folosim formatul %pI4 argumentul către printk este un pointer. De unde și construcția &iph->saddr (cu operatorul & - ampersand) în loc de iph->saddr.

Portul TCP sursă este, în antentul TCP, în formatul network byte-order. Parcurgeți secțiunea Conversii. Folosiți ntohs pentru conversie.

Pentru testare folosiți fișierul 1-2-netfilter/user/test-1.sh. Testul generează o conexiune către stația locală (pe localhost), conexiune ce va fi interceptată și afișată de modulul de kernel. Scriptul este copiat pe mașina virtuală la make copy doar dacă este marcat ca executabil. Scriptul folosește utilitarul compilat static netcat din skels/networking/netcat; acest executabil trebuie să aibă permisiuni de execuție.

După rulare ar trebui să obținem un output similar celui de mai jos:

# ./test-1.sh
[  229.783512] TCP connection initiated from 127.0.0.1:44716
Should show up in filter.
Check dmesg output.

2. [2p] Precizare adresă destinație

Extindeți modulul de la exercițiul 1 astfel încât să permită precizarea unei adrese destinație dorite prin intermediul unei rutine ioctl MY_IOCTL_FILTER_ADDRESS. O să afișați doar pachetele care conțin adresa destinație precizată. Pentru rezolvarea acestui task completați zonele marcate cu TODO 2 și urmăriți precizările de mai jos.

Pentru implementarea rutinei ioctl trebuie să completați funcția my_ioctl. Revedeți secțiunea din laboratorul 4. Adresa transmisă din user space este în network byte-order, deci nu va fi nevoie de conversie.

Adresa IP trimisă prin ioctl este trimisă prin adresă, nu prin valoare. Adresa trebuie stocată în variabila ioctl_set_addr. Pentru stocare folosiți copy_from_user.

Pentru compararea adreselor, completați funcția test_daddr. Se vor folosi adresele în network byte-order; fără a fi nevoie conversia adreselor (dacă sunt egale de la stânga la dreapta vor fi egale și invers).

Funcția test_daddr trebuie apelată în hook-ul netfilter pentru a afișa pachetele de inițiere de conexiune pentru care adresa destinație este cea transmisă prin rutina de tip ioctl. Pachetele de inițiere de conexiune au flag-ul syn activat și flag-ul ack dezactivat în antetul TCP. Adică trebuie să verificați două lucruri:

  • flag-urile TCP
  • adresa destinație a pchetului (folosind funcția test_daddr)

Pentru testare folosiți scriptul 1-2-netfilter/user/test-2.sh. Acest script are nevoie de compilarea fișierului 1-2-netfilter/user/test.c în executabilul test. Compilarea se face automat pe sistemul fizic la rularea comenzii make build. Scriptul de testare este copiat pe mașina virtuală doar dacă este marcat ca executabil. Scriptul folosește utilitarul compilat static netcat din skels/networking/netcat; acest executabil trebuie să aibă permisiuni de execuție.

În urma rulării testului veți obține un output similar celui de mai jos:

# ./test-2.sh
[  797.673535] TCP connection initiated from 127.0.0.1:44721
Should show up in filter.
Should NOT show up in filter.
Check dmesg output.

Testul comandă filtrarea pachetelor întâi către adresa IP 127.0.0.1 și apoi către adresa IP 127.0.0.2. Primul pachet de inițiere de conexiune (către 127.0.0.1) este interceptat și afișat de filtru, în vreme ce al doilea (către 127.0.0.2) nu este interceptat.

3. [2p] Socket TCP în starea listening

Creați un modul de kernel care creează un socket TCP ce ascultă conexiuni pe portul 60000 pe interfața de loopback (în init_module). Folosiți scheletul din 3-4-tcp-sock/ din scheletul laboratorului și completați zonele marcate cu TODO 1 ținând cont de observațiile de mai jos.

Parcurgeți secțiunile Operații asupra structurii socket și Structura proto_ops din laborator.

Pentru a crea un socket folosiți funcția sock_create_kern.

Socketul sock este un server socket și trebuie pus în starea listening. Adică pe socket trebuie aplicate operațiile de tip bind și listen. Pentru echivalentul bind, listen în kernel space va trebui să apelați funcții de tipul sock->ops->...; exemple de astfel de funcții pe care să le apelați sunt sock->ops->bind, sock->ops-listen etc.

Pentru exemplu de apel a funcțiilor sock->ops->bind, respectiv sock->ops->listen, urmăriți cum sunt acestea apelate în handler-ele de apel de sistem sys_bind și sys_listen.

Pentru al doilea argument al apelului de tip listen (backlog) folosiți macro-ul LISTEN_BACKLOG.

Să aveți în vedere eliberarea socket-ul în funcția de tip exit a modulului și în zona cu label-uri de eroare; folosiți funcția sock_release.

Pentru testare, rulați scriptul 3-4-tcp_sock/test-3.sh. Scriptul este copiat pe mașina virtuală la make copy doar dacă este marcat ca executabil.

În urma rulării testului, se va afișa un socket TCP ascultând conexiuni pe portul 60000.

4. [2p] Acceptarea unei conexiuni în kernel space

Extindeți modulul de la exercițiul anterior pentru a permite acceptarea unei conexiuni din exterior (nu trebuie transmis mesaj, doar acceptată o nouă conexiune). Trebuie completate zonele marcate cu TODO 2.

Parcurgeți secțiunile Operații asupra structurii socket și Structura proto_ops din laborator. Pentru echivalentul accept în kernel-space, consultați handler-ul de apel de sistem sys_accept4. Urmăriți în implementarea lnet_sock_accept cum este folosit apelul sock->ops->accept. Folosiți 0 ca valoarea pentru ultimul argument (flags).

Socket-ul nou creat (new_sock) trebuie creat cu apelul sock_create_lite și apoi îi trebuie configurate operațiile folosind

newsock->ops = sock->ops;

Afișați adresa și portul socket-ului destinație. Pentru a afla numele peer-ului unui socket (adresa sa), consultați handler-ul de apel de sistem sys_getpeername.

Primul argument al funcției sock->ops->getname va fi socket-ul de tip conexiune, adică new_sock, cel inițializat cu ajutorul apelului accept.

Ultimul argument al funcției sock->ops->getname va fi 1, însemnând că dorim aflarea de infomații despre capătul destinație (remote end sau peer).

Afișați adresa peer-ului (indicată de variabila raddr) folosind macro-ul print_sock_address() definit mai sus în fișier.

Eliberați socket-ul nou creat (în urma acceptării conexiunii) în funcția de exit a modulului și în zona cu label-uri de eroare. După adăugarea codului care face accept în funcția de inițializare a modulului, operația de insmod se va bloca până se va realiza o conexiune. Puteți debloca folosind netcat pe portul respectiv. În consecință, scriptul de testare de la exercițiul precedent nu va ma funcționa.

Pentru testarea, adică inițierea unei conexiuni din exterior, folosiți scriptul 3-4-tcp_sock/test-4.sh. Scriptul este copiat pe mașina virtuală la make copy doar dacă este marcat ca executabil.

Nu se va afișa ceva special (în buffer-ul kernel-ului). Reușita testului va fi dată de realizarea conexiunii. Apoi folosiți Ctrl+c pentru a încheia rularea scriptului de testare și apoi puteți elimina modulul din kernel.

5. [3p] Socket UDP sender

Creați un modul de kernel care creează un socket UDP și transmite mesajul din macro-ul MY_TEST_MESSAGE pe socket către adresa loopback pe portul 60001.

Porniți de la scheletul 5-udp-sock/ din scheletul laboratorului.

Citiți secțiunile Operații asupra structurii socket și Structura proto_ops din laborator.

Pentru a vedea cum se face transmiterea de mesaje în kernel-space, consultați handler-ul de apel de sistem sys_send sau secțiunea Transmiterea/recepția unui mesaj.

Câmpul msg_name al structurii struct msghdr trebuie inițializat la adresa destinație (de tipul struct sockaddr *), iar câmpul msg_namelen la dimensiunea adresei.

Inițializați câmpului msg_flags a structurii struct msghdr la valoarea 0.

Inițializați respectiv câmpurile msg_control și msg_controllen ale structurii struct msghdr la NULL și 0.

Pentru transmiterea efectivă folosiți kernel_sendmsg.

Parametrii de transmitere a mesajului sunt preluați din kernel-space. Faceți, pentru pointer-ul la structura de tip struct iovec cast la un pointer la structura struct kvec în apelul kernel_sendmsg.

Ultimii doi parametri ai funcției kernel_sendmsg sunt 1 (numărul de vectori I/O) și len (dimensiunea mesajului).

Pentru testare folosiți scriptul test-5.sh. Scriptul este copiat pe mașina virtuală la make copy doar dacă este marcat ca executabil. Scriptul folosește utilitarul compilat static netcat din skels/networking/netcat; acest executabil trebuie să aibă permisiuni de execuție.

La o implementare corectă, rularea script-ului test-5.sh va conduce la afișarea mesajului kernelsocket, într-un output precum cel de mai jos:

/root # ./test-5.sh 
+ pid=1059
+ sleep 1
+ nc -l -u -p 60001
+ insmod udp_sock.ko
kernelsocket
+ rmmod udp_sock
+ kill 1059

Soluții

so2/laboratoare/lab10/exercitii.txt · Last modified: 2018/05/07 17:36 by darius.neatu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0