This is an old revision of the document!


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
</code>

</note>

===== Exerciții =====

<note important>
Î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.
</note>

==== 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  [[:so2:laboratoare:lab10#netfilter|netfilter]].

[[:so2:laboratoare:lab10#structura_sk_buff|Structura sk_buff]] dă acces la antetele unui pachet. Antetul IP se obține într-o structură [[http://lxr.free-electrons.com/source/include/uapi/linux/ip.h?v=4.9#L85|struct iphdr]] folosind funcția [[http://lxr.free-electrons.com/source/include/linux/ip.h?v=4.9#L23|ip_hdr]], iar antetul TCP se obține într-o structură [[http://lxr.free-electrons.com/source/include/uapi/linux/tcp.h?v=4.9#L24|struct tcphdr]] folosind funcția [[http://lxr.free-electrons.com/source/include/linux/tcp.h?v=4.9#L28|tcp_hdr]].

[[http://www.eventhelix.com/Realtimemantra/Networking/Tcp.pdf|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.

<note tip>
Pentru afișarea adresei IP sursă folosiți formatul ''%pI4'' al funcției ''printk''. Detalii găsiți în [[https://www.kernel.org/doc/Documentation/printk-formats.txt|documentația nucleului]] (secțiunea ''IPv4 addresses''). Un exemplu de snippet de cod care să folosească ''%pI4'' este:<code c>
printk("IP address is %pI4\n", &iph->saddr);
</code>

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%%''.
</note>

Portul TCP sursă este, în antentul TCP, în formatul [[http://en.wikipedia.org/wiki/Byte_order#Endianness_in_networking|network byte-order]]. Parcurgeți secțiunea [[:so2:laboratoare:lab10#conversii|Conversii]]. Folosiți [[http://lxr.free-electrons.com/source/include/linux/byteorder/generic.h?v=4.9#L141|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.

După rulare ar trebui să obținem un output similar celui de mai jos:<code>
# ./test-1.sh
[  229.783512] TCP connection initiated from 127.0.0.1:44716
Should show up in filter.
Check dmesg output.
</code>

==== 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 [[:so2:laboratoare:lab04#ioctl|laboratorul 4]]. Adresa transmisă din user space este în [[http://en.wikipedia.org/wiki/Endianness#Endianness_in_networking|network byte-order]], deci **nu** va fi nevoie de conversie.

<note hint>
Adresa IP trimisă prin ''ioctl'' este trimisă prin adresă, nu prin valoare. Adresa trebuie stocată în variabila ''ioctl_set_addr''. Pentru stocare folosiți [[http://lxr.free-electrons.com/source/include/asm-generic/uaccess.h?v=4.9#L258|copy_from_user]].
</note>

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.

În urma rulării testului veți obține un output similar celui de mai jos:<code>
# ./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.
</code>

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 [[:so2:laboratoare:lab10#operatii_asupra_structurii_socket|Operații asupra structurii socket]] și [[:so2:laboratoare:lab10#structura_proto_ops|Structura proto_ops]] din laborator.

Pentru a crea un socket folosiți funcția [[http://lxr.free-electrons.com/source/net/socket.c?v=4.9#L1212|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.

<note tip>
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 [[http://lxr.free-electrons.com/source/net/socket.c?v=4.9#L1360|sys_bind]] și [[http://lxr.free-electrons.com/source/net/socket.c?v=4.9#L1391|sys_listen]].
</note>

<note tip>
Pentru al doilea argument al apelului de tip ''listen'' (''backlog'') folosiți macro-ul ''LISTEN_BACKLOG''.
</note>

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 [[http://lxr.free-electrons.com/source/net/socket.c?v=4.9#L571|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 [[:so2:laboratoare:lab10#operatii_asupra_structurii_socket|Operații asupra structurii socket]] și [[:so2:laboratoare:lab10#structura_proto_ops|Structura proto_ops]] din laborator. Pentru echivalentul ''accept'' în kernel-space, consultați handler-ul de apel de sistem [[http://lxr.free-electrons.com/source/net/socket.c?v=4.9#L1418|sys_accept4]]. Urmăriți în implementarea [[http://lxr.free-electrons.com/source/net/socket.c?v=4.9#L1418|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 [[http://lxr.free-electrons.com/source/net/socket.c?v=4.9#L1587|sys_getpeername]].

<note tip>
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''.
</note>

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 [[:so2:laboratoare:lab10#operatii_asupra_structurii_socket|Operații asupra structurii socket]] și [[:so2:laboratoare:lab10#structura_proto_ops|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 [[http://lxr.free-electrons.com/source/net/socket.c?v=4.9#L1664|sys_send]] sau secțiunea [[:so2:laboratoare:lab10#transmitereareceptia_unui_mesaj|Transmiterea/recepția unui mesaj]].

<note important>
Câmpul ''msg_name'' al structurii [[http://lxr.free-electrons.com/source/include/linux/socket.h?v=4.9#L41|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 [[http://lxr.free-electrons.com/source/include/linux/socket.h?v=4.9#L41|struct msghdr]] la valoarea ''0''.

Inițializați respectiv câmpurile ''msg_control'' și ''msg_controllen'' ale structurii [[http://lxr.free-electrons.com/source/include/linux/socket.h?v=4.9#L41|struct msghdr]] la ''NULL'' și ''0''.
</note>

Pentru transmiterea efectivă folosiți [[http://lxr.free-electrons.com/source/net/socket.c?v=4.9#L635|kernel_sendmsg]].

Parametrii de transmitere a mesajului sunt preluați din kernel-space. Faceți, pentru pointer-ul la structura de tip [[http://lxr.free-electrons.com/source/include/uapi/linux/uio.h?v=4.9#L16|struct iovec]] cast la un pointer la structura [[http://lxr.free-electrons.com/source/include/linux/uio.h?v=4.9#L18|struct kvec]] în apelul [[http://lxr.free-electrons.com/source/net/socket.c?v=4.9#L635|kernel_sendmsg]].

<note important>
Ultimii doi parametri ai funcției [[http://lxr.free-electrons.com/source/net/socket.c?v=4.9#L635|kernel_sendmsg]] sunt ''1'' (numărul de vectori I/O) și ''len'' (dimensiunea mesajului).
</note>

Pentru testare folosiți scriptul ''test-5.sh'' și integrați executabilul static ''nc-static'' așa cum este descris în continuare. Scriptul este copiat pe mașina virtuală la ''make copy'' doar dacă este marcat ca executabil.

<note important>
Utilitarul ''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 ''skels/networking/5-udp-sock/''. Binarul poate fi descărcat cu ajutorul comenzii<code>
wget http://www.stearns.org/nc/nc-static
</code>

Verificați că fișierul a fost descărcat corect verificând suma de control MD5 cu ajutorul comenzii<code>
$ md5sum nc-static ec6aa710d3112808cb31a1d6ded775a1 nc-static </code>

Înainte de pornirea mașinii virtuale QEMU, acordați executabilului nc-static permisiuni de execuție folosind

chmod a+x nc-static

, după care rulați comanda make copy pentru copierea pe mașina virtuală.

După pornirea mașinii virtuale, copiați executabilul nc-static în /bin/nc:

cp nc-static /bin/nc

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
 punt!

Mesajul 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.

Soluții

so2/laboratoare/lab10/exercitii.1524730100.txt.gz · Last modified: 2018/04/26 11:08 by relu.dragan
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