9.15. Namen en DNS

Elke pagina tot nu toe heeft numerieke IP-adressen gebruikt – 192.168.1.20 en dergelijke. Echte applicaties doen dat vrijwel nooit. Servers heten example.com of api.example.com, en de applicatie zoekt de naam tijdens runtime op om een IP te vinden waar pakketten naartoe gestuurd kunnen worden. Die opzoeking is het Domain Name System, oftewel DNS.

9.15.1. Waar een naam naar verwijst

Een naam is slechts een label. example.com draagt zelf geen IP-informatie – die moet worden opgezocht, op dezelfde manier als een telefoonnummer in een telefoonboek wordt opgezocht. De DNS-infrastructuur is het gedistribueerde telefoonboek van het internet, en het resultaat van een opzoeking is een of meer IP-adressen waarmee de camera verbinding kan maken.

example.com  ->  93.184.216.34

Een enkele naam verwijst vaak naar verschillende adressen (voor load balancing, geografische redundantie, IPv4- en IPv6-versies van dezelfde dienst). Elk daarvan werkt; de applicatie kiest er één en probeert die, en valt terug op de volgende als die faalt.

9.15.2. Hoe de opzoeking verloopt

Wanneer de camera om example.com vraagt:

  1. De camera stuurt een klein UDP-datagram (ja, UDP – zie UDP – verstuur een pakket, hoop op het beste) naar de geconfigureerde DNS-server. Het adres van de DNS-server kwam uit dezelfde DHCP-uitwisseling die de camera zijn eigen IP gaf.

  2. De DNS-server heeft het antwoord mogelijk al in de cache (er is recent om gevraagd). In dat geval antwoordt hij onmiddellijk.

  3. Zo niet, dan loopt de DNS-server de globale DNS-hiërarchie af: vraag de root-servers naar .com, vraag die servers naar example.com, vraag die servers naar de naam. De hele boomdoorloop is verborgen voor de camera; de camera ziet één query en één antwoord.

  4. De DNS-server slaat het resultaat in de cache op voor de volgende keer en stuurt het antwoord terug naar de camera als nog een UDP-datagram.

De hele uitwisseling duurt doorgaans een paar milliseconden bij een warme cache, tot een honderdtal bij een koude.

9.15.3. De Python-interface

De functie getaddrinfo() doet de opzoeking en retourneert een adres dat klaar is om aan een socket-constructor te geven:

import socket

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

De signatuur is getaddrinfo(host, port). De retourwaarde is een lijst van 5-tuples (één per opgezocht adres); de [0] kiest de eerste en de [-1] kiest het laatste veld, namelijk de (ip, port)-tuple die rechtstreeks aan een socket kan worden gegeven:

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

De meeste cameracode die met een externe server praat, begint met die ene regel. De asyncio-helpers (asyncio.open_connection()) doen de opzoeking intern als een naam wordt doorgegeven in plaats van een numeriek IP, dus async-code roept doorgaans getaddrinfo() niet rechtstreeks aan.

9.15.4. Wat er mis kan gaan

  • Geen DNS-server bereikbaar. Als Wi-Fi net is opgestart en de verbinding wankel is, kan de eerste getaddrinfo()-aanroep een timeout krijgen. Er wordt een OSError opgeworpen; probeer opnieuw zodra de verbinding stabiel is.

  • Naam bestaat niet. Een typefout of een verouderde naam werpt een OSError op nadat de DNS-server “geen dergelijke naam” retourneert. De foutcode onderscheidt dit van “DNS-server onbereikbaar”, maar voor de meeste camera-applicaties is het beleid van opnieuw proberen/opgeven hetzelfde.

  • Geretourneerd adres werkt niet. DNS kan een adres retourneren dat de dienst niet langer host. De oplossing is terug te vallen op de volgende invoer in de resultatenlijst van getaddrinfo(), of de naam later opnieuw op te zoeken (de DNS-cache zal dan waarschijnlijk bijgewerkt zijn).

  • Captive portals. Sommige Wi-Fi-netwerken onderscheppen DNS en retourneren het IP van de captive-portal-pagina voor alles. De camera lijkt verbinding te maken, maar de gegevens die hij terugkrijgt komen niet overeen met wat de eigenlijke dienst zou sturen. Niet gebruikelijk in ingezette omgevingen, maar iets dat voorkomt op conferentie-Wi-Fi en soortgelijke netwerken.

