OpenBSD: Netzwerk und Routing

OpenBSD: Netzwerk und Routing

OpenBSD-Routing mit ospfd, DHCP-Server dhcpd, IPv6-Konfiguration und Netzwerk-Troubleshooting. Praxisnahe Anleitung für stabile und sichere Netze.

OpenBSD trennt die Aufgaben der Netzwerk-Konfiguration klar zwischen Kernel und User Space. Die Kernel-Routing-Tabelle entscheidet über die Paket-Weiterleitung, ospfd und bgpd implementieren dynamische Routing-Protokolle, dhcpd verteilt IP-Adressen automatisch. Die IPv6-Unterstützung ist vollständig integriert.

Der Artikel zur Systemkonfiguration zeigt die Basis-Netzwerk-Konfiguration über hostname.if-Dateien. Die Netzwerk-Grundlagen-Serie vermittelt die übergreifenden Konzepte; hier geht es um deren OpenBSD-spezifische Umsetzung bei Routing, DHCP, IPv6 und Netzwerk-Troubleshooting.

Routing-Grundlagen

Kernel-Routing-Tabelle

Die Kernel-Routing-Tabelle entscheidet über Paket-Weiterleitung. OpenBSD speichert Routen im Kernel-Memory – persistente Speicherung erfolgt in Konfigurationsdateien.

Um die aktuelle Routing-Tabelle anzuzeigen:

$ route -n show
Routing tables

Internet:
Destination        Gateway            Flags   Refs      Use   Mtu  Prio Iface
default            192.168.1.1        UGS        0        0     -     8 vio0
224/4              127.0.0.1          URS        0        0 32768     8 lo0
127/8              127.0.0.1          UGRS       0        0 32768     8 lo0
127.0.0.1          127.0.0.1          UHhl       1        2 32768     1 lo0
192.168.1/24       192.168.1.65       UCn        2        0     -     4 vio0
192.168.1.1        cc:32:e5:e9:1b:0b  UHLch      1        2     -     3 vio0
192.168.1.65       52:54:00:ab:cd:09  UHLl       0        3     -     1 vio0
192.168.1.255      192.168.1.65       UHb        1        2     -     1 vio0
[...]
Internet6:
[IPv6-Einträge gekürzt – lo0-interne Routen für Kompatibilitäts-Adressen]

Die Option -n unterdrückt die Namensauflösung und zeigt die Adressen numerisch an.

Die Spalten bedeuten:

  • Destination – Ziel-Netzwerk oder Host
  • Gateway – die Adresse, an die das Paket als Nächstes geschickt wird. Liegt das Ziel in einem anderen Netz, ist das gewöhnlich die IP-Adresse eines Routers. Bei direkt erreichbaren Zielen kann OpenBSD hier eine Hardware-Adresse des Netzwerkinterfaces anzeigen, beispielsweise eine MAC-Adresse.
  • Flags – aktive Route-Eigenschaften (siehe unten)
  • Refs – Anzahl aktiver Referenzen auf diese Route
  • Use – Anzahl der Pakete, die über diese Route liefen
  • Mtu – Maximum Transmission Unit; die maximale Größe eines IP-Pakets in Byte, das über die Route ohne Fragmentierung übertragen werden kann. - bedeutet, dass für diese Route kein eigener MTU-Wert gesetzt ist.
  • Prio – Routing-Priorität; niedrigere Werte werden bevorzugt
  • Iface – ausgehendes Interface

Die hier sichtbaren Routing-Prioritäten 4 und 8 entstehen beim Anlegen der Routen. Jedes Interface besitzt eine Routing-Priorität zwischen 0 und 15. Standardmäßig beträgt sie 0. Für WLAN-Interfaces verwendet OpenBSD 4, für umb(4) 6 und für carp(4) 15. Die Interface-Priorität kann mit ifconfig geändert werden.

OpenBSD verwendet die Interface-Priorität als Ausgangswert für die Priorität neuer Routen. Eine Route zu einem direkt angeschlossenen Netz entsteht beispielsweise durch die Konfiguration einer IP-Adresse und Netzmaske auf einem Interface. Für eine solche Route addiert OpenBSD 4 (RTP_CONNECTED) zur Interface-Priorität. Für eine neue statische Route werden 8 (RTP_STATIC) addiert. Hat das Interface die Priorität 0, ergeben sich daraus die in der Beispielausgabe sichtbaren Werte 4 und 8.

„Direkt verbunden“ und „statisch“ bezeichnen dabei unterschiedliche Eigenschaften. Direkt verbunden bedeutet, dass das Ziel ohne einen dazwischenliegenden Router über ein Interface erreichbar ist. Statisch bedeutet, dass die Route ausdrücklich eingerichtet wurde und nicht beispielsweise aus der Interface-Konfiguration oder durch ein Routing-Protokoll entstanden ist. Eine statische Route kann deshalb auch zu einem direkt erreichbaren Ziel führen.

Die Flags zeigen den Zustand jeder Route. Die wichtigsten in der täglichen Praxis:

Flag Name Bedeutung
U UP Route ist aktiv
G GATEWAY Pakete werden über einen Gateway weitergeleitet
R REJECT Ziel wird als nicht erreichbar zurückgewiesen
S STATIC Manuell eingetragene Route
H HOST Route zu einem einzelnen Host (nicht Netzwerk)
L LLINFO Link-Layer-Adresse (MAC) bekannt
C CLONING Bei Benutzung dieser Route können daraus neue, spezifischere Routen erzeugt werden
c CLONED Die Route wurde aus einer Cloning-Route erzeugt
n CONNECTED Direkt verbundenes Interface
h CACHED Von einer Gateway-Route referenziert
l LOCAL Lokale Adresse dieses Systems
b BROADCAST Route zur Broadcast-Adresse eines lokal angeschlossenen IPv4-Netzes

Die Default-Route (default) steht für 0.0.0.0/0 – sie greift für alle Pakete ohne spezifischere Route und verweist auf das Gateway im lokalen Netzwerk.

Eine Cloning-Route kann als Ausgangspunkt für weitere, spezifischere Routing-Einträge dienen. Wird eine solche Route benutzt und benötigt der Kernel für ein bestimmtes Ziel einen eigenen Eintrag, kann er diesen aus der vorhandenen Route erzeugen. Die ursprüngliche Route trägt das Flag C, die daraus erzeugte Route das Flag c.

Eine Host-Route ist dagegen lediglich eine Route zu einem einzelnen Zielhost und wird mit H gekennzeichnet. H bedeutet daher nicht automatisch, dass die Route durch Cloning entstanden ist; eine durch Cloning erzeugte Host-Route kann jedoch sowohl c als auch H tragen.

Das Flag b kennzeichnet eine Route zur Broadcast-Adresse eines lokal angeschlossenen IPv4-Netzes. Ein Paket an diese Adresse richtet sich an alle IPv4-Hosts im betreffenden Netzwerksegment. Bei 192.168.1.0/24 ist 192.168.1.255 die zugehörige Broadcast-Adresse. Einen netzübergreifenden „globalen Broadcast“ gibt es nicht; Router leiten solche Broadcasts nicht einfach in andere Netze weiter.

Statische Routen

Statische Routen definieren explizite Pfade für bestimmte Netzwerke. Sie werden in /etc/hostname.if-Dateien eingetragen.

Eine Default-Route über die Interface-Konfiguration einrichten:

# /etc/hostname.vio0
inet 192.168.1.10/24
!route add default 192.168.1.1

Eine Zeile mit ! führt während der Verarbeitung der hostname.if-Datei einen Shell-Befehl aus. Dabei ist die Direktive nicht auf Netzwerkbefehle beschränkt; grundsätzlich kann jeder in dieser Systemumgebung verfügbare Befehl ausgeführt werden. OpenBSD ersetzt außerdem $if innerhalb des Befehls durch den Namen des gerade konfigurierten Interfaces. Für die Netzwerkkonfiguration lässt sich damit beispielsweise route aufrufen, wie hier zum Einrichten der Default-Route.

Zusätzliche Netzwerk-Routen für Multi-Subnet-Umgebungen setzen:

