Skip to content
Snippets Groups Projects

Python mérés

1. Feladat - Bemelegítő gyakorlatok

A következő részfeladatok célja bemutatni a Python alapvető szintaktikai elemeit és a nyelv használhatóságát, lehetőségeit.

A Python nyelvvel való ismerkedéshez célszerű először egy elszeparált környezetet kialakítani, amiben kedvünkre telepíthetünk különböző verziójú csomagokat anélkül, hogy azok összeakadnának a globális Python csomagokkal. Ezáltal rendezetten és elszeparáltan tarthatóak a globális rendszercsomagok és egy könnyen menedzselhető, virtuális környezetet kapunk. Ehhez telepítenünk kell a virtualenv Python csomagot a pip3 csomagmenedzselő segítségével. Ha a pip3 nincs telepítve, először adjuk ki a $ sudo apt-get install python3-pip parancsot a pip3, majd a $ sudo pip3 install virtualenv parancsot a virtualenv telepítéséhez.

A virtuális mérési környezet létrehozásához adjuk ki a $ virtualenv py-meres parancsot. Ekkor létrejön egy mappa benne a szükséges futtatható Python állományokkal és a pip3 csomagmenedzselő függőségeivel. Alapértelmezetten a globálisan telepített külső csomagok nem kerülnek linkelésre, így minden a Python standard könyvtárában nem szereplő függőség külön telepítendő, ha szükséges.

Dolgozzunk a létrejött py-meres mappában. Minden részfeladatot, ahol egy szkript fájlt kell készíteni, egy különálló .py kiterjesztésű fájlban oldjuk meg!

A virtuális környezet aktiválásához adjuk ki a mappában a $ source bin/activate parancsot. Ekkor a prompt elé bekerülő név jelzi, hogy melyik környezetben dolgozunk.

Környezet deaktiválása: $ deactivate

Ellenőrizzük, hogy elérhető-e a megfelelő Python verzió (>= 3.5.x): $ python3 --version. Ha a használt Python verzió nem 3.x, akkor állítsuk be: $ virtualenv –p /usr/bin/python3 py-meres

czentye@ThinkPad-L470:~$ virtualenv py-meres
created virtual environment CPython3.8.5.final.0-64 in 471ms
  creator CPython3Posix(dest=/home/czentye/py-meres, clear=False, no_vcs_ignore=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/czentye/.local/share/virtualenv)
    added seed packages: pip==21.0.1, setuptools==52.0.0, wheel==0.36.2
  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
czentye@ThinkPad-L470:~$ cd py-meres/
czentye@ThinkPad-L470:~/py-meres$ source bin/activate
(py-meres) czentye@ThinkPad-L470:~/py-meres$ python3 -V
Python 3.8.5
(py-meres) czentye@ThinkPad-L470:~/py-meres$ 

FONTOS! Importálási hibák elkerülésére figyeljünk a következőkre:

  • Virtuális környezetben tevékenykedéshez ne felejtsük el aktiválni a környezetet!
  • Külső Python csomag virtuális környezetbe telepítésénél ne használjunk rendszeradmin jogosultságot, mert a csomag abban az esetben a globális Python csomagok közé telepítődik, amelyek alapértelmezetten nem linkelődnek a virtuális környezetünkbe! Tehát $ sudo pip3 install ...
  • Python szkriptet (amit virtuális környezetben futtatunk) ne a szokványos #!/usr/bin/python3 shebang sorral kezdjük, hanem a rugalmasabb és esetünkben fontos #!/usr/bin/env python3 sorral. Ez előbbi esetben ugyanis közvetlenül a globális Python interpretert jelöljük ki, ami nem láthatja a virtuális környezetbe telepített csomagjainkat. Az utóbbi sorral az env programot hívjuk meg, ami jelen esetünkben már a virtuáis környezetbe linkelt Python interpretert fogja meghívni. Erről magunk is megbizonyosodhatunk, ha meghívjuk a $ which python3 parancsot, ami a virtuális környezeten kívül a /usr/bin/python3 útvonalat, míg aktiválás után a /home/user/py-meres/bin/python3 útvonalat adja vissza.