9.15.5. De eigen naam van de camera

De pagina’s tot nu toe hebben de namen van andere apparaten opgezocht. De camera heeft ook een eigen naam die hij aan het lokale netwerk bekendmaakt wanneer hij om een adres vraagt. De standaard is een generieke identifier die per board verschilt; network.hostname() overschrijft die met iets wat de rest van het netwerk zal herkennen:

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

Stel de hostnaam in voordat je de interface omhoog brengt, zodat de naam wordt verzonden als onderdeel van het initiële adresverzoek van de camera in plaats van erna.

Twee dingen gebeuren nu zodra de camera zich bij het netwerk heeft aangesloten. Ten eerste registreren de meeste thuisrouters de hostnamen waaraan ze adressen toewijzen in hun eigen lokale opzoeking, zodat andere apparaten de cam kunnen bereiken als kitchen-cam – zonder het numerieke adres te hoeven kennen dat de router toevallig heeft toegewezen. (Enterprise-netwerken honoreren dit al dan niet; het gedrag is aan de router.) Ten tweede draait de cam zelf standaard een mDNS-responder, zodat dezelfde naam ook bereikbaar is als kitchen-cam.local op elk netwerk waarvan de clients mDNS begrijpen – wat de meeste moderne desktopbesturingssystemen doen.

Notitie

Geef de kale hostnaam door aan network.hostname() – gewoon "kitchen-cam", zonder .local-achtervoegsel. De .local-vorm is wat mDNS tijdens het opzoeken toevoegt; door die in de hostnaam te bakken laat je de cam kitchen-cam.local als een gewone hostnaam adverteren, wat niet is wat een van beide kanten van de opzoeking verwacht.

9.15.6. Wanneer namen niet genoeg zijn

Een paar situaties waarbij DNS niet helpt:

  • Ontdekking op het lokale netwerk. Standaard-DNS beantwoordt vragen over namen die in de globale directory zijn geregistreerd; het weet niets over apparaten op het lokale segment. Multicast DNS (mDNS) is het systeem dat dat gat opvult. Elk deelnemend apparaat sluit zich aan bij een speciale multicastgroep op het lokale netwerk en luistert naar query’s; wanneer een apparaat om een naam vraagt die eindigt op .local, antwoordt het apparaat dat die naam bezit rechtstreeks. Geen centrale server, geen DNS-configuratie. Bonjour op Apple-apparaten, Avahi op Linux en Windows 10+ spreken allemaal hetzelfde protocol – en dat is waarom de naam kitchen-cam.local die de vorige sectie instelde, op een thuisnetwerk resolveert zonder dat er iets extra’s geconfigureerd is.

    De kant van de cam in die uitwisseling is de responder, en die draait al. Wat de cam niet heeft is een resolver – de andere helft, die een script zou laten vragen aan het netwerk “waar is printer.local?” en daar een antwoord op zou krijgen. De meegeleverde mDNS-code is alleen-responder (embedded apparaten zijn doorgaans het ding dat gevonden wordt, niet het ding dat zoekt). Wanneer de ontdekking de andere kant op moet stromen, is UDP-broadcast (zie UDP – verstuur een pakket, hoop op het beste) het eenvoudigere antwoord voor het lokale-segment-geval, of voegt een puur-Python-module zoals cbrand/micropython-mdns een volledige resolver toe.

  • IPv6-namen. getaddrinfo() retourneert zowel IPv4- als IPv6-resultaten als beide beschikbaar zijn. Kies de familie die de socket van de applicatie kan gebruiken.

Voor de meeste cameracode is getaddrinfo een one-liner bovenaan elke functie die een netwerkverbinding opent. De voorbeelden elders in deze sectie die tegen "192.168.1.20" draaiden, zouden allemaal op dezelfde manier werken tegen een publieke naam zoals "api.example.com" – gewoon eerst resolven.