# /etc/hostname.vio0
inet 192.168.1.10/24
!route add default 192.168.1.1
!route add 10.0.0.0/8 192.168.1.254
!route add 172.16.0.0/12 192.168.1.254

Die Route zu 10.0.0.0/8 verwendet das Gateway 192.168.1.254. Für Ziele, zu denen keine spezifischere Route vorhanden ist, verwendet das System die Default-Route.

Eine Host-spezifische Route für eine einzelne IP einrichten:

!route add -host 203.0.113.1 192.168.1.253

-host kennzeichnet 203.0.113.1 als einzelnes Ziel und richtet eine Host-Route über 192.168.1.253 ein.

ℹ️ Info
Beim Routing wird zunächst die Route mit dem längsten passenden Präfix bevorzugt. Eine /32-Route zu einem einzelnen Host ist damit spezifischer als eine passende /24-Netzwerkroute und diese wiederum spezifischer als eine Default-Route (/0).
Existieren mehrere Routen mit demselben passenden Präfix, reicht die Präfixlänge allein für die Auswahl nicht aus. Dann werden weitere Eigenschaften der Routen berücksichtigt, beispielsweise ihre Routing-Priorität.

Default-Route via /etc/mygate

Mit /etc/mygate lassen sich beim Systemstart Default-Gateways für IPv4 und IPv6 konfigurieren. netstart(8) verarbeitet die Datei, nachdem die dafür benötigten Interfaces konfiguriert wurden, und richtet die entsprechenden Default-Routen ein.

Der einfachste Fall – IPv4-Gateway:

# /etc/mygate
192.168.1.1

IPv4 und IPv6 lassen sich kombinieren, pro Zeile ein Gateway:

# /etc/mygate
192.168.1.1
2001:db8:1::1

Pro Adressfamilie wird nur die erste Adresse verwendet. Weitere IPv4- oder IPv6-Einträge werden stillschweigend ignoriert.

Auch /etc/mygate kann Zeilen mit !command enthalten. netstart(8) führt den angegebenen Shell-Befehl bei der Verarbeitung der Datei aus:

# /etc/mygate
192.168.1.1
!doas rcctl start unbound

In diesem Beispiel wird der lokale DNS-Resolver unbound mit rcctl gestartet.

⚠️ Wichtig
Bei inet autoconf ignoriert netstart IPv4-Einträge in /etc/mygate; bei inet6 autoconf gilt das entsprechend für IPv6.

Routing zwischen Interfaces aktivieren

OpenBSD blockiert standardmäßig Paket-Weiterleitung zwischen den Interfaces. Für Router-Funktionalität muss das IP-Forwarding aktiviert werden.

Den aktuellen Status prüfen:

$ sysctl net.inet.ip.forwarding
net.inet.ip.forwarding=0
$ sysctl net.inet6.ip6.forwarding
net.inet6.ip6.forwarding=0

Temporär für das laufende System einschalten:

$ doas sysctl net.inet.ip.forwarding=1
$ doas sysctl net.inet6.ip6.forwarding=1

Dauerhaft via /etc/sysctl.conf:

net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1

IPv4- und IPv6-Forwarding werden separat aktiviert. Änderungen in /etc/sysctl.conf gelten nach dem nächsten Boot. Sollen sie sofort wirksam werden, müssen die entsprechenden Werte zusätzlich mit sysctl gesetzt werden.

Policy Routing mit rdomains

Routing-Domains (rdomains) trennen Routing-Tabellen voneinander. Interfaces und Prozesse können einer bestimmten rdomain zugeordnet werden.
Ein möglicher Einsatz ist Multi-WAN mit zwei getrennten Uplinks.

Basis-Konfiguration in den Interface-Dateien:

# /etc/hostname.vio0
inet 192.168.1.10/24
rdomain 0
!route -T 0 add default 192.168.1.1

# /etc/hostname.vio1
inet 10.0.0.10/24
rdomain 1
!route -T 1 add default 10.0.0.1

rdomain ordnet das Interface einer Routing-Domain zu. Die Zahl hinter route -T gibt die Routing-Tabelle an, in die der Befehl die Route einträgt. Damit gehört vio0 zu rdomain 0 und erhält seine Default-Route in Routing-Tabelle 0. vio1 gehört zu rdomain 1; seine Default-Route wird in Routing-Tabelle 1 eingetragen.

Einen Prozess explizit in einer Domain starten:

$ doas route -T 1 exec /usr/sbin/unbound

route -T 1 exec startet unbound in Routing-Domain 1. Auch seine Kindprozesse bleiben dieser Routing-Domain zugeordnet. Für Ziele ohne spezifischere Route verwendet der Prozess damit die Default-Route über 10.0.0.1.

Die aktiven Routing-Tabellen anzeigen:

$ route -T 0 -n show -inet
Routing tables

Internet:
Destination        Gateway            Flags   Refs      Use   Mtu  Prio Iface
default            192.168.1.1        UGS        5      133     -     8 vio0
127/8              127.0.0.1          UGRS       0        0 32768     8 lo0
192.168.1/24       192.168.1.65       UCn        2      897     -     4 vio0
192.168.1.1        cc:32:e5:e9:1b:0b  UHLch      1      699     -     3 vio0
[...]

-inet beschränkt die Ausgabe auf IPv4.

Routing-Tabelle und Routing-Domain sind nicht dasselbe. Eine Routing-Tabelle enthält die Routen für die Weiterleitung von Paketen. Eine Routing-Domain trennt Netzwerkbereiche voneinander; Interfaces verschiedener rdomains können nicht direkt miteinander kommunizieren und dürfen sogar dieselben IP-Adressen verwenden. Beim Anlegen einer rdomain erzeugt OpenBSD gleichzeitig eine Routing-Tabelle mit derselben Nummer.

Dynamisches Routing mit ospfd

Routing-Protokolle

Statische Routen funktionieren für kleine Netzwerke. Größere Infrastrukturen benötigen dynamisches Routing – Routen werden automatisch basierend auf Netzwerk-Topologie verteilt.

OpenBSD bringt separate Daemons für jedes Routing-Protokoll mit – alle im Basissystem enthalten, kein pkg_add nötig:

  • ospfd(8) — Open Shortest Path First, für interne Netzwerke (IGP)
  • bgpd(8) — Border Gateway Protocol, für Internet-Routing (EGP)
  • ripd(8) — Routing Information Protocol, älteres IGP
  • eigrpd(8) — Enhanced Interior Gateway Routing Protocol

OSPF-Konfiguration

OSPF verteilt Routen innerhalb einer Organisation. Alle Router im gleichen OSPF-Bereich (Area) tauschen Routing-Informationen aus.

OSPF-Grundkonfiguration in /etc/ospfd.conf:

router-id 10.0.0.1

area 0.0.0.0 {
	interface vio0
	interface vio1
}

Die Parameter definieren:

  • router-id — Eindeutige Router-Identifikation, typischerweise eine Interface-IP
  • area 0.0.0.0 — OSPF-Backbone-Area, in einfachen Single-Area-Setups die übliche Wahl
  • interface — Interfaces auf denen OSPF aktiv ist

Daemon aktivieren und starten:

doas rcctl enable ospfd
doas rcctl start ospfd

Nachbarn prüfen:

doas ospfctl show neighbor
ID              Pri State        DeadTime Address         Iface     Uptime
10.0.0.2        1   FULL/DR      00:00:39 10.0.0.2        vio1      00:01:00
10.0.0.2        1   FULL/DR      00:00:39 192.168.1.65    vio0      00:01:00

Die Spalten zeigen:

  • ID — Router-ID des Nachbarn
  • Pri — OSPF-Priorität des Nachbarn bei der Wahl von Designated Router und Backup Designated Router
  • State — Zustand der Nachbarschaft; FULL bedeutet, dass die Link-State-Datenbanken vollständig synchronisiert sind, /DR kennzeichnet den Nachbarn als Designated Router
  • DeadTime — verbleibende Zeit, bis der Nachbar ohne weitere Hello-Pakete als ausgefallen gilt
  • Address — IP-Adresse des Nachbarn auf diesem Netz
  • Iface — lokales Interface, über das der Nachbar erreicht wird
  • Uptime — Dauer der bestehenden Nachbarschaft

