This is an old revision of the document!
Pentru desfășurarea laboratorului pornim de la arhiva de sarcini a laboratorului. Descărcăm și decomprimăm arhiva în directorul so2/
din directorul home al utilizatorului student
de pe sistemul de bază (stația mjolnir
):
student@mjolnir:~$ cd so2/ student@mjolnir:~/so2$ wget http://elf.cs.pub.ro/so2/res/laboratoare/lab10-tasks.zip student@mjolnir:~/so2$ unzip lab10-tasks.zip student@mjolnir:~/so2$ tree lab10-tasks
În cadrul directorului lab10-tasks/
se găsesc resursele necesare pentru dezvoltarea exercițiilor de mai jos: fișiere schelet de cod sursă, fișiere Makefile și Kbuild, scripturi și programe de test.
Vom dezvolta exercițiile pe sistemul de bază (stația mjolnir
) și apoi le vom testa pe mașina virtuală QEMU. După editarea și compilarea unui modul de kernel îl vom copia în directorul dedicat pentru mașina virtuală QEMU folosind o comandă de forma
student@mjolnir:~/so2$ cp /path/to/module.ko ~/so2/qemu-vm/fsimg/root/modules/
unde /path/to/module.ko
este calea către fișierul obiect aferent modulului de kernel. Apoi vom porni, din directorul ~/so2/qemu-vm/
mașina virtuală QEMU folosind comanda
student@mjolnir:~/so2/qemu-vm$ make
După pornirea mașinii virtuale QEMU vom putea folosi comenzi în fereastra QEMU pentru a încărca și descărca modulul de kernel:
# insmod modules/module-name.ko # rmmod module/module-name
unde module-name
este numele modulului de kernel.
Ctrl+Alt+t
. Cele trei tab-uri de terminal îndeplinesc următoarele roluri:
~/so2/qemu-vm/
.student@mjolnir:~$ netcat -lup 6666
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ă iphdr folosind funcția ip_hdr, iar antetul TCP se obține într-o structură 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.
%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/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. Îl copiați în cadrul mașinii virtuale QEMU și apoi îl rulați obținând 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.
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.
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:
test_daddr
)
Pentru testare folosiți scriptul 1-2-netfilter/test-2.sh
. Acest script are nevoie de compilarea fișierului 1-2-netfilter/test.c
în executabilul test
. Compilarea o faceți pe sistemul fizic folosind comanda
make -f Makefile.test
Copiați modulul de kernel, împreună cu scriptul test-2.sh
și executabilul test
pe mașina virtuală QEMU pentru testare. Î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.
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 arhiva de sarcini a 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.
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.
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
. În urma rulării testului, se va afișa un socket TCP ascultând conexiuni pe portul 60000
.
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 sys_accept4 apelul sock->ops->accept
. Folosiți 0
ca valoarea pentru ultimul argument (flags
).
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.
getname
va fi socket-ul de tip conexiune, adică new_sock
, cel inițializat cu ajutorul apelului accept
.
Ultimul argument al funcției de tip 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
.
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
. 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.
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 arhiva de sarcini a 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.
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 control
și controllen
ale structurii struct msghdr
la NULL
și 0
.
Pentru transmiterea efectiva folosiți kernel_sendmsg.
Parametrii de transmitere a mesajului sunt preluați din kernel-space. Faceți, pentru structura de tip struct iovec
cast la struct kvec *
în apelul kernel_sendmsg
.
kernel_sendmsg
sunt 1
(numărul de vectori I/O) și len
(dimensiunea mesajului).
Pentru testare folosiți scriptul test-5.sh
și integrați executabilul static nc-static
așa cum este descris în continuare.
nc
din busybox din mașina virtuală QEMU nu are suport pentru UDP. De aceea vom folosi un alt executabil.
Pentru deschiderea unui socket UDP pe portul 60001 folosiți utilitarul netcat compilat static de la adresa http://www.stearns.org/nc/nc-static. Descărcați binarul pe sistemul fizic în directorul fsimg/root/
al mașini virtuale QEMU. Binarul poate fi descărcat cu ajutorul comenzii
wget http://www.stearns.org/nc/nc-static
Verificați că fișierul a fost descărcat corect verificând suma de control MD5 cu ajutorul comenzii
$ md5sum nc-static ec6aa710d3112808cb31a1d6ded775a1 nc-static
După pornirea mașinii virtuale QEMU, acordați executabilului nc-static
permisiuni de execuție folosind
chmod a+x nc-static
și apoi copiați executabilul nc-static
în /bin/nc
:
cp nc-static /bin/nc
Punt!
afișat în urma rulării testului test-5.sh
este un mesaj afișat de utilitarul netcat
în momentul în care procesul este oprit de rularea comenzii kill
în cadrul scriptului.