1.a Feladat - Interaktív mód

Indítsuk el az interpretert a $ python3 paranccsal. Az interpreter érvényes Python kifejezéseket vár, amelyeket megadás után azonnal feldolgoz és az eredményt (minden Python kifejezésnek van eredménye!) kiírja a konzolra. Interpreterből kilépéshez használható az exit() függvényhívást, vagy a kényelmesebb CTRL + D billenytűkombinációt (EOT - end-of-transmission karakter).

Feladat: A Python nyelv gyakran használt tudományos és matematika algoritmusok implementálására. Írassuk ki mérföldben 3 tizedes jegy pontossággal egy félmaraton hosszát az interpreter segítségével!

  • Az arányossági adatokat tároljuk változókban: félmaratoni táv - 21097,5 m; 1 mérföld - 1,6093 km
  • Lebegőpontos típusnál tizedes pontot használjunk (3.14 és nem 3,14)!
  • Képlet: (táv(m) / 1000 ) / arány (km/mil)
  • Kiíratásra használjuk a print() függvényt és sztring formázáshoz a format() függvényt (float típus -f karakter, formátum (link) a segédletben)

Érdekesség: Figyeljük meg a lebegő pontos értékek kerekítési tulajdonságát az eredeti és a 3 tizedes pontosságal kiírt eredményen! (Floating Point Arithmetic: Issues and Limitations)

1.b Feladat - Külső modul használata

Feladat: Írassuk ki a google.com (nem kell a www elé!) tartománynévhez rendelt levelező szervereket (MX rekord)!

A DNS rekordok kezeléséhez használjuk a dnspython csomagot. A csomagot a $ pip3 install dnspython paranccsal tudjuk telepíteni. A DNS rekord lekéréséhez használjuk a dns.resolver csomag query() függvényét.

  • Használat előtt ne felejtsük el importálni a szükséges csomagot!
  • Dokumentáció megtekintéséhez használjuk a help() beépített függvényt. Nézzük meg mind a csomag, mind a függvény dokumentációját!
  • A lekérésnél rekord típusnak adjuk meg az 'MX' stringet!
  • A query() visszatérési értéke a dnspython csomag egy speciális objektuma (ami a dns.resolver.Answer osztály egy példánya), ami egyben iterálható is. A bejegyzések kiíratásához járjuk be az objektumot a megfelelő nyelvi elemmel! Ne felejtsük el a behúzást a kódblokk esetén!

1.c Feladat - Saját DNS modul

Feladat: Hosszabb kódok esetén az interaktív interpreter használata körülményes. Írjunk Python modult dns_tool.py néven az előző feladatban megvalósított funkcióhoz!

  1. feladathoz - Nevezzük át a fájlt!
  • A rekordok lekérését egy külön függvényben (get_mx()) definiáljuk, amely a DNS nevet egy paraméterként kapja meg és az eredményeket egy nagy listába csomagolva adja vissza.
  • Adjunk minimális dokumentációt a függvényünkhöz!
  • A rekordtípus opcionálisan legyen megadható a függvénynek (pl. későbbi továbbfejlesztéshez), ami alapértelmezetten az 'MX' értéket vegye fel!
  • A rekordokat egyenként tuple-ben adjuk vissza: (preferencia, név) sorrendben. Az elemekhez az egy darab rekordot reprezentáló objektum preference és exchange attribútumán keresztül férhetünk hozzá. A visszaadott struktúra tehát a következő formátumú: [(pref1, exch1), (pref2, exch2), ...]
  • A rekord attribútumokat mindenképpen konvertáljuk string típusúvá!
  • Hiányzó válasz vagy nem létező rekord esetén a query() függvény kivételt dob, amelyek a dns.exception.DNSException leszármazottai. Kezeljük valamilyen módon a kivételt (akár minden kivétel elkapásával) és ekkor egyszerűen térjünk vissza egy üres tuple-lal!
  • Teszteljük a most megírt modulunkat interaktív módon (indítsunk egy Python interpretert, importáljuk be és hívjuk meg a függvényt) és nézzük meg a modul/függvény dokumentációkat is a help() függvénnyel!