Die OSPF Routing Information Base (RIB) zeigt die vom OSPF-Prozess bekannten Routen:

doas ospfctl show rib
Destination          Nexthop           Path Type    Type      Cost    Uptime
10.0.0.0/24          10.0.0.1        C Intra-Area   Network   10      00:02:13
192.168.1.0/24       192.168.1.62    C Intra-Area   Network   10      00:02:13

Dabei bezeichnet Destination das Zielpräfix und Nexthop den nächsten Router beziehungsweise die lokale Adresse zum Ziel. Unter Path Type kennzeichnet C einen direkt verbundenen Pfad; Intra-Area bedeutet, dass der Pfad innerhalb derselben OSPF-Area liegt. Type beschreibt hier mit Network ein Netzwerkziel, Cost die für die Pfadauswahl verwendeten kumulierten OSPF-Kosten und Uptime die Zeit, seit der Eintrag in diesem Zustand besteht.

OSPF aktualisiert die Kernel-Routing-Tabelle automatisch wenn sich die Topologie ändert – fällt ein Router aus, konvergiert das Netzwerk ohne manuellen Eingriff.

Interface-Kosten anpassen:

router-id 10.0.0.1

area 0.0.0.0 {
	interface vio0 {
		metric 100
	}
	interface vio1 {
		metric 10
	}
}

Niedrigere Kosten bevorzugen bestimmte Routen. OSPF berechnet kürzeste Pfade basierend auf kumulativen Interface-Kosten.

BGP-Konfiguration

ℹ️ Info
BGP-Konfiguration unter OpenBSD mit bgpd(8) ist im Buch „Souveräne Systeme" ausführlich behandelt. BGP-Fehlkonfiguration kann Internet-weite Routing-Probleme verursachen und setzt Kenntnisse in AS-Design und Routing-Policy voraus.

Für kleinere und mittlere Netze genügt OSPF häufig vollständig. BGP ist dagegen für das Routing zwischen autonomen Systemen vorgesehen.

Routing-Metriken und Gewichtung

Mehrere Routen zum gleichen Ziel können unterschiedliche Routing-Prioritäten besitzen. Niedrigere Prioritätswerte werden gegenüber höheren bevorzugt. Diese Kernel-Routing-Priorität ist von den Metriken und Auswahlverfahren dynamischer Routing-Protokolle zu unterscheiden.

Route mit Priorität hinzufügen:

doas route add -priority 10 10.0.0.0/8 192.168.1.254
doas route add -priority 20 10.0.0.0/8 192.168.1.253

Die Route mit Priorität 10 wird gegenüber der Route mit Priorität 20 bevorzugt. Unterschiedliche Prioritäten allein implementieren jedoch keine Prüfung der Gateway-Erreichbarkeit und damit kein automatisches Gateway-Failover. Für mehrere gleichwertige Pfade besitzt OpenBSD zusätzlich Multipath-Routing.

Dynamische Routing-Protokolle besitzen eigene Auswahlkriterien. Bei OSPF beeinflusst metric die OSPF-Pfadberechnung; die Priorität, mit der OSPF-Routen in die Kernel-Tabelle eingetragen werden, wird separat über fib-priority gesteuert. Auch BGP-Attribute wie AS-Path und Local Preference gehören zur BGP-Pfadauswahl und sind nicht mit der Kernel-Routing-Priorität gleichzusetzen.

DHCP-Server mit dhcpd

Grundlegende DHCP-Konfiguration

Der DHCP-Server dhcpd verteilt IP-Adressen automatisch an Clients. Die Konfiguration erfolgt über /etc/dhcpd.conf.

Grundkonfiguration für einzelnes Subnetz:

# /etc/dhcpd.conf
subnet 192.168.1.0 netmask 255.255.255.0 {
	range 192.168.1.100 192.168.1.200;
	option routers 192.168.1.1;
	option domain-name-servers 192.168.1.1;
	option domain-name "internal.local";
	default-lease-time 3600;
	max-lease-time 7200;
}

Die Parameter definieren:

  • subnet - Netzwerk-Bereich des DHCP-Servers
  • range - IP-Adress-Pool für dynamische Zuweisung
  • option routers - Default-Gateway für Clients
  • option domain-name-servers - DNS-Server-Liste
  • option domain-name - Domainname, den der DHCP-Server an Clients übermittelt
  • default-lease-time - Standard-Lease-Dauer in Sekunden
  • max-lease-time - Maximale Lease-Verlängerung

DHCP-Server starten:

doas rcctl enable dhcpd
doas rcctl set dhcpd flags vio0
doas rcctl start dhcpd

vio0 beschränkt dhcpd auf dieses Interface; DHCP-Anfragen auf anderen Interfaces werden von dieser Instanz nicht beantwortet.

⚠️ Wichtig

Zwei unabhängig konfigurierte DHCP-Server im selben Netzwerk-Segment konkurrieren um Lease-Vergaben – bestehende Clients können fehlerhafte oder widersprüchliche Konfigurationen erhalten. Vor dem Testen prüfen, ob im Zielnetz bereits ein DHCP-Server aktiv ist.

Für redundante Konfigurationen unterstützt dhcpd(8) die Synchronisation von Lease-Vergaben zwischen mehreren Instanzen. Mit -Y legt eine Instanz ein Ziel fest, an das sie Synchronisationsmeldungen sendet; mit -y bestimmt sie die Adresse oder das Interface, auf dem sie solche Meldungen empfängt. Ein gemeinsamer Schlüssel in /var/db/dhcpd.key kann die Synchronisationsmeldungen authentifizieren. Die Daemons gleichen ihren Lease-Zustand miteinander ab; sie greifen dabei nicht auf eine gemeinsame Lease-Datei zu. Details beschreibt der Abschnitt SYNCHRONISATION der Manpage.

Statische IP-Zuweisungen

Server und Netzwerk-Hardware benötigen häufig feste IP-Adressen. DHCP kann MAC-Adressen spezifische IPs zuweisen. Eine host-Deklaration muss dabei nicht innerhalb eines subnet-Blocks stehen; die folgende Form gruppiert sie lediglich übersichtlich mit der zugehörigen Netzkonfiguration:

subnet 10.0.0.0 netmask 255.255.255.0 {
	range 10.0.0.100 10.0.0.110;
	option routers 10.0.0.1;
	option domain-name-servers 10.0.0.1;
	default-lease-time 600;
	max-lease-time 1200;

	host client1 {
		hardware ethernet 52:54:00:12:34:06;
		fixed-address 10.0.0.50;
	}
}

Im gezeigten Beispiel wird der Client über seine Ethernet-Adresse zugeordnet. Nach einer Config-Änderung muss dhcpd neu gestartet werden (doas rcctl restart dhcpd) – ein automatisches Neuladen wie bei anderen Daemons per SIGHUP gibt es laut dhcpd(8) nicht.

Client-seitig bestätigt dhcpleasectl, dass die feste Adresse ankommt. Die Option -l vio1 beschränkt die Ausgabe auf die Lease des Interfaces vio1:

doas dhcpleasectl -l vio1
vio1 [Bound]
	inet 10.0.0.50 netmask 255.255.255.0
	default gateway 10.0.0.1
	nameservers 10.0.0.1
	lease 10 minutes
	dhcp server 10.0.0.2

[Bound] bezeichnet eine erfolgreich gebundene DHCP-Lease. Die folgenden Zeilen nennen Adresse und Netzmaske, Default-Gateway, DNS-Server, Lease-Dauer und den DHCP-Server, der die Adresse vergeben hat.

ℹ️ Info
Feste Zuweisungen über fixed-address erscheinen nicht als dynamisch vergebene Leases in /var/db/dhcpd.leases. Die Lease-Datei verwaltet die dynamischen Vergaben aus den konfigurierten Pools.

MAC-Adresse eines Geräts ermitteln:

$ arp -a
Host                                 Ethernet Address    Netif Expire    Flags
printer.example.com                  52:54:00:12:34:05    vio0 19m58s

