Stiva OSI:
Stiva TCP IP:
netcat este un utilitar de retea folosit pentru a trimite/citi mesaje pe conexiuni. Exemplul urmator este pentru a simula o comunicare de baza server / client.
Se instaleaza folosind [sudo] apt-get install netcat
.
nc -l 8081
Aceasta comanda va deschide un socket pe portul 8081 si asculta pentru noi conexiuni.
Noul proces se poate vedea folosind comanda lsof -i -P | grep LISTEN
avand un output asemanator cu acesta:
nc 2527 ubuntu 3u IPv4 69208 0t0 TCP *:8081 (LISTEN)
Ni se specifica numele executabilului, process ID-ul iar in final protocolul de la nivelul transport si port-ul pe care asculta.
echo "salut" | nc 127.0.0.1 8080
Clientul va trimite folosind un pipe “|” (o metoda de a redirectiona output-ul unui proces catre input-ul altui proces in linux) mesajul “salut” catre ip-ul de loopback pe portul pe care ruleaza si serverul. In terminalul in care ruleaza serverul vom observa ca acesta a primit mesajul.
Este un utilitar pentru a vizualiza drumul pe care un pachet il parcurge pentru a ajunge la destinatie si a descoperi eventualele intarzieri. Se foloseste dand ca input un domeniu
ubuntu@ubun2004:~$ traceroute www.google.ro
traceroute to www.google.ro (142.250.185.163), 30 hops max, 60 byte packets
1 _gateway (192.168.13.2) 0.399 ms 0.366 ms 0.328 ms
2 192.168.100.2 (192.168.100.2) 0.667 ms 0.644 ms 0.820 ms
3 StamAcasa.rdsnet.ro (10.0.0.1) 2.802 ms 2.782 ms 2.749 ms
4 TotulVaFiBine.rdsnet.ro (172.19.211.1) 4.249 ms 4.229 ms 4.217 ms
5 10.220.153.20 (10.220.153.20) 19.676 ms * *
6 72.14.216.212 (72.14.216.212) 19.615 ms 19.753 ms 17.989 ms
7 * 74.125.242.227 (74.125.242.227) 17.081 ms 10.23.192.126 (10.23.192.126) 19.847 ms
8 216.239.41.57 (216.239.41.57) 32.737 ms 32.715 ms 72.14.233.180 (72.14.233.180) 17.316 ms
9 108.170.226.2 (108.170.226.2) 33.542 ms 33.990 ms 33.970 ms
10 108.170.251.193 (108.170.251.193) 36.129 ms * 108.170.236.247 (108.170.236.247) 31.870 ms
11 209.85.252.215 (209.85.252.215) 32.228 ms 142.250.210.197 (142.250.210.197) 33.306 ms 142.250.210.209 (142.250.210.209) 32.633 ms
12 108.170.252.65 (108.170.252.65) 34.098 ms fra16s51-in-f3.1e100.net (142.250.185.163) 31.214 ms 108.170.252.65 (108.170.252.65) 32.272 ms
Mesajele trec prin mai multe routere sau servere chiar daca totul se intampla foarte repede. Internetul este format dintr-o multime de retele interconectate intre ele prin routere care stiu sa ghideze pachetele dupa IP destinatie.
O adresa IP este compusa din 4 campuri a cate un byte de exemplu 192.168.5.1
avand reprezentarea binara 11000000.10101000.00000101.00000001
. Fiecare retea, fie ea publica sau privata, are o adresa de retea si o masca care desparte un IP intr-un prefix de retea si un sufix de host. Se noteaza 192.168.5.0/24
si inseamana ca primii 24 de biti din IP sunt fixi iar ceilalti poti fi folositi pentru a atribui IP-uri dispozitivelor din retea. Rețaua aceasta are următorul interval 192.168.5.0 - 192.168.5.255
. Prima adresă este rezervată rețelei iar ultima adresă din interval este rezervată pentru broadcast și nu pot fi asignate unor noduri.
Masca poate fi reprezentata si de forma unei adrese IP dupa urmatoarea regula: toti bitii fixi au valoarea 1, restul au valoarea 0. Masca de 24 ar avea reprezentarea binara 11111111.11111111.11111111.00000000
și în format uman 255.255.255.0
.
64.183.234.9/15
192.168.0.0/16
164.88.24.37/29
În cadrul acestui capitol vom lucra cu python, un limbaj de programare simplu pe care îl vom folosi pentru a crea și trimite pachete pe rețea. Pentru debug și autocomplete, este bine să avem un editor și IDE pentru acest limbaj. În cadrul orelor vom lucra cu Visual Studio Code, dar puteți lucra cu orice alt editor.
# comment pentru hello world
variabila = 'hello "world"'
print (variabila)
# int:
x = 1 + 1
# str:
xs = str(x) + ' ' + variabila
# tuplu
tup = (x, xs)
# lista
l = [1, 2, 2, 3, 3, 4, x, xs, tup]
print (l[2:])
# set
s = set(l)
print (s)
print (s.intersection(set([4, 4, 4, 1])))
# dict:
d = {'grupa': 123, "nr_studenti": 10}
print (d['grupa'], d['nr_studenti'])
lista = [1,5,7,8,2,5,2]
for element in lista:
print (element)
for idx, element in enumerate(lista):
print (idx, element)
for idx in range(0, len(lista)):
print (lista[idx])
idx = 0
while idx < len(lista):
print (lista[idx])
idx += 1
'''
comment pe
mai multe
linii
'''
x = 1
y = 2
print (x + y)
if (x == 1 and y == 2) or (x==2 and y == 1):
print (" x e egal cu:", x, ' si y e egal cu: ', y)
elif x == y:
print ('sunt la fel')
else:
print ("nimic nu e adevarat")
def functie(param = 'oooo'):
'''dockblock sunt comments in care explicam
la ce e buna functia
'''
return "whooh " + param + "!"
def verifica(a, b):
''' aceasta functie verifica
o ipoteza interesanta
'''
if (a == 1 and b == 2) or (a==2 and b == 1):
return 1
elif a == b:
return 0
return -1
import os
import sys
import logging
from os.path import exists
import time
logging.basicConfig(format = u'[LINE:%(lineno)d]# %(levelname)-8s [%(asctime)s] %(message)s', level = logging.NOTSET)
logging.info("Mesaj de informare")
logging.warn("Mesaj de warning")
logging.error("Mesaj de eroare")
try:
1/0
except:
logging.exception("Un mesaj de exceptie!")
program_name = sys.argv[0]
print (program_name)
print ("Exista '/elocal'?", exists('/elocal'))
print (os.path.join('usr', 'local', 'bin'))
for element in "hello world":
sys.stdout.write(element)
sys.stdout.flush()
time.sleep(1)
def main():
print ("functia main")
# un if care verifică dacă scriptul este importat sau apelat ca main
if __name__ == '__main__':
main()
class Grupa:
nume = 'grp'
def __init__(self, nume, numar_studenti):
self.nume = nume
self.numar_studenti = numar_studenti
def _metoda_protected(self):
print ("da")
def __metoda_privata(self):
print ('nu')
def metoda_publica(self):
print ("yes")
g = Grupa('222', '21')
print (g.nume)
print (g.numar_studenti)
print (Grupa.nume)
time.sleep(1)
.Numarul 16 se scrie in binar: 10000 (2^4)
, deci numărăm biții de la dreapta la stânga.
Dacă numărul ar fi stocat într-un tip de date pe 8 biți, s-ar scrie: 00010000
Dacă ar fi reprezentat pe 16 biți, s-ar scrie: 00000000 00010000
, completând cu 0 pe pozițiile mai mari până obținem 16 biți.
În calculatoare există două tipuri de reprezentare a ordinii octeților în funcție de adresele stocate ca octeți:
Pe rețea mesajele transmise trebuie să fie reprezentate într-un mod standardizat, independent de reprezentarea octeților pe mașinile de pe care sunt trimise, și acest standard este dat de Big Endian sau Network Order.
Pentru a verifica ce endianness are calculatorul vostru puteti rula din python:
import sys
print(sys.byteorder)
În python există modulul struct care face conversia din tipul de date standard al limbajului în bytes reprezentând tipuri de date din C. Acest lucru este util fiindcă în cadrul rețelelor vom avea de configurat elemente low-level ale protocoalelor care sunt restricționate pe lungimi fixe de biți. Ca exemplu, headerul UDP este structurat din 4 cuvinte de 16 biți (port sursă, port destinație, lungime și checksum):
import struct
# functia pack ia valorile date ca parametru si le "impacheteaza" dupa un tip de date din C dat
struct.pack(formatare, val1, val2, val3)
# functia unpack face exact opusul, despacheteaza un sir de bytes in variabile dupa un format
struct.unpack(formatare, sir_de_bytes)
Format Octeti | Tip de date C | Tip de date python | Nr. biți | Note |
---|---|---|---|---|
x |
pad byte | no value | 8 | |
c |
char | bytes of length 1 | 8 | |
b |
signed char | integer | 8 | (1) |
B |
unsigned char | integer | 8 | |
? |
_Bool | bool | (2) | |
h |
short | integer | 16 | |
H |
unsigned short | integer | 16 | |
i |
int | integer | 32 | |
I |
unsigned int | integer | 32 | |
l |
long | integer | 32 | |
L |
unsigned long | integer | 32 | |
q |
long long | integer | 64 | (3) |
Q |
unsigned long long | integer | 64 | (3) |
f |
float | float | 32 | |
d |
double | float | 64 | |
s |
char[] | bytes | (1) | |
p |
char[] | bytes | (1) | |
P |
void * | integer |
Note:
Metodele de pack/unpack sunt dependente de ordinea octeților din calculator. Pentru a seta un anumit tip de endianness cand folosim funcțiile din struct, putem pune înaintea formatării caracterele următoare:
Caracter | Byte order |
---|---|
@ | native |
< | little-endian |
> | big-endian |
! | network (= big-endian) |
numar = 16
# impachetam numarul 16 intr-un 'unsigned short' pe 16 biti cu network order
octeti = struct.pack('!H', numar)
print("Network Order: ")
for byte in octeti:
print (bin(byte))
# impachetam numarul 16 intr-un 'unsigned short' pe 16 biti cu Little Endian
octeti = struct.pack('<H', numar)
print("Little Endian: ")
for byte in octeti:
print (bin(byte))
# B pentru 8 biti, numere unsigned intre 0-256
struct.pack('B', 300)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
struct.error: ubyte format requires 0 <= number <= 255
# string de 10 bytes, sunt codificati primii 10 si
# restul sunt padded cu 0
struct.pack('10s', 'abcdef'.encode('utf-8'))
b'abcdef\x00\x00\x00\x00'
# numarul 256 packed in NetworkOrder pe 64 de biti
struct.pack('!L', 256)
b'\x00\x00\x01\x00'
# numarul 256 packed in LittleEndian pe 64 de biti
struct.pack('<L', 256)
b'\x00\x01\x00\x00'
operator = '+'.encode('utf-8')
# encode 12+13 as byte string
# 32-bit network order integers and 1 byte-char operator
octeti = struct.pack('!IcI', 12, operator, 13)
print(octeti)
# b'\x00\x00\x00\x0c+\x00\x00\x00\r'
valori = struct.unpack('!IcI', octeti)
print(valori)
# (12, b'+', 13)
# Atentie!
# accesarea unui singur octet returneaza valoarea lui ca numar
print(octeti[4]) # 43 echivalent cu codul ascii pt '+'
# slicing returneaza stringul de bytes
print(octeti[4:5]) # b'+' echivalent cu codul ascii pt '+'
scapy este o librărie care acoperă o serie mare de funcționalități ce pot fi implementate programatic. Principalele features sunt cele de creare și manipulare a pachetelor, dar și aceea de a capta pachetele care circulă pe rețea. Pentru a scana pachetele care circulă, similar cu tcpdump, există funcția sniff
. Pentru a instala librăria, folosim pipy:
pip install --pre scapy[complete]
Captarea pachetelor se face folosind sniff:
pachete = sniff()
# Trimiteti de pe router un mesaj UDP catre server: sendto(b'salut', ('server', 2222))
# Apasati Ctrl+C pentru a opri functia care monitorizeaza pachete
<Sniffed: TCP:0 UDP:1 ICMP:0 Other:0>
pachete[UDP][0].show()
###[ Ethernet ]###
dst= 02:42:c6:0a:00:02
src= 02:42:c6:0a:00:01
type= IPv4
###[ IP ]###
version= 4
ihl= 5
tos= 0x0
len= 33
id= 7207
flags= DF
frag= 0
ttl= 64
proto= udp
chksum= 0x928d
src= 198.10.0.1
dst= 198.10.0.2
\options\
###[ UDP ]###
sport= 2222
dport= 2330
len= 13
chksum= 0x8c36
###[ Raw ]###
load= 'salut'
Funcția sniff()
ne permite să captăm pachete în cod, la fel cum am face cu wireshark sau tcpdump. De asemenea putem salva captura de pachete în format .pcap cu tcpdump:
tcpdump -i any -s 65535 -w example.pcap
și putem încărca pachetele în scapy pentru a le procesa:
packets = rdpcap('example.pcap')
for pachet in packets:
if pachet.haslayer(ARP):
pachet.show()
Mai mult, funcția sniff are un parametrul prin care putem trimite o metodă care să proceseze pachetul primit în funcție de conținut:
def handler(pachet):
if pachet.haslayer(TCP):
if pachet[TCP].dport == 80: #or pachet[TCP].dport == 443:
if pachet.haslayer(Raw):
raw = pachet.getlayer(Raw)
print(raw.load)
sniff(prn=handler)
Putem converti și octeții obținuți printr-un socket raw dacă știm care este primul layer (cel mai de jos):
# presupunem ca avem octetii corespunzatorui unui pachet UDP in python:
raw_socket_date = b'E\x00\x00!\xc2\xd2@\x00@\x11\xeb\xe1\xc6\n\x00\x01\xc6\n\x00\x02\x08\xae\t\x1a\x00\r\x8c6salut'
pachet = IP(raw_socket_date)
pachet.show()
###[ IP ]###
version= 4
ihl= 5
tos= 0x0
len= 33
id= 49874
flags= DF
frag= 0
ttl= 64
proto= udp
chksum= 0xebe1
src= 198.10.0.1
dst= 198.10.0.2
\options\
###[ UDP ]###
sport= 2222
dport= 2330
len= 13
chksum= 0x8c36
###[ Raw ]###
load= 'salut'