9.15. Namn och DNS

Varje sida hittills har använt numeriska IP-adresser – 192.168.1.20 och liknande. Verkliga applikationer gör nästan aldrig det. Servrar heter example.com eller api.example.com, och applikationen slår upp namnet vid körning för att hitta en IP att skicka paket till. Den uppslagningen är Domain Name System, eller DNS.

9.15.1. Vad ett namn löses upp till

Ett namn är bara en etikett. example.com bär ingen IP-information i sig själv – det måste slås upp, på samma sätt som ett telefonnummer slås upp i en telefonkatalog. DNS-infrastrukturen är internets distribuerade telefonkatalog, och resultatet av en uppslagning är en eller flera IP-adresser som kameran kan ansluta till.

example.com  ->  93.184.216.34

Ett enda namn löses ofta upp till flera adresser (för lastbalansering, geografisk redundans, IPv4- och IPv6-versioner av samma tjänst). Vilken som helst av dem fungerar; applikationen väljer en och provar den, och faller tillbaka på nästa om den misslyckas.

9.15.2. Hur uppslagningen sker

När kameran frågar efter example.com:

  1. Kameran skickar ett litet UDP-datagram (ja, UDP – se UDP – skicka ett paket, hoppas på det bästa) till sin konfigurerade DNS-server. DNS-serverns adress kom från samma DHCP-utbyte som gav kameran dess egen IP.

  2. DNS-servern kan redan ha svaret i cache (den har blivit tillfrågad nyligen). Om så är fallet svarar den omedelbart.

  3. Om inte vandrar DNS-servern genom den globala DNS-hierarkin: fråga root-servrarna om .com, fråga de servrarna om example.com, fråga de servrarna om namnet. Hela trädvandringen är dold för kameran; kameran ser en förfrågan och ett svar.

  4. DNS-servern cachar resultatet till nästa gång och skickar tillbaka svaret till kameran som ytterligare ett UDP-datagram.

Hela utbytet tar vanligtvis några millisekunder med en varm cache, upp till ett hundratal med en kall.

9.15.3. Python-gränssnittet

Funktionen getaddrinfo() gör uppslagningen och returnerar en adress redo att lämnas till en socket-konstruktor:

import socket

addr = socket.getaddrinfo("example.com", 80)[0][-1]
print(addr)
# ('93.184.216.34', 80)

Signaturen är getaddrinfo(host, port). Returvärdet är en lista med 5-tupler (en per upplöst adress); [0] väljer den första och [-1] väljer det sista fältet, vilket är den (ip, port)-tupel som en socket kan lämnas direkt:

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")
    ...

Det mesta av kamerakod som kommunicerar med en fjärrserver börjar med den enda raden. Hjälpfunktionerna i asyncio (asyncio.open_connection()) gör uppslagningen internt om ett namn skickas i stället för en numerisk IP, så asynkron kod anropar vanligtvis inte getaddrinfo() direkt.

9.15.4. Vad som kan gå fel

  • Ingen DNS-server nåbar. Om Wi-Fi precis har kommit upp och länken är ostabil kan det första getaddrinfo()-anropet få timeout. OSError utlöses; försök igen när länken är stabil.

  • Namnet finns inte. Ett stavfel eller ett föråldrat namn utlöser OSError efter att DNS-servern returnerat ”inget sådant namn”. Felkoden skiljer detta från ”DNS-server inte nåbar”, men för de flesta kameraapplikationer är policyn för att försöka igen eller ge upp densamma.

  • Den returnerade adressen fungerar inte. DNS kan returnera en adress som inte längre är värd för tjänsten. Lösningen är att falla tillbaka på nästa post i resultatlistan från getaddrinfo(), eller slå upp namnet igen senare (DNS-cachen har sannolikt uppdaterats vid det laget).

  • Captive portals. Vissa Wi-Fi-nätverk avlyssnar DNS och returnerar IP:n för captive-portal-sidan för allting. Kameran kommer att verka ansluta men de data den får tillbaka kommer inte att matcha vad den faktiska tjänsten skulle skicka. Inte vanligt i driftsatta miljöer, men något som händer på konferens-Wi-Fi och liknande nätverk.

9.15.5. Kamerans eget namn

Sidorna hittills har slagit upp andra enheters namn. Kameran har också ett eget namn som den annonserar till det lokala nätverket när den ber om en adress. Standarden är en generisk identifierare som varierar mellan kort; network.hostname() åsidosätter den med något som resten av nätverket känner igen:

import network
network.hostname("kitchen-cam")
# ... then bring the link up as usual ...

Ställ in värdnamnet innan du tar upp gränssnittet, så att namnet går ut som en del av kamerans inledande adressförfrågan i stället för efteråt.

Två saker händer nu när kameran väl har anslutit till nätverket. För det första registrerar de flesta hemroutrar de värdnamn de delar ut adresser till i sin egen lokala uppslagning, så att andra enheter kan nå kameran som kitchen-cam – utan att behöva känna till den numeriska adress routern råkade tilldela. (Företagsnätverk kanske respekterar detta eller inte; beteendet är upp till routern.) För det andra kör kameran själv en mDNS-responder direkt ur lådan, så att samma namn också är nåbart som kitchen-cam.local på vilket nätverk som helst vars klienter förstår mDNS – vilket de flesta moderna skrivbordsoperativsystem gör.

Anteckning

Skicka det rena värdnamnet till network.hostname() – bara "kitchen-cam", inget .local-suffix. Formen .local är vad mDNS lägger till vid uppslagningstillfället; att baka in det i värdnamnet får kameran att annonsera kitchen-cam.local som ett vanligt värdnamn, vilket inte är vad någon sida av uppslagningen förväntar sig.

9.15.6. När namn inte räcker

Några situationer som DNS inte hjälper till med:

  • Upptäckt på det lokala nätverket. Standard-DNS besvarar frågor om namn som är registrerade i den globala katalogen; det vet ingenting om enheter på det lokala segmentet. Multicast DNS (mDNS) är systemet som fyller den luckan. Varje deltagande enhet går med i en särskild multicast-grupp på det lokala nätverket och lyssnar efter förfrågningar; när en enhet frågar efter ett namn som slutar på .local svarar den enhet som äger det namnet direkt. Ingen central server, ingen DNS-konfiguration. Bonjour på Apple-enheter, Avahi på Linux och Windows 10+ talar alla samma protokoll – vilket är anledningen till att namnet kitchen-cam.local som föregående avsnitt satte upp löses på ett hemnätverk utan något extra konfigurerat.

    Kamerans sida av det utbytet är respondern, och den körs redan. Vad kameran inte har är en resolver – den andra halvan, som skulle låta ett skript fråga nätverket ”var finns printer.local?” och få ett svar tillbaka. Den medföljande mDNS-koden är endast responder (inbyggda enheter är vanligtvis det som hittas, inte det som gör hittandet). När upptäckt måste flöda i den andra riktningen är UDP-broadcast (se UDP – skicka ett paket, hoppas på det bästa) det enklare svaret för det lokala-segment-fallet, eller en ren Python-modul som cbrand/micropython-mdns lägger till en fullständig resolver.

  • IPv6-namn. getaddrinfo() returnerar både IPv4- och IPv6-resultat om båda är tillgängliga. Välj den familj som applikationens socket kan använda.

För det mesta av kod på kamerasidan är getaddrinfo en enradare överst i varje funktion som öppnar en nätverksanslutning. Exemplen på andra ställen i det här avsnittet som kördes mot "192.168.1.20" skulle alla fungera på samma sätt mot ett publikt namn som "api.example.com" – man måste bara lösa upp det först.