Die Option -a zeigt alle Einträge des ARP-Caches. Die Spalten enthalten Hostname beziehungsweise IP-Adresse, Ethernet-Adresse, zugehöriges Interface, verbleibende Gültigkeitsdauer und gegebenenfalls Flags. Bei Geräten ohne aktuellen ARP-Eintrag muss die MAC-Adresse direkt am Gerät oder in dessen Konfiguration ermittelt werden.

Mehrere Subnetze

Ein shared-network beschreibt mehrere IP-Subnetze, die dasselbe physische Netzwerksegment teilen. Der DHCP-Server behandelt die dynamischen Adressbereiche der enthaltenen Subnetze als gemeinsamen Pool.

Alias-Adresse für das zweite Subnetz auf dem Server-Interface anlegen:

doas ifconfig vio1 alias 10.0.1.1/24

alias fügt dem Interface vio1 die zusätzliche IPv4-Adresse 10.0.1.1/24 hinzu, ohne eine bereits vorhandene Adresse zu ersetzen.

Konfiguration mit zwei Subnetzen im gemeinsamen Pool:

# /etc/dhcpd.conf
shared-network vio1-testnetz {
	subnet 10.0.0.0 netmask 255.255.255.0 {
		range 10.0.0.100 10.0.0.101;
		option routers 10.0.0.1;
		option domain-name-servers 10.0.0.1;
		default-lease-time 600;
		max-lease-time 1200;
	}
	subnet 10.0.1.0 netmask 255.255.255.0 {
		range 10.0.1.100 10.0.1.101;
		option routers 10.0.1.1;
		option domain-name-servers 10.0.1.1;
		default-lease-time 600;
		max-lease-time 1200;
	}
}

Innerhalb eines shared-network-Blocks bilden alle enthaltenen Subnetze einen gemeinsamen Adress-Pool. Welchem Subnetz ein Client zugeteilt wird, hängt nicht von der Reihenfolge im Pool ab – bei einem Test mit drei Clients auf demselben Segment ergab sich folgende Verteilung:

doas cat /var/db/dhcpd.leases
lease 10.0.0.100 {
	client-hostname "hans";
}
lease 10.0.1.100 {
	client-hostname "wilhelm";
}
lease 10.0.1.101 {
	client-hostname "xaver";
}

client-hostname enthält den vom jeweiligen Client übermittelten Hostnamen.

Ein Client landet nicht zwangsläufig im ersten Subnetz des Pools, selbst wenn dort noch Adressen frei wären. Da die Subnetze laut Konfiguration dasselbe physische Netz bilden, kann der Server anhand des Client-Standorts nicht zwischen ihnen unterscheiden.

ℹ️ Info
Alternativ lassen sich Subnetze auch über getrennte physische Interfaces bedienen, ohne shared-network – jedes Interface bekommt dann sein eigenes, unabhängiges subnet, ohne gemeinsamen Pool. Diese Variante ist sinnvoll, wenn die Netze bereits physisch getrennt sind.

DHCP-Relay für entfernte Subnetze

DHCP-Clients beginnen ihre Anfrage per Broadcast im lokalen Netzwerksegment; Router leiten diese Broadcasts nicht in andere Subnetze weiter. Ein DHCP-Relay-Agent lauscht im entfernten Subnetz und leitet die Anfragen gezielt an einen DHCP-Server in einem anderen Netz weiter.

Relay auf dem Router-System aktivieren, der zwischen dem Client-Segment und dem DHCP-Server-Segment sitzt:

doas rcctl enable dhcrelay
doas rcctl set dhcrelay flags "-i vio2 10.0.0.2"
doas rcctl start dhcrelay

Die Parameter definieren:

  • -i vio2 - Interface, auf dem der Relay-Agent DHCP-Broadcasts entgegennimmt
  • 10.0.0.2 - Ziel-IP des DHCP-Servers, an den Anfragen weitergeleitet werden

Anfragen aus dem vio2-Segment werden an 10.0.0.2 weitergeleitet. Der DHCP-Server antwortet über denselben Relay-Agent zurück zum Client.

⚠️ Wichtig

Der DHCP-Server benötigt eine subnet-Deklaration für das Netz des relayten Clients. Soll der Client dort dynamisch eine Adresse erhalten, bekommt dieses entfernte Netz einen eigenen passenden Pool:

subnet 172.16.2.0 netmask 255.255.255.0 {
	range 172.16.2.100 172.16.2.105;
	option routers 172.16.2.1;
	option domain-name-servers 172.16.2.1;
	default-lease-time 600;
	max-lease-time 1200;
}

Das entfernte Netz gehört nicht zusammen mit dem Servernetz in einen shared-network-Block, wenn beide physisch getrennt sind. Der Relay-Agent trägt seine Adresse im giaddr-Feld der DHCP-Anfrage ein; dadurch kann dhcpd die Anfrage dem entfernten Subnetz zuordnen.

Funktionsnachweis auf Paket-Ebene: tcpdump auf dem DHCP-Server-Interface zeigt die relayte Anfrage. -n verhindert die Namensauflösung, -i vio1 wählt das Interface und der Filter port 67 or port 68 lässt Pakete passieren, bei denen Quell- oder Zielport dem DHCP-Server-Port 67 oder dem DHCP-Client-Port 68 entspricht:

doas tcpdump -n -i vio1 port 67 or port 68
172.16.2.1.67 > 10.0.0.2.67: (request) hops:1 xid:0xd76edd47 G:172.16.2.1 ether 52:54:00:cc:cd:01 [|bootp]

In der Ausgabe sendet 172.16.2.1.67 von UDP-Port 67 an den DHCP-Server 10.0.0.2.67. (request) kennzeichnet die BOOTP/DHCP-Anfrage, hops:1 den bereits durchlaufenen Relay-Hop und xid die Transaktions-ID, über die Anfrage und Antwort zusammengehören. G:172.16.2.1 ist die vom Relay eingetragene Gateway-/giaddr-Adresse; ether nennt die Hardware-Adresse des Clients. [|bootp] zeigt an, dass die erfassten Daten für eine vollständige BOOTP-Dekodierung an dieser Stelle nicht ausreichen.

Der Client im entfernten Segment erhält eine Adresse aus dem Pool seines eigenen Subnetzes. Im Test mit Relay-Adresse 172.16.2.1 erhielt der Client 172.16.2.100/24 mit Gateway 172.16.2.1; die Gateway-Adresse war anschließend per ARP und ICMP erreichbar.

DHCP-Client mit dhcpleased

Auf der Client-Seite übernimmt dhcpleased(8) die IP-Konfiguration per DHCP. Ein separates dhclient-Kommando existiert unter OpenBSD nicht mehr.

Daemon aktivieren:

doas rcctl enable dhcpleased
doas rcctl start dhcpleased

Das Interface muss dhcpleased explizit über hostname.if mitgeteilt werden:

# /etc/hostname.vio1
dhcp

Nach dem Anlegen prüft OpenBSD die Datei-Rechte und korrigiert sie bei Bedarf automatisch:

doas sh /etc/netstart vio1
WARNING: /etc/hostname.vio1 is insecure, fixing permissions.
⚠️ Wichtig

Ein reines ifconfig vio1 down / up liest /etc/hostname.vio1 nicht neu ein. Änderungen an der Datei werden erst über netstart oder einen Reboot wirksam.

War das Interface zuvor statisch konfiguriert, entfernt der Wechsel zu dhcp die alte Adresse nicht automatisch – beide inet-Zeilen bleiben parallel bestehen. Für einen sauberen Wechsel die alte Adresse manuell entfernen (ifconfig vio1 delete <alte-IP>).

Lease-Status direkt beim Client abfragen. Wie oben beschränkt -l vio1 die Ausgabe auf dieses Interface:

doas dhcpleasectl -l vio1
vio1 [Bound]
	inet 10.0.0.100 netmask 255.255.255.0
	default gateway 10.0.0.1
	nameservers 10.0.0.1
	lease 10 minutes
	dhcp server 10.0.0.2

[Bound] bezeichnet den Zustand einer erfolgreich gebundenen DHCP-Lease. Die folgenden Zeilen zeigen zugewiesene Adresse und Netzmaske, Gateway, DNS-Server, verbleibende Lease-Zeit und den vergebenden DHCP-Server.

Lease-Verwaltung

Aktive DHCP-Leases werden in /var/db/dhcpd.leases gespeichert:

doas cat /var/db/dhcpd.leases
lease 10.0.0.100 {
	starts 1 2026/07/27 15:14:17 UTC;
	ends 1 2026/07/27 15:24:17 UTC;
	hardware ethernet 52:54:00:12:34:06;
	uid 01:52:54:00:12:34:06;
	client-hostname "client";
}

lease 10.0.0.100 bezeichnet die vergebene IP-Adresse. starts und ends geben Beginn und Ende der Lease in UTC an; die führende Zahl bezeichnet den Wochentag. hardware ethernet enthält die MAC-Adresse des Clients. uid speichert dessen DHCP-Client-Identifier; im gezeigten Fall besteht er aus dem Hardwaretyp 01 für Ethernet und der anschließenden MAC-Adresse. client-hostname enthält den vom Client übermittelten Hostnamen.

Bei Änderungen des Lease-Zustands können für dieselbe Adresse mehrere Einträge in der Datei erscheinen; die Datei sollte deshalb nicht als unveränderliches Verlaufsprotokoll interpretiert werden.

DHCP-Clients erneuern ihre Leases selbstständig vor Ablauf. Das Löschen der serverseitigen Lease-Datei erzwingt bei bereits gebundenen Clients weder eine sofortige Neuverhandlung noch eine neue IP-Adresse. Im Test behielt ein Client nach dem Leeren der Server-Lease-Datenbank 172.16.2.100 und erhielt dieselbe Adresse beim regulären Renewal erneut bestätigt.

IPv6-Konfiguration

IPv6-Adressen und Autoconfiguration

IPv6 nutzt 128-Bit-Adressen statt IPv4s 32 Bit. Die Adress-Struktur unterstützt hierarchisches Routing und Autoconfiguration.

Basis-IPv6-Autoconfiguration in /etc/hostname.if:

# /etc/hostname.vio2
inet6 autoconf
⚠️ Wichtig
Eine alleinstehende inet6-Zeile konfiguriert weder eine IPv6-Adresse noch SLAAC. hostname.if(5) erlaubt die Adressfamilie auch ohne Adresse; für automatische IPv6-Konfiguration muss jedoch ausdrücklich inet6 autoconf verwendet werden, für eine feste Adresse inet6 <adresse> <prefixlen>.

Mit inet6 autoconf wird das Interface für IPv6-Autoconfiguration markiert (AUTOCONF6). slaacd(8) überwacht entsprechend markierte Interfaces und verarbeitet dort Router Advertisements; daraus können zusätzlich Global-Unicast-Adressen und weitere IPv6-Konfigurationsinformationen entstehen. slaacd ist dabei vom IPv4-DHCP-Daemon dhcpleased getrennt.

Aktuelle IPv6-Adressen anzeigen. Das Argument inet6 beschränkt die Ausgabe auf die IPv6-Konfiguration des Interfaces:

$ ifconfig vio2 inet6
vio2: flags=2a48843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,AUTOCONF6TEMP,AUTOCONF6,AUTOCONF4,LRO> mtu 1500
	lladdr 52:54:00:cc:cd:01
	inet6 fe80::5054:ff:fecc:cd01%vio2 prefixlen 64 scopeid 0x3
	inet6 2001:db8:2:0:47f0:731b:6691:7f2e prefixlen 64 autoconf pltime 2651 vltime 5351
	inet6 2001:db8:2:0:f185:af7d:7924:cdac prefixlen 64 autoconf temporary pltime 2651 vltime 5351

Die Zahl vor der spitzen Klammer bei flags= ist die numerische Bitmaske der anschließend symbolisch aufgeführten Interface-Flags. Die erste Zeile zeigt dieselben allgemeinen Interface-Eigenschaften wie die spätere vollständige ifconfig-Ausgabe im Abschnitt „Interface-Status prüfen“. AUTOCONF6 kennzeichnet aktivierte automatische IPv6-Konfiguration, AUTOCONF6TEMP die Erzeugung temporärer IPv6-Adressen. mtu ist die Maximum Transmission Unit, lladdr die Link-Layer- beziehungsweise MAC-Adresse.

Die fe80-Adresse ist Link-Local und nur im lokalen Segment gültig; %vio2 ordnet die Link-Local-Adresse dem Interface zu. prefixlen 64 bezeichnet die Präfixlänge, scopeid 0x3 den numerischen Scope-Identifier des Interfaces. Die beiden 2001:db8-Adressen sind Global Unicast – eine persistente automatisch erzeugte Adresse und eine temporäre Privacy-Adresse (siehe Abschnitt IPv6 Privacy Extensions). autoconf kennzeichnet automatisch konfigurierte Adressen, temporary eine temporäre Privacy-Adresse. pltime und vltime geben Preferred- und Valid-Lifetime in Sekunden an.

Der Ablauf lässt sich per tcpdump nachvollziehen – Router Solicitation, Router Advertisement, und Duplicate Address Detection für die neu generierte Adresse:

$ doas tcpdump -n -i vio2 icmp6
fe80::5054:ff:fecc:cd01 > ff02::2: icmp6: router solicitation
fe80::5054:ff:fecc:cd08 > fe80::5054:ff:fecc:cd01: icmp6: router advertisement
:: > ff02::1:ff6a:49e9: icmp6: neighbor sol: who has 2001:db8:2:0:357a:c3ef:b66a:49e9
fe80::5054:ff:fecc:cd01 > ff02::2: icmp6: neighbor adv: tgt is 2001:db8:2:0:357a:c3ef:b66a:49e9

ff02::2 ist die Link-Local-Multicast-Adresse für alle Router. Die Adresse ff02::1:ff6a:49e9 ist die zur geprüften IPv6-Adresse gehörende Solicited-Node-Multicast-Adresse. Die Neighbor-Solicitation-Nachricht (neighbor sol) mit Quelladresse :: fragt mit who has nach der neuen Adresse und prüft vor ihrer Vergabe, ob sie bereits im Segment verwendet wird (Duplicate Address Detection). neighbor adv bezeichnet ein Neighbor Advertisement; tgt is nennt dessen Zieladresse. Erst nach erfolgreicher Prüfung ist die neue Adresse aktiv nutzbar.

Statische IPv6-Konfiguration

Server benötigen feste IPv6-Adressen. Manuelle Konfiguration ähnelt IPv4:

# /etc/hostname.vio2
inet6 2001:db8:2::10/64

CIDR-Notation funktioniert identisch zu IPv4. Für SLAAC werden /64-Präfixe verwendet.

Mehrere IPv6-Adressen über das alias-Keyword:

# /etc/hostname.vio2
inet6 autoconf
inet6 alias 2001:db8:2::30/64

Das alias-Keyword fügt eine zusätzliche, feste Adresse zur automatisch generierten hinzu – nützlich, wenn ein Interface sowohl per SLAAC konfiguriert werden als auch eine zusätzliche, stabile Adresse tragen soll. IPv6 erlaubt mehrere Adressen pro Interface.

Änderung nach dem Bearbeiten der Datei einlesen:

doas sh /etc/netstart vio2

Ergebnis prüfen:

$ ifconfig vio2 inet6
vio2: flags=2248843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,AUTOCONF6TEMP,AUTOCONF6,LRO> mtu 1500
	lladdr 52:54:00:cc:cd:08
	inet6 fe80::5054:ff:fecc:cd08%vio2 prefixlen 64 scopeid 0x3
	inet6 2001:db8:2::30 prefixlen 64

Adressen ohne Zeitangaben (pltime/vltime) wie hier sind statisch gesetzt – im Gegensatz zu den befristeten autoconf-Adressen aus dem vorigen Abschnitt laufen sie nicht automatisch ab.

IPv6 Privacy Extensions

OpenBSD verwendet für persistente SLAAC-Adressen standardmäßig Semantically Opaque Interface Identifiers (SOII) nach RFC 7217. Die Interface-ID wird damit nicht einfach aus der MAC-Adresse abgeleitet.

Temporäre, zufällige Adressen (RFC 8981) sind unter OpenBSD standardmäßig aktiviert, sobald autoconf gesetzt ist – ein separates Einschalten ist nicht nötig. Sobald über SLAAC ein geeignetes Präfix verfügbar ist, können neben der persistenten automatisch konfigurierten Adresse auch temporäre Adressen entstehen:

