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:
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
networking
.
Similar, putem genera și scheletul pentru un singur exercițiu, atribuind valoarea <lab_name>/<task_name>
variabilei LABS
.
tools/labs/skels
.
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 ...
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ă.
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
Ctrl+Shift+t
. Cele trei tab-uri de terminal îndeplinesc următoarele roluri:
~/so2/linux/tools/labs
.~/so2/linux/
cu sursele nucleului unde putem folosi Vim și cscope pentru parcurgerea codului sursă.student@eg106-pc:~$ netcat -lup 6666
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.
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.
%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.
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/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.
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.
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
. 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
.
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 penultimul argument (flags
) și false
pentru ultimul argument (kern
).
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.
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.
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.
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.
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.
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