1.d Feladat - Futtatható DNS szkript

Feladat: Tegyük futtathatóvá az elkészült modult!

  1. feladathoz - Nevezzük át a fájlt!
  • A szkriptet ne hagyományos (és mondhatni elavult) shebang sorral definiáljuk, hanem a rugalmasabb és esetünkben fontos env paranccsal (Lásd az 1. Feladat bevezetőjét)!
  • Figyeljünk arra, hogy a modul importálható maradjon (ellenőrizzük a __name__ paramétert)!
  • Futtatáshoz ne felejtsünk el futtatási jogot adni a szkriptnek ($ chmod a+x dns_tool2.py, futtatás: ./dns_tool2.py)!
  • Modul futtatásakor a DNS nevet interaktív módon, a konzolon keresztül kérjük be a megfelelő beépített függvénnyel (input())!

2. fealadat - Statisztika szkript

Feladat: Készítsünk futtatható Python szkriptet mirror_stat.py néven, amely statisztikát készít megadott Debian mirror szerverek elérhetőségéről, amit a futása végén ki is listáz! A mirror szerverek a következő fájlban: Primary Debian mirrors vannak megadva, mentsük le a szkriptünk mellé. Az elérhetőség teszteléséhez telepítsük és használjuk a következő csomagot: ping3. A pingeléshez root jogosultság szükséges, így a szkriptet is root jogosultsággal kell indítani! Viszont a szkript indításához nem használhatjuk csupán a sudo parancsot az első feladatnál leírt linkelési/importálási problémák miatt. Ennek megkerülésére közvetlenül a virtuális környezetbe linkelt python3 interpretert kell futtatnunk, paraméterül megadva a szkriptünket: sudo ./bin/python3 mirror_stat.py. A statisztikában a szerverekhez tartozó RTT értéket tároljuk el!

(Amúgy modern rendszereken a pinghez nem kell sudo, feleslegesen ne szívasd meg magad! - blint)

  • mirror_stat.py.txt: Skeleton fájl a 2. feladathoz - Nevezzük át!

  • Töltsük le a mirror szervereket tartalmazó fájlt és mentsük le a virtuális környezeti mappánkba. Fájlkezeléshez használjuk a with kulcsszót!

  • A statisztika struktúra inicializálásához a beolvasott sorokat először fel kell dolgozni. Pingeléshez csak a domain nevet használjuk, ezért hagyjuk el a 'http://' elő- és '/.../...' utótagokat! Használjuk a string változók split() függvényét! Célszerű ehhez a '/' karakter szerint szeletelni és az így visszaadott listából a megfelelő tagot kiválasztani.

  • A statisztika tárolásához használjunk szótárat(dict)!

  • A szerverek elérhetőségéhez használjuk a ping3 modul ping() függvényét! A használható funkciókat a help() és dir() beépített függvények segítségével deríthetjük ki.

  • Ellenőrizzük a sikerességet a pingeléskor (Sikertelen ping esetén False a visszatérési érték), és a statisztikában mentsük el a visszaadott értéket. Elérhetetlen szerver esetén None értéket tároljunk el!

  • Egy komplex struktúra formázott kiíratáshoz használjuk a beépített pprint csomag pprint() függvényét!

  • Ne felejtsük el futtatáshoz a szkriptet a megfelelő sudo paranccsal indítani! (ahogy fent is írtam, nem kell sudo hozzá - blint)

3. Feladat - Hálózati kommunikáció - port szkenner

A Python standard könyvtárai egyaránt támogatják az alacsony-szintű, socket alapú és magas-szintű hálózatkezelést is. Az alacsony-szintű hálózatkezelés (CPython és Linux környezet révén) a C nyelvi szintaktikára épül, ahol a nyelv a Linux socket-kezelő rendszerhívásait ajánlja ki. A követező kép ezt az alacsony-szintű TCP kommunikációt és az ahhoz meghívandó függvényeket szemlélteti:
pic