In der oben gezeigten Ausgabe von ifconfig vio2 inet6 stehen dafür zwei automatisch konfigurierte Global-Unicast-Adressen nebeneinander: Die erste trägt autoconf, die zweite zusätzlich temporary. Das bereits dort sichtbare AUTOCONF6TEMP-Flag zeigt an, dass zusätzlich zur persistenten autoconf-Adresse temporäre Adressen erzeugt werden – erkennbar am Suffix temporary in der Adresszeile. Ihre Gültigkeit und Erneuerung wird über die angezeigten Preferred- und Valid-Lifetimes gesteuert.

ℹ️ Info
In älteren OpenBSD-Anleitungen findet sich dafür das Schlüsselwort autoconfprivacy; in den hier behandelten Versionen heißt die Option temporary beziehungsweise -temporary.

Privacy Extensions deaktivieren (für Server, die keine temporären Adressen verwenden sollen):

# /etc/hostname.vio2
inet6 autoconf -temporary

Damit wird die Erzeugung neuer temporärer SLAAC-Adressen deaktiviert. Bereits konfigurierte temporäre Adressen werden dabei nicht sofort entfernt, sondern laufen laut Manpage regulär aus. Die persistente automatisch konfigurierte Adresse bleibt erhalten.

Router Advertisement Daemon

IPv6-Router senden RA-Messages für Autoconfiguration. OpenBSD kann als IPv6-Router fungieren via rad (Router Advertisement Daemon).

Basis-RA-Konfiguration in /etc/rad.conf:

interface vio2 {
	prefix 2001:db8:2::/64
}

Die Parameter definieren:

  • interface - Ausgehendes Interface für RA-Messages
  • prefix - IPv6-Präfix für Clients

Ein dns-Block ist optional und wird nur benötigt, wenn IPv6-DNS-Server per RA verteilt werden sollen.

Router Advertisement Daemon starten:

doas rcctl enable rad
doas rcctl start rad

Konfiguration bei laufendem Daemon neu einlesen, ohne ihn neu zu starten:

doas ractl reload

Clients im Netzwerk konfigurieren sich automatisch mit Adressen aus dem definierten Präfix – sichtbar über slaacd auf Client-Seite, siehe voriger Abschnitt.

Netzwerk-Troubleshooting

Connectivity Tests

Basis-Erreichbarkeit prüfen mit ping:

$ ping -c 4 192.168.1.1
PING 192.168.1.1 (192.168.1.1): 56 data bytes
64 bytes from 192.168.1.1: icmp_seq=0 ttl=64 time=1.108 ms
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.048 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=1.857 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=64 time=1.384 ms
--- 192.168.1.1 ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 1.048/1.349/1.857/0.319 ms

Der Parameter -c 4 begrenzt den Test auf vier Echo Requests. Die Kopfzeile nennt das Ziel und mit 56 data bytes die Nutzdatenmenge jedes ICMP-Echo-Requests. Jede Antwortzeile zeigt mit 64 bytes die Größe der empfangenen ICMP-Antwort einschließlich ICMP-Header, mit icmp_seq die laufende Nummer, mit ttl den empfangenen Time-To-Live-Wert und mit time die gemessene Round-Trip-Zeit. Die TTL wird von jedem Router auf dem Weg reduziert; ein Übergang von einem Router zum nächsten wird als Hop bezeichnet. Erreicht die TTL den Wert 0, wird das Paket verworfen.

Die Statistik am Ende zählt gesendete und empfangene Pakete und berechnet daraus den Paketverlust. min/avg/max/std-dev fasst die kleinste, mittlere und größte Round-Trip-Zeit sowie deren Standardabweichung zusammen. ping beendet sich mit Exit-Status 0, sobald mindestens eine Antwort eingegangen ist; bei einem vollständigen Timeout ist der Status größer 0. Dadurch kann ein Skript unterscheiden, ob das Ziel während des Tests erreichbar war.

IPv6-Erreichbarkeit:

$ ping6 -c 4 2001:db8:2::30

ping6 verwendet denselben -c-Parameter und dieselbe grundlegende Ausgabelogik für IPv6-Adressen; statt ICMP für IPv4 wird ICMPv6 verwendet.

Traceroute

Netzwerk-Pfade verfolgen mit traceroute:

$ traceroute 192.168.1.1
traceroute to 192.168.1.1 (192.168.1.1), 64 hops max, 40 byte packets
 1  kurt (192.168.1.1)  1.616 ms  1.602 ms  2.454 ms

Die Kopfzeile nennt Ziel, maximale Zahl von 64 Hops und die Größe der verwendeten Probe-Pakete von 40 Byte. Jede folgende Zeile beginnt mit der Hop-Nummer und zeigt den antwortenden Router sowie die Antwortzeiten der drei standardmäßig gesendeten Probes. Jede Zeile zeigt damit einen Hop zum Ziel. Die gemessenen Antwortzeiten liefern Hinweise auf Verzögerungen entlang des Pfads; einzelne hohe Werte beweisen jedoch noch kein langsames Netzwerksegment. In einem flachen Testnetz, in dem alle Systeme an derselben Bridge hängen, ist das Ziel oft nur einen Hop entfernt – in produktiven Netzen mit mehreren Routern dazwischen erscheint entsprechend eine Zeile pro Hop. Bei mehreren möglichen Pfaden (ECMP) kann eine Hop-Zeile auch mehrere unterschiedliche Antwort-Adressen zeigen, eine pro Probe:

 3  198.51.100.1 (198.51.100.1)  29.987 ms  31.592 ms  39.918 ms
 4  198.51.100.5 (198.51.100.5)  42.059 ms  38.817 ms 198.51.100.9 (198.51.100.9)  40.03 ms
[... weitere Hops ...]

Antwortet ein Hop nicht innerhalb der Timeout-Zeit, erscheint statt Adresse und Latenz ein * * *.

ℹ️ Info
OpenBSDs traceroute(8) verwendet standardmäßig UDP-Probes. Mit -I werden stattdessen ICMP- beziehungsweise ICMPv6-Echo-Probes verwendet. -P proto setzt ein anderes IP-Protokoll anhand einer Nummer oder eines Namens aus /etc/protocols; die Manpage weist darauf hin, dass dies für die meisten Protokolle nicht zuverlässig funktioniert.

Traceroute mit ICMP-Echo statt UDP:

$ traceroute -I example.com

Ein anderes IP-Protokoll lässt sich mit -P auswählen, beispielsweise -P tcp. Das ist jedoch nicht mit einem spezialisierten TCP-SYN-Traceroute gleichzusetzen und laut Manpage für die meisten Protokolle nicht zuverlässig.

⚠️ Wichtig

Bei traceroute: sendto: Permission denied sollte auch das aktive PF-Regelwerk geprüft werden:

traceroute: sendto: Permission denied
$ doas pfctl -sr | grep -i block
block return all

pfctl -sr zeigt die geladenen Filterregeln (-s rules); grep -i block beschränkt die Ausgabe ohne Beachtung von Groß- und Kleinschreibung auf Zeilen mit block. Die Beispielausgabe block return all ist eine Regel, die passenden Verkehr blockiert und eine Rückmeldung erzeugt.

Die genaue Ursache eines lokalen Permission denied darf jedoch nicht allein aus einer vorhandenen block return-Regel abgeleitet werden. Die PF-Semantik und die Rückmeldung an lokal erzeugten Verkehr werden im Artikel Firewall und pf-Grundlagen (geplant) behandelt.

Interface-Status prüfen

Detaillierte Interface-Information:

$ ifconfig vio0
vio0: flags=2808843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,AUTOCONF4,LRO> mtu 1500
	lladdr 52:54:00:ab:cd:08
	index 1 priority 0 llprio 3
	groups: egress
	media: Ethernet autoselect
	status: active
	inet 192.168.1.62 netmask 0xffffff00 broadcast 192.168.1.255

