Differences

This shows you the differences between two versions of the page.

Link to this comparison view

rl:labs:10 [2022/01/12 12:00]
florin.stancu [Pregătire infrastructură de laborator]
rl:labs:10 [2025/12/08 14:06] (current)
vlad_andrei.badoiu [Utilizare]
Line 1: Line 1:
-~~SHOWSOLUTION~~ 
 ~~NOTOC~~ ~~NOTOC~~
  
-====== Laborator 10. Clienți de rețea ​======+====== Laborator 10. Advanced Linux Networking ​======
  
-===== Cunoștințe și abilități ce vor fi dobândite =====+În cele ce urmează vom trece prin elementele principale ce se întâlnesc în networking pe Linux. După acest laborator vom avea o înțelegere a modului în care funcționează networking-ul pe containere și ce folosesc în spate utilitare precum ''​ip''​.
  
-  * Identificarea porturilor deschise pe un sistem +==== Pregatire infrastructură de laborator ====
-  * Folosirea clienților de rețea în linia de comandă în Linux (''​netcat'',​ ''​wget'',​ ''​curl'',​ ''​mail''​) +
-  * Accesarea serviciilor de web, transfer ​de fișiere (FTP) și e-mail+
  
-===== Cheat sheet =====+<code bash> 
 +# ATENȚIE: update_lab nu funcționează de pe root, folosiți student inițial 
 +student@host:​~#​ update_lab --force 
 +student@host:​~#​ start_lab netns 
 +</​code>​ 
 + 
 +Verificați ca în home să aveți directorul ''​sock-diag/''​. 
 + 
 +==== Bridge ​==== 
 + 
 +Un bridge Linux se comportă ca un switch. Acesta transmite pachete între interfețele care sunt conectate la el. Este utilizat de obicei pentru transmiterea pachetelor pe routere, pe gateway-uri sau între mașini virtuale și spații de nume de rețea pe un host. De asemenea, suportă STP, filtre VLAN și multicast snooping. 
 + 
 +În diagramă putem vedea mai multe tipuri de interfețe: TUN, TAP, VETH. Astăzi vom lucra doar cu interfețe de tip VETH, pe care le vom discuta în cele ce urmează. 
 + 
 +{{:​rl:​labs:​bridge.png?​300|}} 
 + 
 + 
 +**TUN (tunnel)** operează la nivelul 3, ceea ce înseamnă că pachete pe care le vei primi de la descriptorul de fișier vor fi bazate pe IP. Datele scrise înapoi către dispozitiv trebuie să fie și ele sub formă de pachet IP. 
 + 
 +**TAP (network tap)** operează în mod asemănător cu TUN, însă în loc să poată scrie și primi doar pachete de nivel 3 către/de la descriptorul de fișier, poate utiliza pachete ethernet brute. De obicei, vei vedea dispozitive TAP folosite de virtualizarea KVM/Qemu, unde un dispozitiv TAP este atribuit unei interfețe virtuale guest în timpul creării. 
 + 
 + 
 +API-ul de utilizare din CLI este prezentat in code snipper-ul de mai jos. 
 + 
 +<code bash> 
 +# Creaza un bridge 
 +sudo ip link add name br0 type bridge 
 + 
 +# Porneste bridge-ul 
 +sudo ip link set br0 up 
 + 
 +# Conecteaza o interfata la bridge 
 +sudo ip link set some_interface master br0 
 + 
 +# Adauga o adresa IP pe bridge 
 +# (nu este necesara atunci cand bridge-ul este folosit doar pentru comutare) 
 +sudo ip addr add 192.168.1.1/​24 dev br0 
 + 
 +# Vizualizare bridge 
 +ip link show master br0
  
-  * {{:​rl:​rl_cheatsheet.pdf|Cheat Sheet}}+# Eliminare interfata de la bridge 
 +sudo ip link set some_interface nomaster
  