HASZNOS PARANCSOK Előfordulhat a szkriptek futtatásánál, hogy a félkész kódok miatt a socket-ek beragadnak és lefoglalják az adott portot/címet. Ekkor a még futó programokat manuálisan kell leállítanunk:

  • Nyitott portok listázása: $ netstat -tuplen (vagy ss -tulpn modernebb rendszereken - blint)
  • Futó Python programok kilistázása: $ ps auxf | grep python
  • Program lelövése: $ sudo kill <PID> vagy a drasztikusabb $ sudo kill -9 <PID>

3.a Feladat - Time szerver

Feladat: Írjunk Python szkriptet time_server.py néven, ami megvalósít egy pontos időt visszaadó, TCP alapú szerver funkciót. A szerver a kliensek kapcsolódása után szimplán adja vissza a pontos időt (a klienseket külön szálon kezelve), majd bontsa a kapcsolatot. A megvalósításhoz használjuk a Python magas-szintű socketserver csomagjában definiált ThreadingTCPServer osztályt.

  • time_server.py.txt: Skeleton fájl a 2. feladathoz

  • A szerver implementációkat és handler osztályokat a socketserver csomagban találjuk.

  • A szerver osztályok csak a kapcsolódást menedzselik a Python alacsony-szintű socket interfészén keresztül. A kéréseket speciális RequestHandler osztályok kezelik le. Írjunk saját osztályt a kérések kezeléséhez, amely a BaseRequestHandler osztályból származzon!

  • Nézzünk utána a server és handler osztályok kapcsolatának, működésének és a kérések kezelésének! Definiáljuk felül a handle() metódust, amiben rögtön a kapcsolódás után adjuk vissza a pontos időt!

  • Üzenet küldéséhez használjuk a socket objektum sendall() metódusát. Ne felejtsük el lezárni a kapcsolatot a válasz után (close())!

  • A socket objektum a handler osztályban a request attribútumként érhető el.

  • A pontos idő visszaadásához használjuk a time csomagot (ctime() függvény)!

  • A socket kommunikációhoz bytes típusra konvertálás szükséges. Ehhez használjuk az str típus beépített encode() függvényét.

  • Ne felejtsük el elindítani a szervert a szkriptben, paraméterül megadva a (cím - port) tuple-t és a saját handler osztályunkat! Ehhez példakódot találunk a socketserver modul dokumentációjában!

  • A szerver IP címét és port számát kérjük be a szkript indítási paramétereként. Ehhez használjuk a beépített argparse csomagot! Dokumentáció és példakódok: argparse

  • Minden beolvasott paraméter string! Port szám esetén explicite konvertáljuk az értéket int típusúra!

  • Ctrl-C kombinációval való szabályos kilépéshez kezeljük le a dobott KeyboardInterrupt kivételt és állítsuk le a szervert (server.shutdown())!

  • Teszteléshez használjuk a telnet programot. Pl.: $ telnet localhost 8000

3.b Feladat - Port szkenner

