9.15. Nume și DNS¶
Fiecare pagină de până acum a folosit adrese IP numerice – 192.168.1.20 și altele asemenea. Aplicațiile reale aproape niciodată nu fac asta. Serverele sunt denumite example.com sau api.example.com, iar aplicația caută numele în timpul execuției pentru a găsi un IP către care să trimită pachete. Această căutare este Domain Name System, sau DNS.
9.15.1. La ce se rezolvă un nume¶
Un nume este doar o etichetă. example.com nu poartă în sine nicio informație despre IP – trebuie căutat, la fel cum un număr de telefon este căutat într-o carte de telefon. Infrastructura DNS este cartea de telefon distribuită a internetului, iar rezultatul unei căutări este una sau mai multe adrese IP la care camera se poate conecta.
example.com -> 93.184.216.34
Un singur nume se rezolvă adesea la mai multe adrese (pentru echilibrarea încărcării, redundanță geografică, versiuni IPv4 și IPv6 ale aceluiași serviciu). Oricare dintre ele funcționează; aplicația alege una și o încearcă, revenind la următoarea dacă aceasta eșuează.
9.15.2. Cum are loc căutarea¶
Când camera cere example.com:
Camera trimite o mică datagramă UDP (da, UDP – vezi UDP – trimite un pachet și speră la ce e mai bun) către serverul DNS configurat. Adresa serverului DNS a venit din același schimb DHCP care a furnizat camerei propriul IP.
Serverul DNS poate avea deja răspunsul în cache (a fost întrebat recent). Dacă da, răspunde imediat.
Dacă nu, serverul DNS parcurge ierarhia globală DNS: întreabă serverele root despre
.com, întreabă acele servere despreexample.com, întreabă acele servere despre nume. Întreaga parcurgere a arborelui este ascunsă de cameră; camera vede o singură interogare și un singur răspuns.Serverul DNS pune rezultatul în cache pentru data viitoare și trimite răspunsul înapoi către cameră ca o altă datagramă UDP.
Întregul schimb durează de obicei câteva milisecunde cu un cache cald, până la o sută și ceva cu unul rece.
9.15.3. Interfața Python¶
Funcția getaddrinfo() face căutarea și returnează o adresă gata de transmis unui constructor de socket:
import socket
addr = socket.getaddrinfo("example.com", 80)[0][-1]
print(addr)
# ('93.184.216.34', 80)
Semnătura este getaddrinfo(host, port). Valoarea returnată este o listă de 5-tupluri (câte una per adresă rezolvată); [0] alege prima, iar [-1] alege ultimul câmp, care este tuplul (ip, port) ce poate fi transmis direct unui socket:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(socket.getaddrinfo("example.com", 80)[0][-1])
s.send(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
...
Majoritatea codului pentru cameră care comunică cu un server la distanță începe cu acea singură linie. Funcțiile ajutătoare asyncio (asyncio.open_connection()) fac căutarea intern dacă li se transmite un nume în loc de un IP numeric, așa că codul asincron de obicei nu apelează direct getaddrinfo().
9.15.4. Ce poate merge prost¶
Niciun server DNS accesibil. Dacă Wi-Fi tocmai s-a ridicat și legătura este instabilă, primul apel
getaddrinfo()poate expira. Se genereazăOSError; reîncearcă odată ce legătura este stabilă.Numele nu există. O greșeală de tastare sau un nume învechit generează
OSErrordupă ce serverul DNS returnează „niciun astfel de nume”. Codul de eroare distinge acest caz de „server DNS inaccesibil”, dar pentru majoritatea aplicațiilor cu camera politica de reîncercare/renunțare este aceeași.Adresa returnată nu funcționează. DNS poate returna o adresă care nu mai găzduiește serviciul. Soluția este să revii la următoarea intrare din lista de rezultate a
getaddrinfo()sau să cauți numele din nou mai târziu (cache-ul DNS se va fi actualizat probabil până atunci).Portaluri captive. Unele rețele Wi-Fi interceptează DNS-ul și returnează IP-ul paginii portalului captiv pentru tot. Camera va părea că se conectează, dar datele pe care le primește înapoi nu se vor potrivi cu ceea ce ar trimite serviciul real. Nu este frecvent în mediile implementate, dar se întâmplă la Wi-Fi de conferință și în rețele similare.
9.15.5. Numele propriu al camerei¶
Paginile de până acum au căutat numele altor dispozitive. Camera are și ea un nume propriu pe care îl anunță în rețeaua locală atunci când cere o adresă. Valoarea implicită este un identificator generic care variază în funcție de placă; network.hostname() îl suprascrie cu ceva pe care restul rețelei îl va recunoaște:
import network
network.hostname("kitchen-cam")
# ... then bring the link up as usual ...
Setează hostname-ul înainte de a ridica interfața, astfel încât numele să iasă ca parte a cererii inițiale de adresă a camerei, nu după.
Acum se întâmplă două lucruri odată ce camera s-a alăturat rețelei. În primul rând, majoritatea routerelor casnice înregistrează hostname-urile cărora le atribuie adrese în propriul lor sistem local de căutare, astfel încât alte dispozitive să poată ajunge la cameră ca kitchen-cam – fără a fi nevoie să cunoască adresa numerică pe care routerul a atribuit-o. (Rețelele enterprise pot sau nu să respecte acest lucru; comportamentul depinde de router.) În al doilea rând, camera însăși rulează din start un responder mDNS, astfel încât același nume este accesibil și ca kitchen-cam.local pe orice rețea ai cărei clienți înțeleg mDNS – ceea ce majoritatea sistemelor de operare desktop moderne fac.
Notă
Transmite hostname-ul simplu către network.hostname() – doar "kitchen-cam", fără sufixul .local. Forma .local este ceea ce adaugă mDNS la momentul căutării; integrarea ei în hostname face camera să anunțe kitchen-cam.local ca hostname obișnuit, ceea ce nu este ce așteaptă niciuna dintre părțile căutării.
9.15.6. Când numele nu sunt suficiente¶
Câteva situații în care DNS nu ajută:
Descoperire în rețeaua locală. DNS-ul standard răspunde la întrebări despre nume înregistrate în directorul global; nu știe nimic despre dispozitivele din segmentul local. Multicast DNS (mDNS) este sistemul care umple acel gol. Fiecare dispozitiv participant se alătură unui grup special de multicast în rețeaua locală și ascultă interogările; când un dispozitiv cere un nume care se termină în
.local, oricare dispozitiv deține acel nume răspunde direct. Niciun server central, nicio configurare DNS. Bonjour pe dispozitivele Apple, Avahi pe Linux și Windows 10+ vorbesc toate același protocol – de aceea numelekitchen-cam.localconfigurat în secțiunea anterioară se rezolvă într-o rețea casnică fără nimic suplimentar configurat.Partea camerei din acel schimb este responderul, și el rulează deja. Ce nu are camera este un resolver – cealaltă jumătate, care i-ar permite unui script să întrebe rețeaua „unde este
printer.local?” și să primească un răspuns. Codul mDNS inclus este doar responder (dispozitivele embedded sunt de obicei lucrul care este găsit, nu lucrul care caută). Când descoperirea trebuie să curgă în direcția opusă, broadcast-ul UDP (vezi UDP – trimite un pachet și speră la ce e mai bun) este răspunsul mai simplu pentru cazul segmentului local, sau un modul pur-Python precum cbrand/micropython-mdns adaugă un resolver complet.Nume IPv6.
getaddrinfo()returnează atât rezultate IPv4, cât și IPv6 dacă ambele sunt disponibile. Alege familia pe care o poate folosi socket-ul aplicației.
Pentru majoritatea codului care rulează pe cameră, getaddrinfo este o singură linie în partea de sus a oricărei funcții care deschide o conexiune de rețea. Exemplele din alte locuri din această secțiune care rulau pe "192.168.1.20" ar funcționa toate la fel pe un nume public precum "api.example.com" – doar rezolvă-l mai întâi.