-===== Pregătire infrastructură de laborator ===== +# Stergere bridge 
-<​hidden>​  +sudo ip link delete br0 type bridge
-  * Vom rula o masină virtuală local folosind [[https://​www.virtualbox.org/​ | VirtualBox]] sau [[https://​vmware.pub.ro/​ | VMWare]] (Detalii mai jos). Descărcați arhiva cu resursele mașinii de [[ https://​rl-checker.root.sx/​share/​RL_2021_VM.zip | aici]]. +
- * Vom rula o masină virtuală în [[http://​cloud.curs.pub.ro | cloud-ul facultății]] ​ [[https://​cloud-controller.grid.pub.ro | link direct OpenStack]] +
-  * Pentru a porni o astfel de masină urmăriți tutorialul de la [[https://​cloud.curs.pub.ro/​about/​tutorial-for-students/​ | această adresă]]. +
-    * Când creați instanța de mașină virtuală (în fereastra "​Launch instance"​):​ +
-      * Dați ce nume doriți instanței (e.g., "Lab 7"); +
-      * În meniul din stânga, la Sources, căutați ''​RL 2020''​ și apăsați pe săgeată sus pentru a o selecta; +
-      * La flavor, este suficient ''​m1.small''​ (ultimul din listă), nu aveți nevoie de mai multe procesoare sau memorie mare; +
-      * Conectați-vă la ''​fep'',​ apoi pe mașina virtuală proaspăt pornită prin IP-ul privat (''​10.9.x.x''​):<​code bash> +
-ssh -i ~/​.ssh/​openstack.key student@10.9.x.x+
 </​code>​ </​code>​
-</​hidden>​ 
  
-  * Vom rula o masină virtuală ori pe **OpenStack**, ori **local** ca până acumDetalii la [[:​rl:​labs:​09|laboratorul anterior]] ;) +De asemenea, se pot folosi apelurile de sistem din Linux pentru a interacționa cu API-ul de bridge. Sunt două metode: prin **IOCTL** (varianta veche) și prin socket-uri ​**Netlink**. Mai jos este un exemplu cu IOCTL.
  
-<​spoiler ​Detalii rulare masina virtuala VMware local+<​spoiler ​ioctl_create_bridge.c
-  * Deschideți VMware și porniți mașina virtuală ''​RL_2021''​+<code C> 
-  * Dorim să folosim terminalul sistemului fizic și să lucrăm peste SSH cu mașina virtuală (denumită și ''​host''​)În acest fel vom putea folosi copy-paste în terminal sau alte facilitățiPentru aceasta urmați pașii de mai jos: +#include <stdio.h> 
-    * Autentificați-vă în mașina virtuală folosind contul ''​root''​ cu parola ''​student''​+#include <string.h> 
-    * Aflați adresa IP a mașinii virtuale de pe interfața ''​eth0'':​<code bash+#include <​sys/​ioctl.h> 
-root@host:~ifconfig eth0+#include <net/if.h> 
 +#​include ​<linux/​if_bridge.h
 +#include <​unistd.h>​ 
 +#include <​fcntl.h>​ 
 + 
 +int main() { 
 +    int sock; 
 +     
 +    sock = socket(AF_INET,​ SOCK_STREAM,​ 0); 
 +    if (sock < 0) { 
 +        perror("​socket"​);​ 
 +        return 1; 
 +    } 
 +     
 +    if (ioctl(sock,​ SIOCBRADDBR,​ "​br0"​) < 0) { 
 +        perror("​Failed to create bridge"​);​ 
 +        close(sock);​ 
 +        return 1; 
 +    } 
 +     
 +    printf("​Bridge br0 created successfully\n"​);​ 
 +     
 +    close(sock);​ 
 +    return 0; 
 +}
 </​code>​ </​code>​
-    * De pe sistemul fizic, deschideți un terminal și realizați o sesiune SSH folosind:<​code bash> 
-student@mjolnir:​~$ ssh student@$ADRESA_IP_MV 
-</​code>​unde ''​$ADRESA_IP_MV''​ este adresa IP a mașinii virtuale așa cum ați obținut-o mai sus. 
-    * Schimbati utilizatorul curent ca ''​root''​ folosind comanda <code bash>​student@host:​~$ sudo su</​code>​ 
 </​spoiler>​ </​spoiler>​
  
-<spoiler Detalii rulare masina virtuala VirtualBox local> +==== VETH ====
-  * Deschideți VirtualBox și porniți mașina virtuală ''​RL_2021''​. +
-  * Dorim să folosim terminalul sistemului fizic și să lucrăm peste SSH cu mașina virtuală (denumită și ''​host''​). În acest fel vom putea folosi copy-paste în terminal sau alte facilități. Pentru aceasta urmați pașii de mai jos: +
-    * Vom adauga o regula de port forwarding pentru a putea accesa masina prin SSH de pe statia Host +
-    * Cu masina virtuala oprita deschidem **Settings -> Network -> Advanced -> Port Forwarding** si adaugam urmatoarea regula: +
-    {{ :​rl:​labs:​port_forward_virtual_box.png?​600 | Port forwarding pentru acces SSH de pe Host }}+
  
-    * De pe sistemul fizicdeschidețun terminal și realizați ​sesiune SSH folosind:<code bash> +O altă componentă importantă este VETH (virtual Ethernet)o interfață virtuală de Ethernet. VETH-urile vin mereu în perechi, cum este prezentat în diagrama de mai jos. 
-student@mjolnir:​~$ ssh -p 10022 student@localhost+ 
 +{{:​rl:​labs:​veth.png?​300|}} 
 + 
 +O pereche VETH functioneaza ca un tunel, si este deseori folosit ca un tunel intre doua namespace-uri. 
 + 
 +Pachetele transmise pe interfață din pereche sunt primite imediat pe cealaltă interfață. Când oricare dintre interfețe este inactivă, starea legăturii perechii devine inactivă. 
 + 
 +API-ul de CLI este urmatorul: 
 + 
 +<code bash> 
 +# Creeaza o pereche de veth 
 +ip link add veth0 type veth peer name veth1 
 + 
 +# Porneste interfetele 
 +ip link set veth0 up 
 +ip link set veth1 up 
 + 
 +# Adauga adrese IP pe interfete 
 +ip addr add 10.0.0.1/24 dev veth0 
 +ip addr add 10.0.0.2/24 dev veth1
 </​code>​ </​code>​
-    ​Schimbati utilizatorul curent ​ca ''​root'' ​folosind ​comanda <code bash>student@host:~sudo su</​code>​+ 
 +==== Namespaces ==== 
 + 
 +Ultima componenta pe care o vom studia astazi este network namespace-ul (NS). 
 + 
 +Sistemul de operare Linux partajează un singur set de interfețe de rețea și intrări în tabela de rutare. Chiar daca putem intrari in tabela de rutare folosind rutarea bazată pe politici , asta nu schimbă fundamental faptul că setul de interfețe de rețea și tabelele/​intrările de rutare sunt partajate pe întregul sistem de operare.  
 + 
 +Namespace-urile de rețea (network namespace) schimbă această ipoteză fundamentală. Cu namespace-urile de rețea, putem avea instanțe diferite și separate de interfețe de rețea și tabele de rutare care operează independent una de alta. 
 + 
 +Un exemplu de utilizare sunt containerele,​ care folosesc namespace-uri pentru izolarea interfetelor de networking. 
 + 
 +==== Utilizare ==== 
 + 
 +In cele ce urmeaza vom implementa diagrama de mai jos: 
 + 
 +{{:​rl:​labs:​rl_ns_diagram.png?​500|}} 
 + 
 +**Crearea de namespace-uri** 
 + 
 +Pentru a crea un namespace, vom folosi utilitarul ''​ip''​. 
 + 
 +<code bash> 
 +ip netns add <nume namespace>​ 
 +</​code>​ 
 + 
 +Vom crea un nou namespace cu numele ''​ns1''​. Pentru a afișa lista de namespace-uri,​ vom rula: 
 + 
 +<code bash> 
 +ip netns list 
 +</​code>​ 
 + 
 +**Asignarea interfețelor la un network namespace** 
 + 
 +Vom folosi interfețe virtuale. Vom începe prin a crea o pereche de interfețe veth0 <-> veth1: 
 + 
 +<code bash> 
 +ip link add veth0 type veth peer name veth1 
 +</​code>​ 
 + 
 +Interfețele vor fi vizibile ​ca orice altă interfață pe Linux. 
 +<code bash> 
 +ip link list 
 +</​code>​ 
 + 
 +Ar trebui sa fie listata o pereche de interfețe veth. Momentan, ambele aparțin de namespace-ul ​''​global''​, împreună cu interfețele fizice. 
 + 
 +Următorul pas este de a adăuga veth1 la namespace-ul ''​ns1''​. 
 + 
 +<code bash> 
 +ip link set veth1 netns ns1 
 +</​code>​ 
 + 
 +Dacă rulam comanda ​**ip link list**, vom vedea că interfața ''​veth1''​ a dispărut din listă. Aceasta se află acum în namespace-ul ''​ns1'',​ așa că o vom putea vedea doar din acel namespace:​ 
 + 
 +<​code>​ 
 +ip netns exec ns1 ip link list 
 +</​code>​ 
 + 
 +Prima parte, ip netns exec, este modul în care executam comanda intr-un namespace de rețea diferit. 
 +Următorul este namespace-ul specific în care comanda ar trebui să fie rulată (în acest caz, namespace-ul ns1). 
 +A treia parte reprezinta comanda propriu-zisă care este executată în namespace. În acest caz, vrem să vedem interfețele din namespace-ul ''​ns1'',​ așa că rulam ''​ip link list''​. 
 + 
 +**Configurarea interfețelor in network namespaces** 
 + 
 +Acum că veth1 a fost mutat în namespace-ul ''​ns1'',​ trebuie să configurăm efectiv acea interfață. Vom folosi comanda ''​ip netns exec'',​ de data aceasta pentru a configura interfața ''​veth1''​ în namespace-ul ''​ns1'':​ 
 + 
 +<code bash> 
 +ip netns exec ns1 ip addr add 10.1.1.1/24 dev veth1 
 +ip netns exec ns1 ip link set dev veth1 up 
 +</​code>​ 
 + 
 +**Configurarea interfețelor în namespace-ul global** 
 + 
 +Trebuie să configurăm și interfața ''​veth0''​ care a rămas în namespace-ul global: 
 + 
 +<code bash> 
 +ip addr add 10.1.1.2/24 dev veth0 
 +ip link set dev veth0 up 
 +</​code>​ 
 + 
 +**Crearea unui al doilea namespace** 
 + 
 +Vom crea acum un al doilea namespace cu numele ''​ns2'':​ 
 + 
 +<code bash> 
 +ip netns add ns2 
 +</​code>​ 
 + 
 +**Crearea unui bridge pentru interconectarea namespace-urilor** 
 + 
 +Vom crea un bridge virtual care va permite comunicarea între cele două namespace-uri:​ 
 + 
 +<code bash> 
 +ip link add br0 type bridge 
 +ip link set dev br0 up 
 +</​code>​ 
 + 
 +**Crearea interfețelor pentru ns2 și conectarea la bridge** 
 + 
 +Vom crea o nouă pereche de interfețe virtuale pentru ''​ns2'':​ 
 + 
 +<code bash> 
 +ip link add veth2 type veth peer name veth3 
 +</​code>​ 
 + 
 +Vom conecta ''​veth0''​ (din namespace-ul global) la bridge: 
 + 
 +<code bash> 
 +ip link set veth0 master br0 
 +</​code>​ 
 + 
 +Vom muta ''​veth3''​ în namespace-ul ''​ns2'':​ 
 + 
 +<code bash> 
 +ip link set veth3 netns ns2 
 +</​code>​ 
 + 
 +Vom conecta ''​veth2''​ (care a rămas în namespace-ul global) la bridge: 
 +<code bash> 
 +ip link set veth2 master br0 
 +ip link set dev veth2 up 
 +</​code>​ 
 + 
 +**Configurarea interfețelor în ns2** 
 + 
 +Vom configura interfața ''​veth3''​ în namespace-ul ''​ns2'':​ 
 +<code bash> 
 +ip netns exec ns2 ip addr add 10.1.1.3/24 dev veth3 
 +ip netns exec ns2 ip link set dev veth3 up 
 +ip netns exec ns2 ip link set dev lo up 
 +</​code>​ 
 + 
 +De asemenea, activăm interfața loopback în ''​ns1'':​ 
 + 
 +<code bash> 
 +ip netns exec ns1 ip link set dev lo up 
 +</​code>​ 
 + 
 +**Testarea conectivității cu ping** 
 + 
 +Acum putem testa conectivitatea între cele două namespace-uri folosind ''​ping'':​ 
 + 
 +<code bash> 
 +ip netns exec ns1 ping -c 3 10.1.1.3 
 +</​code>​ 
 + 
 +Ar trebui să vedem pachete transmise cu succes între ''​ns1''​ (10.1.1.1) și ''​ns2''​ (10.1.1.3). 
 + 
 +**Testarea comunicării cu netcat (nc)** 
 + 
 +Pentru a testa comunicarea bidirecțională,​ vom folosi ''​netcat''​. În ''​ns2'',​ vom porni un server care ascultă pe portul 5000: 
 + 
 +<code bash> 
 +ip netns exec ns2 nc -l 5000 
 +</​code>​ 
 + 
 +Într-un terminal separat, din ''​ns1'',​ vom conecta la serverul din ''​ns2'':​ 
 + 
 +<code bash> 
 +ip netns exec ns1 nc 10.1.1.3 5000 
 +</​code>​ 
 + 
 +Acum putem scrie mesaje în clientul din ''​ns1''​ și acestea vor apărea în serverul din ''​ns2'',​ demonstrând conectivitatea între cele două namespace-uri izolate prin intermediul bridge-ului. 
 + 
 +==== Netlink ==== 
 + 
 +Netlink este un API din Linux utilizat pentru comunicarea inter-proces (IPC) atât între kernel și procesele userspace, cât și între diferite procese userspace, într-un mod similar socket-urilor de domeniu Unix. 
 + 
 +Introdus inițial pentru a rezolva problema ioctl-urilor (API-ul de ''​ioctl''​ necesita modificări în kernel pentru fiecare nou protocol de comunicare),​ Netlink este mecanism extensibil de comunicare între spațiul utilizator și kernel. Spre deosebire de ''​ioctl'',​ Netlink permite adăugarea de noi funcționalități fără a modifica codul kernel-ului,​ evitând astfel procesul de aprobare și integrare în kernel. Acesta a fost ulterior adaptat pentru a interacționa cu mai multe subsisteme. 
 + 
 +La fel ca socket-urile de domeniu Unix și spre deosebire de socket-urile INET, comunicarea Netlink nu poate traversa granițele gazdei. Netlink oferă o interfață standard bazată pe socket-uri pentru procesele userspace și un API pentru nucleu destinat utilizării interne de către modulele nucleului. Inițial, Netlink a folosit familia de socket-uri AF_NETLINK. Netlink este conceput pentru a fi un succesor mai flexibil al ioctl; RFC 3549 descrie protocolul în detaliu. 
 + 
 +Netlink este folosit, printre altele, de ''​ip route''​ sau ''​iptables''​. Mai jos gasim un exemplu de cod ce face toggle up <-> down la ''​some_interface''​. 
 + 
 +<spoiler switch_inferface_state.c>​ 
 +<code C> 
 +#include <​stdio.h>​ 
 +#include <​stdlib.h>​ 
 +#include <​unistd.h>​ 
 +#include <​string.h>​ 
 +#include <​time.h>​ 
 +#include <​errno.h>​ 
 + 
 +#include <​netinet/​in.h>​ 
 +#include <​netinet/​tcp.h>​ 
 + 
 +#include <​linux/​sockios.h>​ 
 +#include <​linux/​if.h>​ 
 +#include <​linux/​if_link.h>​ 
 +#include <​linux/​rtnetlink.h>​ 
 + 
 +#define ALIGNTO 4 
 +#define ALIGN(len) (((len)+ALIGNTO-1) & ~(ALIGNTO-1)) 
 +#define ATTR_HDRLEN ALIGN(sizeof(struct nlattr)) 
 +#define SOCKET_BUFFER_SIZE (sysconf(_SC_PAGESIZE) < 8192L ? sysconf(_SC_PAGESIZE) : 8192L) 
 + 
 +int main() 
 +
 + int nls = -1; 
 + struct sockaddr_nl kernel_nladdr;​ 
 + struct iovec io; 
 + struct msghdr msg; 
 + struct ifinfomsg *ifm; 
 + unsigned int change, flags, seq; 
 + char *ifname; 
 + char buf[SOCKET_BUFFER_SIZE];​ /* 8192 by default */ 
 + 
 + struct nlmsghdr *nlmsg; 
 + seq = time(NULL);​ 
 + 
 + /* The netlink message is destined to the kernel so nl_pid == 0. */ 
 + memset(&​kernel_nladdr,​ 0, sizeof(kernel_nladdr));​ 
 + kernel_nladdr.nl_family = AF_NETLINK;​ 
 + kernel_nladdr.nl_groups = 0; /* unicast */ 
 + kernel_nladdr.nl_pid = 0; 
 + 
 + nls = socket(AF_NETLINK,​ SOCK_RAW, NETLINK_ROUTE);​ 
 + if (nls == -1) 
 +
 + printf("​cannot open socket %s\n", strerror(errno));​ 
 + return -1; 
 +
 + 
 + int br; 
 + 
 + br = bind(nls, (struct sockaddr *) &​kernel_nladdr,​ sizeof (kernel_nladdr));​ 
 + if (br == -1) 
 +
 + printf("​cannot bind to socket\n"​);​ 
 + return -1; 
 +
 + 
 + int hlen = ALIGN(sizeof(struct nlmsghdr));​ 
 + nlmsg = buf; 
 + memset(buf,​ 0, hlen); 
 + nlmsg->​nlmsg_len = hlen; 
 + 
 + nlmsg->​nlmsg_type = RTM_NEWLINK;​ 
 + nlmsg->​nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;​ 
 + nlmsg->​nlmsg_seq = seq; 
 + 
 + /* extra header */ 
 + char *ptr = (char *)nlmsg + nlmsg->​nlmsg_len;​ 
 + size_t ehlen = ALIGN(sizeof(*ifm));​ 
 + nlmsg->​nlmsg_len += ehlen; 
 + memset(ptr,​ 0, ehlen); 
 + 
 + /* put interface down */ 
 + change = 0; 
 + flags = 0; 
 + change |= IFF_UP; 
 + flags &= ~IFF_UP; /* down = !up, obviously */ 
 + 
 + ifm = (void *)ptr; 
 + ifm->​ifi_family = AF_UNSPEC;​ 
 + ifm->​ifi_change = change; 
 + ifm->​ifi_flags = flags; 
 +  
 + /* add payload details - nlattr & padding */ 
 + ifname = "​some_interface";​ 
 + struct nlattr *attr = (void *)nlmsg + ALIGN(nlmsg->​nlmsg_len);​ 
 + uint16_t payload_len = ALIGN(sizeof(struct nlattr)) + strlen(ifname);​ 
 + int pad; 
 + 
 + attr->​nla_type = IFLA_IFNAME;​ 
 + attr->​nla_len = payload_len;​ 
 + memcpy((void *)attr + ATTR_HDRLEN,​ ifname, strlen(ifname));​ 
 + pad = ALIGN(strlen(ifname)) - strlen(ifname);​ 
 + if (pad > 0) 
 + memset((void *)attr + ATTR_HDRLEN + strlen(ifname),​ 0, pad); 
 + 
 + nlmsg->​nlmsg_len += ALIGN(payload_len);​ 
 + 
 + /* end of inner netlink nlattr details */ 
 + 
 + /* Stick the request in an io vector */ 
 + io.iov_base = (void *)nlmsg; 
 + io.iov_len = nlmsg->​nlmsg_len;​ 
 +  
 + /* Wrap it in a msg */  
 + memset(&​msg,​ 0, sizeof(msg));​ 
 + msg.msg_iov = &io; 
 + msg.msg_iovlen = 1; 
 + msg.msg_name = (void *)&​kernel_nladdr;​ 
 + msg.msg_namelen = sizeof(kernel_nladdr);​ 
 + 
 + /* Send it */ 
 + int res = sendmsg(nls,​ &msg, 0); 
 + printf("​result of send: %d", res); 
 +  
 + return 0; 
 +
 +</​code>​ 
 + 
 +Codul de mai sus: 
 +  * Deschide un socket cu AF_NETLINK ca familie 
 +  * Setează id-ul adresei la 0 (pentru kernel) 
 +  * Creează antetul standard al mesajului 
 +  * Atașează payload-ul corect (incluzând ifinfomsg și numele NIC-ului) 
 +  * Apelează sendmsg 
 + 
 +<code bash> 
 +> gcc switch_interface_state.c -o switch_interface_state 
 +> ip a show some_interface 
 +sudo ./​switch_interface_state 
 +> ip a show some_interface 
 +</​code>​ 
 </​spoiler>​ </​spoiler>​
-  * Pentru a pregăti ​configurația ​de laborator, pe mașina virtuală (stația ''​host''​) ​folosițcomenzile următoare din contul utilizatorului ​''​root'' ​de pe stația ​''​host'' ​(puteți da copy/paste la comenzi în terminal):<​code ​bash+==== Exercitii ==== 
-root@host:​~# ​update_lab ​--force + 
-root@host:​~# ​start_lab lab10+=== Namespaces === 
 + 
 +În configurația ​cu două namespace-uri descrisă în secțiunea ''​Utilizare''​, vom captura pachetele trimise de ''​nc''​ cu ''​tcpdump''​ și le vom studia folosind ''​Wireshark''​. 
 + 
 +=== Netlink Socket Diagnostics Subsystem === 
 + 
 +În primele versiuni ale kernelului Linux (inainte de v2.2comunicarea între user space șkernel space se realiza predominant prin 
 +[[https://​elixir.bootlin.com/​linux/​v6.18/​source/​include/​linux/​fs.h#​L2268|file ops]] (syscall-uri). Acest set de callback-uri este implementat (macar parțial) pentru fiecare fișier accesibil în Linux. Pentru un fișier normal (e.g., din home-ul vostru) callback-urile acestea pointează către funcții ​din API-ul sistemului de fișiere. Fișierele speciale (i.e., ​''​/dev''​''​/​proc'', ​''/​sys''​se diferențiază prin faptul că generează informații despre sistem în mod dinamic sau permit ajustarea parametrilor de funcționare. De exemplu, următoarele două comenzi sunt echivalente: 
 + 
 +<​code>​ 
 +root@host:​~# ​sysctl ​-w net.ipv4.ip_forward=1 
 +root@host:​~# ​echo 1 > /​proc/​sys/​net/​ipv4/​ip_forward
 </​code>​ </​code>​
-  * Deschideți trei noi tab-uri în terminal (folosiți combinația de taste ''​Ctrl+Shift+t''​),​ și conectați-vă,​ din nou, la mașina virtuală folosind comanda ''​ssh''​ de mai sus. 
-    * De pe cele trei noi tab-uri, conectați-vă la cele trei containere (''​red'',​ ''​green''​ și ''​blue''​). 
-    * Pentru o conectare mai usoara puteti folosi aliasul ''​go''​ (ex. ''​go red''​) 
  
-<​note>​ +Similar, utilitarul **netstat** pe care îl folosim pentru ​investiga conexiunile de rețea active în sistem citește ​și parsează conținutul fișierelor din ''​/proc/net/'':​
-Pentru ​vedea cum accesați stațiile ''​red'',​ ''​green'' ​și ''​blue''​ (containere Docker configurate peste mașina virtuală VMware - stația ''​host''​) urmăriți indicațiile din [[:rl:​info:​resurse:​vm-laborator#​instructiuni_de_utilizare|pagina cu instrucțiuni de utilizare a mașinii virtuale]]. +
-</​note>​+
  
-<note+<code
-Conturile ​de acces la mașina virtuală (stația ''​host''​sunt (''​username:parola''​):​ +root@host:​~#​ netstat -4tan 
-  * ''​root:student''​ +Active Internet connections (servers and established) 
-  * ''​student:​student''​ +Proto Recv-Q Send-Q Local Address ​          ​Foreign Address ​        ​State ​      
-</note>+tcp        0      0 0.0.0.0:​25 ​             0.0.0.0:​* ​              ​LISTEN ​     
 +tcp        0      0 0.0.0.0:​22 ​             0.0.0.0:​* ​              ​LISTEN ​     
 +tcp        0    332 10.9.1.55:​22 ​           141.85.150.247:​51302 ​   ESTABLISHED 
 + 
 +root@host:​~#​ cat /​proc/​net/​tcp 
 +  sl  local_address rem_address ​  st tx_queue rx_queue tr tm->when retrnsmt ​  ​uid ​ timeout inode 
 +   0: 00000000:​0019 00000000:​0000 0A 00000000:​00000000 00:00000000 00000000 ​    ​0 ​       0  8358 
 +   1: 00000000:​0016 00000000:​0000 0A 00000000:​00000000 00:00000000 00000000 ​    ​0 ​       0  7615 
 +   2: 3701090A:​0016 F796558D:​C866 01 00000000:​00000000 02:000A9B46 00000000 ​    ​0 ​       0 22536 
 +</​code>​ 
 + 
 +Acest utilitar este compatibil cu versiunile mai vechi de Linux, însă echivalentul //"​modern"// ​(mai nou de v2.4), anume **ss**, folosește subsistemul de [[https://​man.archlinux.org/​man/​sock_diag.7.en|Socket Diagnostics]] din protocolul [[https://​man.archlinux.org/​man/​netlink.7|Netlink]]. Netlink este un datagram protocol creat pentru a permite comunicarea proceselor din userspace cu anumite drivere din kernel space. Introdus inițial pentru ca developerii se săturaseră să tot adauge [[https://​man.archlinux.org/​man/​ioctl.2|ioctl]]-uri noi, acesta a fost ulterior adaptat pentru a interacționa cu [[https://​elixir.bootlin.com/​linux/​v6.18/​source/​include/​uapi/​linux/​netlink.h#​L9|mai multe subsisteme]]. De fapt, atât **ip route** cât și **iptables** se bazează pe protocolul Netlink. 
 + 
 +== Task A - Demo C API == 
 + 
 +În continuare vom lucra cu scheletul de cod din ''​~/​sock-diag/''​. În forma sa actuală, programul inițiază o conexiune Netlink și cere kernel-ului o listă cu toți sockeții TCP ce reprezintă o conexiune activă. 
 + 
 +<​code>​ 
 +root@host:​~/​sock-diag#​ gcc main.c 
 +root@host:​~/​sock-diag#​ ./a.out 
 +[-] main.c:​100 ​ ================================= 
 +[-] main.c:​101 ​ sport  : 22 
 +[-] main.c:​102 ​ dport  : 51302 
 +[-] main.c:​103 ​ src ip : 10.9.1.55 
 +[-] main.c:​108 ​ dst ip : 141.85.150.247 
 +[-] main.c:​113 ​ inode  : 22536 
 +</​code>​ 
 + 
 +Facem totuși cateva observații:​ 
 +  * Valorile câmpurilor din structura ''​conn_req.id''​ sunt setate pe zero. Acestea pot fi folosite pentru a filtra rezultatele produse de kernel ​(e.g., arată-mi toate conexiunile către //​141.85.227.65//​ -- [[https://​ocw.cs.pub.ro/​|ocw.cs.pub.ro]]). 
 +  * Flag-ul pasat prin ''​conn_req.idiag_ext''​ cere sistemului de operare sa returneze câte o structură ''​tcp_info''​ pentru fiecare socket identificat. Acestea se numesc **atribute opționale**. Consultați secțiunea relevantă din [[https://​man.archlinux.org/​man/​sock_diag.7.en#​idiag_ext|man page]] dacă sunteți curioși ce alte informații ar mai fi disponibile. 
 + 
 +== Task B - Parsare atribute opționale == 
 + 
 +Pentru fiecare socket raportat, folosiți [[https://​man.archlinux.org/​man/​netlink.3|aceste macro-uri]] pentru a parsa atributele opționale (i.e., structurile ​''​tcp_info''​) conținute in următorii ''​rta_len''​ octeți, după informațiile de bază găsite la adresa ''​diag_msg''​. Inspectați câmpurile structurii ''​tcp_info''​ și afișați statisticile ce vi se par importante (plus unitățile de măsură). De exemplu
 + 
 +<​code>​ 
 +[-] main.c:​101 ​ sport  : 22 
 +[-] main.c:​102 ​ dport  : 51302 
 +[-] main.c:​103 ​ src ip : 10.9.1.55 
 +[-] main.c:​108 ​ dst ip : 141.85.150.247 
 +[-] main.c:​113 ​ inode  : 22536 
 +[-] main.c:​126 ​  
 +[-] main.c:​127 ​ sent   : ​    ​330929 [bytes] 
 +[-] main.c:​129 ​ ACK-ed :     ​330893 [bytes] 
 +[-] main.c:​131 ​ recv   : ​    ​297542 [bytes] 
 +[-] main.c:​133 ​ rto    :     ​224000 [microsec] 
 +</​code>​ 
 + 
 +<spoiler În caz că vă blocați :-P> 
 +<code c> 
 +/calculate remaining space in optional attribute buffer */ 
 +rta_len = nlh_it->​nlmsg_len - NLMSG_LENGTH(sizeof(*diag_msg));​ 
 + 
 +/* iterate over optional attributes */ 
 +for (attr = (void *)diag_msg + NLMSG_ALIGN(sizeof(*diag_msg));​ 
 +     ​RTA_OK(attr,​ rta_len); attr = RTA_NEXT(attr,​ rta_len)) 
 +
 +    switch (attr->​rta_type) { 
 +        case INET_DIAG_INFO: ​   /* TCP stats */ 
 +            info = RTA_DATA(attr);​ 
 + 
 +            DEBUG("​sent ​  : %10llu [bytes]",​ info->​tcpi_bytes_sent);​ 
 +            DEBUG("​ACK-ed : %10llu [bytes]",​ info->​tcpi_bytes_acked);​ 
 +            DEBUG("​recv ​  : %10llu [bytes]",​ info->​tcpi_bytes_received);​ 
 +            DEBUG("​rto ​   : %10u [microsec]",​ info->​tcpi_rto);​ 
 + 
 +            break; 
 + 
 +        /* default: ignore all other optional attributes */ 
 +    } 
 +
 + 
 +/* skip to next message encapsulated in response */ 
 +nlh_it = NLMSG_NEXT(nlh_it,​ ans); 
 +</​code>​ 
 +</​spoiler>​ 
 + 
 +== Task C - Filtrare socketi in state-ul LISTEN == 
 + 
 +Porniți o instantă de **netcat** care să asculte pe un port local. Folosiți [[https://​man.archlinux.org/​man/​nc.1#​k|flag-ul -k]] pentru a nu omorî procesul dupa finalizarea conexiunii. 
 + 
 +Modificați programul astfel încât să obțină lista sockeților în //modul TCP LISTENING//,​ nu ESTABLISHED. Filtrați rezultatele pe baza portului pe care ascultați; ar trebui să obțineți un singur rezultat. 
 + 
 +Pornim **netcat**. 
 +<​code>​ 
 +root@host:~# nc -lkp 12345 
 +</​code>​ 
 + 
 +<spoiler În caz că vă blocați :-P> 
 +Modificam programul. 
 +<code diff> 
 +diff --git a/main.c b/main.c 
 +index 8938aa6..d540730 100644 
 +--- a/main.c 
 ++++ b/main.c 
 +@@ -55,7 +55,7 @@ main(int32_t argc, char *argv[]) 
 +  
 +     ​conn_req.sdiag_family ​  = AF_INET; ​               /* target addr family ​ *
 +     ​conn_req.sdiag_protocol = IPPROTO_TCP; ​           /* target protocol ​    */ 
 +-    conn_req.idiag_states ​  = 1 << TCP_ESTABLISHED; ​  /* filter sock states ​ */ 
 ++    conn_req.idiag_states ​  = 1 << TCP_LISTEN; ​       /* filter sock states ​ */ 
 +     ​conn_req.idiag_ext ​     = 1 << (INET_DIAG_INFO - 1);    /* get tcp_info ​ */ 
 +  
 +     ​nlh.nlmsg_len ​  = NLMSG_LENGTH(sizeof(conn_req));​ /* message length ​     */ 
 +@@ -64,7 +64,7 @@ main(int32_t argc, char *argv[]) 
 +  
 +     ​conn_req.id.idiag_src[0] = 0;                     /* src ip addr filter ​ */ 
 +     ​conn_req.id.idiag_dst[0] = 0;                     /* dst ip addr filter ​ */ 
 +-    conn_req.id.idiag_sport ​ = htons(0); ​             /* src port filter ​    */ 
 ++    conn_req.id.idiag_sport ​ = htons(12345); ​         /* src port filter ​    */ 
 +     ​conn_req.id.idiag_dport ​ = htons(0); ​             /* dst port filter ​    */ 
 +  
 +     ​iov[0].iov_base = (void *) &​nlh; ​                 /* include nl header ​  */ 
 +</​code>​ 
 + 
 + 
 +Testam: 
 +<​code>​ 
 +root@host:​~/​sock-diag#​ gcc main.c 
 +root@host:​~/​sock-diag#​ ./a.out 
 +[-] main.c:​100 ​ ================================= 
 +[-] main.c:​101 ​ sport  : 12345                               
 +[-] main.c:​102 ​ dport  : 0                                   
 +[-] main.c:​103 ​ src ip : 0.0.0.0 
 +[-] main.c:​108 ​ dst ip : 0.0.0.0 
 +[-] main.c:​113 ​ inode  : 5620190 
 +[-] main.c:​126 ​                                              
 +[-] main.c:​127 ​ sent   : ​         0 [bytes] 
 +[-] main.c:​129 ​ ACK-ed :          0 [bytes] 
 +[-] main.c:​131 ​ recv   : ​         0 [bytes] 
 +[-] main.c:​133 ​ rto    :          0 [microsec] 
 +</​code>​ 
 +</​spoiler>​ 
 + 
 +=== Namespaces === 
 + 
 +== Task A - iproute2 netns == 
 + 
 +În mod normal, namespace-urile persistă câtă vreme există macar un proces ce le referențiazâ. Există totuși o excepție ce ne permite să construim namespace-uri persistente. Ce contează de fapt pentru kernel este reference count-ul pentru fisier din //procfs// care reprezintă namespace-ul respectiv. **iproute2** (i.e., comanda **ip** pe care ați tot utilizat-o) folosește o particularitate a syscall-ului [[https://​man.archlinux.org/​man/​mount.8|mount()]. Daca analizam cu [[https://​man.archlinux.org/​man/​strace.1|strace]] apelurile de sistem generate, vom identifica urmatoarea secvență:​ 
 + 
 +<​code>​ 
 +root@host:​~$ strace ip netns add PERSIST 
 +    openat(AT_FDCWD,​ "/​run/​netns/​TEST",​ O_RDONLY|O_CREAT|O_EXCL,​ 000) = 5 
 +    mount("/​proc/​self/​ns/​net",​ "/​run/​netns/​TEST",​ 0x556b0d60700c,​ MS_BIND, NULL) = 0 
 +    setns(5, CLONE_NEWNET) 
 +     
 +root@host:​~$ mount -t nsfs 
 +    nsfs on /​run/​docker/​netns/​40545dc4df20 type nsfs (rw) 
 +    nsfs on /​run/​docker/​netns/​d81045a3216b type nsfs (rw) 
 +    nsfs on /​run/​docker/​netns/​f12eee1a9e7c type nsfs (rw) 
 +    nsfs on /​run/​netns/​PERSIST type nsfs (rw) 
 +</​code>​ 
 + 
 +Observăm că utilitarul **iproute** a creat un fișier in directorul ​''​/run/netns/'' ​și a făcut un bind mount cu referința sa din //procfs//. Această operație a incrementat [[https://​lwn.net/​Articles/​1043824/​|reference counter-ul]] namespace-ului,​ urmând ca acesta să se separe într-unul nou prin apelul de [[https://​man.archlinux.org/​man/​setns.2|setns()]]. Deși procesul **iproute2** a murit, namespace-ul va persista până facem [[https://​man.archlinux.org/​man/​umount.8|umount()]] pe toate referințele active. Listând mountpoint-urile curente, vedem ca inclusiv **docker** (sau in cazul nostru, [[https://​containernet.github.io/​|ContainerNet]]) folosește această tehnică atunci când spawneaza containere noi (i.e., <color #​ff0000>​red</color>, <color #​00ff00>​green</​color>,​ <color #​0000ff>​blue</​color>​). 
 + 
 +înainte să trecem la următorul task, folosiți [[https://​man.archlinux.org/​man/​ip-netns.8|ip netns]] pentru a porni o instanță de **netcat** in namespace-ul //PERSIST// creat anterior. Instanța de netcat ar trebui sa fie configurată ca la excercitiul anterior (i.e., TCP, ascultând pe acelasi port).
  
 <​note>​ <​note>​
-În mod implicit folosiți contul ''​root'' ​pentru ​conectare ​pe toate stațiile. ​Aveți nevoie ​de drepturi privilegiate pentru configurareFolosiți contul ​''​student'' ​doar unde vi se cere explicit.+Procesul pornit în acest nou namespace **NU** va avea acces la rețea. Atunci când un nou netns este creat, el va avea doar o interfață de loopback. [[https://​man.archlinux.org/​man/​veth.4|Virtual Ethernet pair-urile]] care sunt folosite pentru a construi un bridge între containere șeventual rețeaua fizică trebuie adăugate separat. 
 + 
 +în absența acestora, ruland un proces într-un network namespace nou este o metodă bună pentru ​a-l preveni să se conecteze la internet (e.g., la un license server ;-)). Singurele alternative ar fi modulul [[https://​man.archlinux.org/​man/​core/​iptables/​iptables-extensions.8#​owner|owner]] din **iptables** sau [[https://​man.archlinux.org/​man/​core/​man-pages/​ld.so.8#​LD_PRELOAD|LD_PRELOAD hooking]]. Prima poate afecta și alte procese decat cel intenționat iar a doua este prea muncitorească și nici nu o sa mearga ​pe distribuțiile ​hardenedÎn trecut se putea filtra traficul si pe bază de PID dar modulul de **iptables** ajungea sa facă [[https://​www.spinics.net/​lists/​netfilter/​msg49716.html|abuz de lock]] pe lista de task-uri din kernel. 
 + 
 +Țineți cont că atunci când ștergeți namespace-ul,​ toate interfețele pe care le-ați adăugat vor fi moștenite ​de namespace-ul originalDacă v-a crăpat vreodată Mininet și v-ați trezit cu câteva zeci de interfețe noi când ați rulat ''​ip addr show''​, acesta este motivul.
 </​note>​ </​note>​
-===== Topologie ===== 
  
-{{ :rl:topologie.png |}}+<​code>​ 
 +root@host:~# ip netns add PERSIST 
 +root@host:~# ip netns exec PERSIST nc -lkp 12345 
 +</​code>​
  
-===== Navigare =====+== Task B - Compatibilitate Socket Diagnostics cu netns ==
  
-**[[:rl:labs:10|Laboratorul 10]]** +Dacă rulăm programul nostru de la exercițiul anterior, observăm că sistemul de operare nu gasește niciun rezultat. Aceasta este o limitare din Linux. Subsistemul de Socket Diagnostics poate identifica sockeți doar în network namespace-ul din care face parte socket-ul Netlink. Soluția cea mai scalabila / performanta se bazeaza pe [[https://​ebpf.io/​|extended Berkley Packet Filter (eBPF)]]. Acesta stă la baza proiectului [[https://​cilium.io/​|Cilium]], unul dintre primele framework-uri care a adresat (ca. 2014) problema observabilității conexiunilor de rețea in mediile bazate pe microservicii. Deoarece eBPF este un subiect mult prea complex pentru un curs introductiv de rețelistică,​ nu vom intra in detalii. Putem însă găsi o soluție mult mai directă pentru problema noastră.
-{{page>:​rl:​labs:​10:​meta:​nav&​nofooter&​noeditbutton}}+
  
-===== Exerciții =====+Modificați programul astfel încât să execute o tranziție în network namespace-ul unui proces, specificat din linie de comanda (e.g., **netcat**-ul nostru). Pe urmâ, reîncercați listarea sockeților TCP în modul listening prin protocolul Netlink Socket Diagnostics. Ce observați?
  
-În cadrul exercițiilor din laboratoarele ​de Linux vom folosi [[:rl:​labs:​10#​topologie|topologia de mai sus]].+Se rulează dând ca parametru ''/​proc/​$(pidof nc)/​ns/​net''​ daca vreți sa folosiți apelul ​de sistem ''​open()''​. Alternativ, puteți ​folosi [[https://​man.archlinux.org/​man/​pidfd_open.2|pidfd_open]]. 
 + 
 +<​solution -hidden>​ 
 +<code diff> 
 +diff --git a/main.c b/main.c 
 +index aeb88d9..e05eb2b 100644 
 +--- a/main.c 
 ++++ b/main.c 
 +@@ -38,9 +38,21 @@ main(int32_t argc, char *argv[]) 
 +     ​ssize_t ​                ​ans; ​           /* answer ​                     */ 
 +     ​int32_t ​                ret = -1;       /* program return code         */ 
 +  
 ++    /* sanity check */ 
 ++    DIE(argc != 2, "​Usage:​ ./a.out /​proc/​*/​ns/​net"​);​ 
 +
 ++    /* open network namespace magic link */ 
 ++    int32_t ns_fd = open(argv[1],​ O_RDONLY | O_CLOEXEC);​ 
 ++    DIE(ns_fd == -1, "​failed to open netns magic link (%s)", strerror(errno));​ 
 +
 ++    /* switch network namespace ​                        * 
 ++     * NOTE: must be done before opening netlink socket */ 
 ++    ans = setns(ns_fd,​ CLONE_NEWNET);​ 
 ++    GOTO(ans == -1, clean_ns, "​failed to swtich netns (%s)", strerror(errno));​ 
 +
 +     /* open netlink socket diagnostics connection */ 
 +     nl_fd = socket(AF_NETLINK,​ SOCK_DGRAM, NETLINK_INET_DIAG);​ 
 +-    DIE(nl_fd == -1, "​failed to open netlink socket (%s)",​ 
 ++    GOTO(nl_fd == -1, clean_ns, "​failed to open netlink socket (%s)",​ 
 +          strerror(errno));​ 
 +  
 +     /* initial zero-ing of structures */ 
 +@@ -150,5 +162,8 @@ out: 
 + ​clean_nl:​ 
 +     ​close(nl_fd);​ 
 +  
 ++clean_ns:​ 
 ++    close(ns_fd);​ 
 +
 +     ​return ret; 
 + } 
 +</​code>​ 
 +</​solution>​
  
-{{namespace>:​rl:​labs:​10:​contents&​nofooter&​noeditbutton}} 
rl/labs/10.1641981615.txt.gz · Last modified: 2022/01/12 12:00 by florin.stancu
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