Feladat: Valósítsunk meg egy port szkanner funkciót, amely egy megadott hoszton ("www.google.hu") végigszkenneli a "jól ismert" (well-known) portokat (1 - 1023). Eredményként írassuk ki a nyitott portokat. Dolgozzunk a Python konzolban az egyszerűség végett.

  • Ehhez használjuk a segédletben emített scapy csomagot, amit a következő paranccsal telepíthetünk: pip3 install scapy
  • A feladathoz nem kell szkriptet írni, elég interaktívan megvalósítani!
  • A scapy szintaktikára a segédletben linkelt oldalon találunk példákat vagy használjuk ezt az összefoglalót: scapy_cheatsheet.pdf
  • A csomagok összeállítására használjunk Layer 2-4 megközelítést (Ethenet + IP + TCP), az összefűzésre a / operátor használható (Basic Packet Crafting / Viewing, Altering Packets blokk).
  • Érdekességképpen mentsük el a csomagot egy változóban és vizsgáljuk meg az automatikusan kitöltött mezőket a .show() paranccsal.
  • Lehetőség van egy összefogott csomaghalmaz definiálására is: A vizsgálandó portokat tuple ként is megadhatjuk (start, stop) formátumban(Altering Packets blokk). A scapy képes kezelni a mezőkhöz definiált listákat/tupleket és Descartes-szorzatkét generálja ki az összes csomagot.
  • Küldésre használjuk a srp parancsot (Sending Packets blokk), a szkennelés végeztével állítsuk le a próbálkozást CTRL+C kombinációval vagy a preferáltabb módon adjunk meg timeout értéket a küldéshez, 1s bőven elég.
  • Eredményként az srp parancs 2 értékkel tér vissza: 1. Sikeres request-response párok, 2. Válasz nélküli kérések. Az eredmények a scapy saját osztályaiban vannak megvalósítva, de kezelhetőek listaként is a következő formátummal: srp() --> [ [ ( sikeres kérés1, kapott válasz1 ), ( sikeres kérés2, kapott válasz2 ), .... ], [ sikertelen kérések ...] ]
  • Eredmény kiíratásához használhatjuk a .summary() vagy .show() függvényeit.
  • Vegyük a sikeresek listáját, azokból vegyük a válaszokat (Receiving and Analyzing Packets blokk). A TCP részhez a .payload attribútumokon végigjárva vagy az általánosabb [] operátorral férhetünk hozzá, aminek a kikért payload osztályát kell megadnunk (packet[TCP]).
  • Eredményként írassuk ki a kapott válaszok source portját (.sport attribútum), azaz a nyitott portokat, amelyekről érkezett válasz.

4. Feladat - Kötelezően választható (1 db) - IMSc plusz feladat (+2 pont)

Az alábbi feladatok közül csak egyet kell kötelezően megoldani a mérés során, mely szabadon választható.

IMSc pont szerezhető mindkét feladat megoldásával!

4.a Feladat - Minimál SSH kliens

Feladat: Írjunk Python szkriptet ssh_client.py néven, amely egy minimális, interaktív SSH klienst valósít meg. Az SSH kapcsolat kezeléséhez használjuk a pxssh modult, amit a pexpect csomag részeként tudunk telepíteni. Érdemes megnézni a pxssh dokumentációját.

  • ssh_client.py.txt: Skeleton fájl a 4. feladathoz - Nevezd át!

  • A scriptet NE indítsuk rendszergazdai jogosultsággal!

  • SSH szerver címét indítási paraméterként adjuk meg!

  • Felhasználónév/jelszó párost kérjük be a konzolról interaktív módon! Jelszó biztonságos bekérésére használjuk a getpass modul getpass() függvényét, ami nem jeleníti meg a leütött karaktereket!

  • Az SSH kapcsolatot kezelő objektum a pxssh() függvénnyel kérhető ki, amin a login() függvényt meghívva csatlakozhatunk az SSH szerverhez. Példakód az SSH kapcsolatot kezelő osztály dokumentációjában található!

  • Kezeljük a hibás kapcsolódásnál felmerülő kivételeket/hibákat!

  • Az interaktív konzolt a prompt-tal együtt a konzolról olvasással valósítsuk meg egy kvázi végtelen ciklusban.

  • q/Q karakterekre, illetve CTRL+C kombinációra lépjünk ki! Ne felejtsük el ekkor bontani a kapcsolatot!

  • A begépelt parancsokat folyamatosan, az előző parancsokat megőrizve mentsük el a saját ssh.log fájlunkba.

  • A laborban lévő image-ek nagy valószínűséggel futtatnak SSH szervert, amivel tesztelhetjük a kliensünket:

    • Hoszt: localhost
    • Felhasználó: vagrant
    • Jelszó: vagrant