Die erste Zeile nennt die Interface-Flags und die MTU (Maximum Transmission Unit), hier 1500 Byte. Die sichtbaren Flags bedeuten:

  • UP - Interface ist aktiviert
  • BROADCAST - Interface unterstützt IPv4-Broadcasts
  • RUNNING - Treiber beziehungsweise Interface-Ressourcen sind betriebsbereit
  • SIMPLEX - Das Interface empfängt seine eigenen gesendeten Frames nicht automatisch als Eingang zurück
  • MULTICAST - Multicast wird unterstützt
  • AUTOCONF4 - Interface bezieht seine IPv4-Adresse per DHCP (siehe Abschnitt DHCP-Client)
  • LRO - Large Receive Offload; der Treiber kann mehrere eingehende TCP-Segmente für die weitere Verarbeitung zusammenfassen

lladdr ist die Link-Layer- beziehungsweise MAC-Adresse. index ist die Kernel-Indexnummer des Interfaces, priority seine Interface-Priorität und llprio die Priorität für Link-Layer-Kommunikation. groups: egress zeigt die Interface-Gruppe, der das Interface angehört. media beschreibt das verwendete Übertragungsmedium und autoselect dessen automatische Auswahl. Die inet-Zeile enthält IPv4-Adresse, Netzmaske und Broadcast-Adresse; 0xffffff00 entspricht der Netzmaske 255.255.255.0 beziehungsweise /24.

Der status: active zeigt einen aktiven Link. status: no carrier weist darauf hin, dass kein Carrier beziehungsweise Link erkannt wird. Bei virtuellen NICs (wie vio unter QEMU/KVM) fehlt oft der sonst übliche Duplex-Zusatz hinter media: (z.B. (1000baseT full-duplex)) – das Fehlen dieser Angabe ist bei virtuellen Interfaces normal und kein Fehlerzeichen.

Paket-Statistiken

Netzwerk-Fehler identifizieren. netstat -i zeigt die Interface-Tabelle mit Adressen und Paket-Zählern:

$ netstat -i
Name    Mtu   Network     Address              Ipkts Ifail    Opkts Ofail Colls
lo0     32768 <Link>                               0     0        0     0     0
lo0     32768 127/8       localhost                0     0        0     0     0
vio0    1500  <Link>      52:54:00:ab:cd:08     2762     0     2453     0     0
vio0    1500  192.168.1/2 hans.fridolin.lan     2762     0     2453     0     0
vio2    1500  <Link>      52:54:00:cc:cd:08      228     0       47     0     0
vio2    1500  172.16.2/24 172.16.2.1             228     0       47     0     0
vio2    1500  fe80::%vio2 fe80::5054:ff:fec      228     0       47     0     0
vio2    1500  2001:db8:2: 2001:db8:2::30         228     0       47     0     0
pflog0  33136 <Link>                               0     0        0     0     0

Die Spalten Name, Mtu, Network und Address nennen Interface, MTU, zugehöriges Netzwerk beziehungsweise Adressfamilie und die jeweilige Adresse. Die Zähler bedeuten:

  • Ipkts - Empfangene Pakete
  • Ifail - Fehlgeschlagene eingehende Pakete
  • Opkts - Gesendete Pakete
  • Ofail - Fehlgeschlagene ausgehende Pakete
  • Colls - Kollisionen (bei Hubs, selten bei Switches)

Jedes Interface erscheint mehrfach: eine Zeile für die Link-Layer-Statistik (<Link>), und je eine weitere Zeile pro konfigurierter Adressfamilie bzw. Netzwerk – ein Interface mit IPv4- und IPv6-Adresse zeigt entsprechend mehrere Zeilen. Steigende Ifail- oder Ofail-Werte sind ein Anlass zur weiteren Diagnose; ihre Ursache lässt sich aus den Zählern allein nicht bestimmen.

ARP-Tabelle

MAC-Adress-Auflösung überprüfen:

$ arp -a
Host                                 Ethernet Address    Netif Expire    Flags
198.51.100.1                         52:54:00:12:34:01    vio1 permanent l
client.example.com                   52:54:00:12:34:02    vio0 19m58s
router.example.com                   52:54:00:12:34:03    vio0 permanent l

Wie oben zeigt arp -a alle Einträge. Die Spalten zeigen Hostname oder IP, MAC-Adresse, Interface, Ablaufzeit und Flags. Das Flag l kennzeichnet einen lokalen ARP-Eintrag. Ob ein Eintrag abläuft, zeigt die Spalte Expire; mit permanent gekennzeichnete Einträge laufen nicht ab.

Nur numerische Adressen ohne DNS-Auflösung anzeigen. -a wählt alle Cache-Einträge, -n unterdrückt die Namensauflösung:

$ arp -an

Die numerische Ausgabe vermeidet Verzögerungen durch DNS-Lookups und funktioniert auch bei nicht erreichbarem Resolver.

ARP-Cache leeren. -d löscht Einträge; zusammen mit -a wird die Löschoperation auf alle Cache-Einträge angewendet:

$ doas arp -d -a
198.51.100.1 (198.51.100.1) deleted
203.0.113.132 (203.0.113.132) deleted

Das Leeren des Caches erzwingt bei der nächsten Kommunikation eine neue ARP-Auflösung. Das ist beispielsweise nach einer MAC-Adress-Änderung oder bei der Fehlersuche hilfreich. Der Cache wird anschließend automatisch wieder gefüllt.

Socket-Statistiken

Offene Netzwerk-Verbindungen anzeigen. Bei netstat zeigt -a alle Sockets, -n Adressen und Ports numerisch; grep ESTABLISHED lässt aus dieser Ausgabe nur TCP-Verbindungen im Zustand ESTABLISHED stehen:

$ netstat -an | grep ESTABLISHED
tcp          0     44  192.168.1.65.22        192.168.1.132.52174    ESTABLISHED
ℹ️ Info
OpenBSDs netstat trennt IP-Adresse und Port mit einem Punkt (192.168.1.65.22), nicht mit einem Doppelpunkt wie bei vielen Linux-Tools üblich.

Die erste Spalte nennt mit tcp beziehungsweise tcp6 das Transportprotokoll und die Adressfamilie. Die weiteren Spalten zeigen:

  • Recv-Q - Empfangswarteschlange des Sockets
  • Send-Q - Sendewarteschlange des Sockets
  • Local Address - Lokales IP.Port-Paar
  • Foreign Address - Remote IP.Port-Paar
  • State - TCP-Verbindungsstatus

Listening-Ports identifizieren. Zusätzlich zu -a und -n zeigt -l nur lauschende Sockets; grep LISTEN beschränkt die Textausgabe auf den TCP-Zustand LISTEN:

$ netstat -anl | grep LISTEN
tcp          0      0  *.22                   *.*                    LISTEN
tcp          0      0  127.0.0.1.25           *.*                    LISTEN
tcp6         0      0  fe80::1%lo0.25         *.*                    LISTEN
tcp6         0      0  ::1.25                 *.*                    LISTEN
tcp6         0      0  *.22                   *.*                    LISTEN

Services mit einer Wildcard-Bindung zeigen *.PORT. *.* in der Spalte Foreign Address bedeutet bei einem lauschenden Socket, dass noch kein bestimmter entfernter Endpunkt gebunden ist. Spezifische lokale Adressen (127.0.0.1.PORT, ::1.PORT) beschränken den Socket auf diese Adresse – IPv4 und IPv6 erscheinen dabei als getrennte Einträge (tcp vs. tcp6).

Für Live-Statistiken mit automatischer Aktualisierung:

$ systat netstat

Das systat-Tool zeigt Netzwerk-Aktivität fortlaufend an. Standardmäßig wird die Ansicht alle fünf Sekunden aktualisiert; mit -s lässt sich das Intervall ändern, um den Verkehr während Tests in kürzeren oder längeren Abständen zu beobachten.

tcpdump für Paket-Analyse

Netzwerkverkehr live mitschneiden:

$ doas tcpdump -n -i vio0
tcpdump: listening on vio0, link-type EN10MB
07:40:01.579084 192.168.1.65.22 > 192.168.1.132.52174: P 2031605786:2031605822(36) ack 150708228 win 271 <nop,nop,timestamp 4109385259 2708599083> (DF) [tos 0xb8]
07:40:01.590399 192.168.1.65.22 > 192.168.1.132.52174: P 36:112(76) ack 1 win 271 <nop,nop,timestamp 4109385269 2708599083> (DF) [tos 0xb8]
[... weitere Pakete ...]

