QVINTVS · SCRIBET

Dnsmasq: IP-Adressen nach Interface vergeben

In diesem Blogpost möchte ich beleuchten, wie man mithilfe von dnsmasq IP-Adressen abhängig vom Interface vergibt.

Vorgeschichte

Bei mir läuft ein Raspberry Pi als Mädchen für alles im Netzwerk. Er stellt etwa per CIFS Dateien im Netzwerk bereit (NAS), unterhält eine kleine Website mit Statistiken über sich und das Netzwerk (mithilfe von Munin) oder spannt mithilfe von hostapd ein WLAN auf. Und er verteilt eben auch per DHCP IP-Adressen im Netzwerk, sowohl für die WLAN- als auch für die kabelgebundenen Clients. Als Software kommt hierfür dnsmasq zum Einsatz, weil ich mich mit diesem Programm bereits seit geraumer Zeit ganz gut auskenne.

Der Pi hat zwei Netzwerk-Interfaces: eth0 für Kabelnetzwerk (Ethernet) und wlan0 für Drahtlosnetzwerk. Letzteres ist bereits ordentlich mit hostapd aufgesetzt, es fehlt nur noch der DHCP-Server für die IP-Adressvergabe. Auf der Kommandozeile sieht das so aus:

% ip -4 addr list 
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN 
    inet 127.0.0.1/8 scope host lo
4: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    inet 10.37.59.2/26 brd 10.37.59.63 scope global eth0
5: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
    inet 10.37.59.65/26 brd 10.37.59.127 scope global wlan0

Subnetze

Der Trick an der Sache ist, sein Netzwerk ordentlich einzuteilen. Der gesamte in meinem Heimnetzwerk vergebene Adressraum ist 10.37.59.0/24 (der exotische Adressraum hängt mit VPN-Verbindungen zusammen). Diesen habe ich nun eindeutig aufgeteilt, sodass klar definiert ist, welches Netzwerksegment sich wo befindet:

Mein Internet-Gateway, eine Fritz!Box, steht unter der Adresse 10.37.59.1 bereit, der Pi besitzt auf eth0 die Adresse 10.37.59.2 und auf wlan0 die Adresse 10.37.59.65 (alle drei sind statisch, also ohne DHCP, fix vergeben). Dementsprechend sieht die Routingtabelle für den Pi wie folgt aus:

% ip -4 route list
default via 10.37.59.1 dev eth0 
10.37.59.0/26 dev eth0  proto kernel  scope link  src 10.37.59.2 
10.37.59.64/26 dev wlan0  proto kernel  scope link  src 10.37.59.65 

Tagging

dnsmasq ermöglicht es, anhand bestimmter Kriterien, z.B. der MAC-Adresse des Clients, sogenannte Tags auf einer Verbindung zu setzen. Diese ermöglichen eine „Wiedererkennung“ der Verbindung an anderer Stelle der Konfiguration, sodass man etwa unterschiedliche DHCP-Optionen versenden kann. Im konkreten Fall etwa steht mein Internet-Gateway (die Fritz!Box) wie gesagt unter der Adresse 10.37.59.1 zur Verfügung — diese Adresse befindet sich jedoch nicht im selben Subnetz wie die WLAN-Clients, sodass diese ihnen nicht als Standardroute übermittelt werden kann (ein manueller Versuch würde im übrigen mit network unreachable quittiert). Für die WLAN-Clients stellt vielmehr der Pi als Verbindungspunkt von Kabel- und WLAN-Netz das Gateway da (und für den Pi wiederum die Fritz!Box).

Leider bietet dnsmasq von Haus aus keine direkte Möglichkeit, einen Tag basierend auf dem Interface, auf dem die DHCP-Anfrage eingeht (also hier eth0 oder wlan0) zu setzen, sodass ich die Gateway-Option nicht ordentlich vergeben kann. Es gibt aber eine indirekte Möglichkeit — hier ein Zitat aus der Manpage von dnsmasq(8) zur Option dhcp-range:

For directly connected networks (ie, networks on which the machine running dnsmasq has an interface) the netmask is optional: dnsmasq will determine it from the interface configuration.

Daraus ergibt sich, dass dnsmasq die per DHCP vergebenen IP-Adressen auf die Netzmaske (→ folglich auch das Subnetz) abstimmt, für das jenes Interface, auf dem die DHCP-Anfrage hereinkommt, konfiguriert ist. Kommt also eine Anfrage auf wlan0 rein, würde dnsmasq gerne IP-Adressen aus dem Bereich 10.37.59.64/26 vergeben, analog für eth0. Mit diesem Wissen kann die dnsmasq-Konfiguration leicht so geändert werden, dass dnsmasq IPs aus den gewünschten Subnetzen vorfindet:

dhcp-range=set:wired,10.37.59.3,10.37.59.62,12h
dhcp-range=set:wifi,10.37.59.66,10.37.59.126,12h

Da dnsmasq nun IP-Adressen abhängig vom Interface vergibt, kann ich mithilfe von set:wired bzw. set:wifi in den jeweilig einem Interface entsprechenden dhcp-range-Direktiven einen Tag setzen. Mit anderen Worten: dnsmasq vergibt IP-Adressen abhängig vom Interface, und ich vergebe dann Tags abhängig von der IP-Adresse. Effektiv bin ich damit in der Lage, indirekt Tags abhängig vom Interface zu vergeben.

Nicht vergessen werden darf, in die Routingtabelle der Fritz!Box eine Route einzutragen, die der Fritz!Box klarmacht, wohin sie den Antwort-Traffic aus dem Internet, der vom WLAN generiert wurde, zurückrouten soll. Dazu muss eine Route eingetragen werden, die diesem Kommando entspricht:

# ip route add 10.37.59.64/26 via 10.37.59.2

Da die Fritz!Box ja per Kabel angeschlossen ist, muss sie über das eth0-Interface des Pi routen.

Nutzen

Der Nutzen besteht darin, dass ich nun von allen dnsmasq-Direktiven Gebrauch machen kann, die sich auf bestimmte Tags beschränken. Als Beispiel sei zunächst dhcp-option genannt, das ich benutze, um die Standardroute (den Standard-Gateway) für Kabel- bzw. WLAN-Clients unterschiedlich zu setzen (Option 3 ist router, also das Standard-Gateway):

dhcp-option=tag:wired,3,10.37.59.1
dhcp-option=tag:wifi,3,10.37.59.65

Kabel-Clients bekommen direkt die Fritz!Box als Gateway, WLAN-Clients den Pi (der dann ja an die Fritz!Box weiterleitet).

Ein anderes Beispiel: Ich kann Kabel- und WLAN-Clients unterschiedliche Domainvervollständig verpassen:

domain=cable.internal.example.org,10.37.59.0/26
domain=wifi.internal.example.org,10.37.59.64/26

Meldet sich der Rechner foo über Kabel an, wird er eine Adresse aus dem 10.37.59.0/26er-Raum erhalten und sein FQDN lautet dann foo.cable.internal.example.org. Meldet er sich aber über WLAN an, so erhält er eine Adresse aus dem 10.37.59.64/26er-Raum und sein FQDN lautet dann foo.wifi.internal.example.org. Somit lässt sich leicht am Domain-Namen ablesen, ob ein Rechner per Kabel- oder WLAN-Verbindung im Netzwerk ist.

Valete.