Die Parameter:

  • -n - Adressen und Ports numerisch anzeigen, ohne Namensauflösung
  • -i vio0 - Pakete auf dem Interface vio0 mitschneiden

Die Meldung listening on vio0, link-type EN10MB bestätigt das überwachte Interface und bezeichnet mit EN10MB den von BPF verwendeten Ethernet-Link-Typ. In jeder Paketzeile steht zuerst der Zeitstempel. Danach folgen Quelladresse und Quellport, > und Zieladresse mit Zielport. Beim gezeigten TCP-Paket bedeutet P ein gesetztes PSH-Flag; 2031605786:2031605822(36) bezeichnet den Sequenznummernbereich und die darin enthaltenen 36 Byte Nutzdaten. ack nennt die bestätigte nächste Sequenznummer und win das angekündigte TCP-Empfangsfenster. In <...> stehen TCP-Optionen, hier zwei No-Operation-Fülloptionen und die TCP-Timestamps. Bei timestamp 4109385259 2708599083 ist der erste Wert der aktuelle TCP Timestamp Value (TSval), der zweite der vom Kommunikationspartner zurückgespiegelte Timestamp Echo Reply Value (TSecr). (DF) zeigt das IPv4-Flag „Don’t Fragment“. [tos 0xb8] gibt das Type-of-Service-Byte des IPv4-Headers hexadezimal an; 0xb8 entspricht hier DSCP 46 (Expedited Forwarding) bei nicht gesetzten ECN-Bits. In der zweiten Zeile stellt 36:112(76) die Sequenznummern relativ zur ersten beobachteten Sequenz dar.

Netzwerkverkehr nach Port filtern:

$ doas tcpdump -n -i vio0 port 22

Für die Fehlersuche an einem einzelnen Dienst lässt sich die Aufzeichnung auf dessen Netzwerkverkehr beschränken. Dieser Filter erfasst nur Pakete zu und von Port 22.

ICMP-Verkehr anzeigen – dafür eignet sich ein paralleler ping von einem anderen Host als Testlast:

$ doas tcpdump -n -i vio0 icmp
tcpdump: listening on vio0, link-type EN10MB
07:43:12.985240 192.168.1.62 > 192.168.1.65: icmp: echo request
07:43:12.985575 192.168.1.65 > 192.168.1.62: icmp: echo reply

Der Zeitstempel steht am Anfang jeder Zeile; Quell- und Zieladresse werden durch > getrennt. icmp: echo request ist die Echo-Anfrage, icmp: echo reply die zugehörige Antwort. Der Filter zeigt außerdem andere IPv4-ICMP-Nachrichten, wenn sie auf dem Interface auftreten. IPv6 verwendet ICMPv6 und wird separat gefiltert:

$ doas tcpdump -n -i vio0 icmp6

Darüber lassen sich beispielsweise Router Solicitations, Router Advertisements und Neighbor Discovery beobachten.

Paket-Inhalte hexadezimal/ASCII ausgeben:

$ doas tcpdump -n -i vio0 -X icmp

Mit dem -X-Flag gibt tcpdump die Paketdaten als Hex- und ASCII-Dump aus. Dadurch wird auch der übertragene Nutzdateninhalt sichtbar. Bei verschlüsselten Protokollen bleibt dieser ohne Entschlüsselung jedoch nicht als Klartext lesbar.

ℹ️ Info
Die Reihenfolge der Optionen ist bei tcpdump wichtig: -i erwartet zwingend ein Argument. -i -X vio0 interpretiert -X fälschlich als Interface-Namen, gefolgt vom Fehler “Failed to open bpf device”. Korrekt ist -i vio0 -X oder -X an beliebiger anderer Position außerhalb direkt hinter -i.

DNS-Auflösung testen

DNS-Lookup durchführen:

$ host example.org
example.org has address 203.0.113.10
example.org mail is handled by 10 mail.example.org.

Die Ausgabe enthält den A-Record und, falls vorhanden, die MX-Records der Domain. Beim MX-Eintrag ist 10 die Präferenz des Mailservers; kleinere Werte werden gegenüber größeren bevorzugt. Bei Problemen lässt sich ein bestimmter DNS-Server gezielt abfragen, statt den konfigurierten System-Resolver zu nutzen:

$ host example.org 192.168.1.1
Using domain server:
Name: 192.168.1.1
Address: 192.168.1.1#53

example.org has address 203.0.113.10

Im Block Using domain server ist Name der abgefragte DNS-Server, Address seine Adresse und #53 der verwendete DNS-Port 53. Der Vergleich hilft einzugrenzen, ob die Ursache beim konfigurierten lokalen Resolver oder an anderer Stelle der DNS-Auflösung liegt. Im gezeigten Testnetz übernimmt der lokale unbound-Resolver diese Aufgabe – öffentliche Drittanbieter-Resolver wie die bekannten 1.1.1.1 oder 9.9.9.9 widersprechen dem Souveränitäts-Gedanken dieser Serie und eignen sich höchstens für einen punktuellen Vergleich.

Reverse-DNS-Lookup:

$ host 203.0.113.10
10.113.0.203.in-addr.arpa domain name pointer example.org.

Für IPv4 bildet host dazu den Namen in der Reverse-DNS-Zone in-addr.arpa: Die Oktette der Adresse erscheinen dort in umgekehrter Reihenfolge, anschließend verweist ein PTR-Record (domain name pointer) auf den Hostnamen. Reverse DNS löst damit eine IP-Adresse zurück zu einem Hostnamen auf. Reverse DNS spielt insbesondere beim Betrieb von Mailservern und bei verschiedenen Zugriffs- und Vertrauensprüfungen eine wichtige Rolle.

Netzwerk als nachvollziehbarer Systemzustand

Mit der Fehlersuche ist der Blick einmal durch die verschiedenen Ebenen des Netzwerks gewandert: vom erreichbaren Ziel über Routing und Interfaces bis zum einzelnen Paket. Damit lässt sich nicht nur feststellen, dass eine Verbindung scheitert, sondern auch, an welcher Stelle der erwartete Netzwerkzustand vom tatsächlich beobachteten Zustand abweicht.

OpenBSD behandelt Netzwerkkonfiguration damit nicht als einen einzelnen, zentralen Zustand. Adressen, Routen, Routing-Domains und dynamisch gelernte Informationen entstehen an unterschiedlichen Stellen, wirken aber gemeinsam darauf, wie der Kernel ein Paket weiterleitet. Gerade diese Trennung macht komplexere Netzwerke nachvollziehbar: Jede Änderung lässt sich ihrem Ursprung zuordnen und ihre Wirkung im resultierenden Netzwerkzustand überprüfen.

Damit ist geklärt, wohin OpenBSD Pakete schickt und wie sich ihr Weg nachvollziehen lässt. Ob sie diesen Weg auch nehmen dürfen, ist die nächste Ebene. Sie führt zum Artikel Firewall und pf-Grundlagen (geplant).


Verwendete Software und Versionen

Diese Anleitung bezieht sich auf:

  • OpenBSD: 7.8, 7.9
  • ospfd: Integriert in OpenBSD Base
  • dhcpd: Integriert in OpenBSD Base
  • rad: Integriert in OpenBSD Base
  • Kontext: Server und Router-Systeme
  • Stand: September 2026

Michael Schwarzer

beschäftigt sich seit über vier Jahrzehnten mit technischen Systemen, ihren Abhängigkeiten und Schnittstellen – heute besonders mit resilienten Systemen, digitaler Unabhängigkeit und dem kontrollierten Einsatz von KI.
Mehr über Michael →

Offenes Wissen braucht Menschen

Wir entwickeln Ideen, teilen Wissen, und lassen Open-Sorce-Software und -Werkzeuge entstehen. Mit deiner Unterstützung hilfst du uns, diese Arbeit langfristig fortzuführen.
Mitwirken und unterstützen →
OpenBSD: Netzwerk und Routing
Vorheriger Artikel → OpenBSD: Sicherheitsarchitektur und Schutzkonzepte