====== std.net.dhcp ======
DHCP-Client nach RFC 2131 — vollständige IP-Adressvergabe über UDP-Broadcast. Deckt den gesamten DORA-Prozess ab (Discover → Offer → Request → Acknowledge) und stellt Paket-Builder, Reply-Parser und High-Level-Lease-Funktion bereit.
import std.net.dhcp;
import std.alloc;
// Vollständigen Lease in einem Aufruf holen
var mac: int64 := alloc(6);
var result: int64 := alloc(DHCP_RESULT_SIZE);
// mac mit 6 Byte Ethernet-Adresse befüllen ...
poke8(mac, 0xAA); poke8(mac+1, 0xBB); // ...
if (DhcpLease(mac, result) == 0) {
var ip: int64 := peek64(result + DHCP_RESULT_IP);
var gw: int64 := peek64(result + DHCP_RESULT_GW);
var lease: int64 := peek64(result + DHCP_RESULT_LEASE);
}
free(result, DHCP_RESULT_SIZE);
free(mac, 6);
> **Berechtigung:** Port 68 < 1024 — erfordert root oder ''CAP_NET_BIND_SERVICE''.
----
===== Imports =====
* ''std.net.socket''
* ''std.net.syscalls''
* ''std.net.types''
* ''std.alloc''
----
===== Konstanten =====
==== Ports ====
^ Konstante ^ Wert ^ Bedeutung ^
| ''DHCP_PORT_SERVER'' | 67 | UDP-Port des DHCP-Servers |
| ''DHCP_PORT_CLIENT'' | 68 | UDP-Port des DHCP-Clients |
==== Operationscodes ====
^ Konstante ^ Wert ^ Bedeutung ^
| ''DHCP_OP_REQUEST'' | 1 | BOOTREQUEST — Client→Server |
| ''DHCP_OP_REPLY'' | 2 | BOOTREPLY — Server→Client |
==== DHCP-Nachrichtentypen (Option 53) ====
^ Konstante ^ Wert ^ Bedeutung ^
| ''DHCP_MSG_DISCOVER'' | 1 | Client sucht DHCP-Server |
| ''DHCP_MSG_OFFER'' | 2 | Server bietet IP an |
| ''DHCP_MSG_REQUEST'' | 3 | Client fordert angebotene IP an |
| ''DHCP_MSG_DECLINE'' | 4 | Client lehnt angebotene IP ab |
| ''DHCP_MSG_ACK'' | 5 | Server bestätigt Lease |
| ''DHCP_MSG_NAK'' | 6 | Server verweigert Lease |
| ''DHCP_MSG_RELEASE'' | 7 | Client gibt IP frei |
| ''DHCP_MSG_INFORM'' | 8 | Client fragt Konfiguration ohne IP-Zuweisung |
==== Options-Codes ====
^ Konstante ^ Wert ^ Bedeutung ^
| ''DHCP_OPT_SUBNET'' | 1 | Subnet Mask |
| ''DHCP_OPT_ROUTER'' | 3 | Default Gateway |
| ''DHCP_OPT_DNS'' | 6 | DNS-Server (erster Eintrag wird gespeichert) |
| ''DHCP_OPT_HOSTNAME'' | 12 | Hostname (nur deklariert, nicht gesetzt) |
| ''DHCP_OPT_DOMAIN'' | 15 | Domain Name (nur angefordert, nicht geparst) |
| ''DHCP_OPT_REQIP'' | 50 | Requested IP Address |
| ''DHCP_OPT_LEASE'' | 51 | Lease-Zeit in Sekunden |
| ''DHCP_OPT_MSGTYPE'' | 53 | Nachrichtentyp |
| ''DHCP_OPT_SERVERID'' | 54 | Server Identifier (IP des DHCP-Servers) |
| ''DHCP_OPT_PARAMREQ'' | 55 | Parameter Request List |
| ''DHCP_OPT_PAD'' | 0 | Padding-Byte |
| ''DHCP_OPT_END'' | 255 | Ende der Options |
==== Paket-Feldoffsets (RFC 2131 §2) ====
^ Konstante ^ Offset ^ Größe ^ Inhalt ^
| ''DHCP_OFFS_OP'' | 0 | 1 | Operationscode |
| ''DHCP_OFFS_HTYPE'' | 1 | 1 | Hardware-Typ (1 = Ethernet) |
| ''DHCP_OFFS_HLEN'' | 2 | 1 | Hardware-Adresslänge (6 für MAC) |
| ''DHCP_OFFS_HOPS'' | 3 | 1 | Relay-Agent-Hops |
| ''DHCP_OFFS_XID'' | 4 | 4 | Transaction ID (Big-Endian) |
| ''DHCP_OFFS_SECS'' | 8 | 2 | Sekunden seit Start |
| ''DHCP_OFFS_FLAGS'' | 10 | 2 | Flags (Bit 15 = Broadcast) |
| ''DHCP_OFFS_CIADDR'' | 12 | 4 | Client IP (bei Erneuerung) |
| ''DHCP_OFFS_YIADDR'' | 16 | 4 | Angebotene/zugewiesene IP |
| ''DHCP_OFFS_SIADDR'' | 20 | 4 | Nächster Server |
| ''DHCP_OFFS_GIADDR'' | 24 | 4 | Relay Agent IP |
| ''DHCP_OFFS_CHADDR'' | 28 | 16 | Client-Hardware-Adresse (MAC in Byte 0–5) |
| ''DHCP_OFFS_SNAME'' | 44 | 64 | Server-Hostname |
| ''DHCP_OFFS_FILE'' | 108 | 128 | Boot-Dateiname |
| ''DHCP_OFFS_COOKIE'' | 236 | 4 | Magic Cookie (0x63825363) |
| ''DHCP_OFFS_OPTIONS'' | 240 | — | Beginn der Options |
| ''DHCP_MIN_LEN'' | — | 300 | Mindestgröße (BOOTP-kompatibel) |
==== Sonstiges ====
^ Konstante ^ Wert ^ Bedeutung ^
| ''DHCP_FLAG_BROADCAST'' | 128 (0x80) | Broadcast-Bit (Byte 10, Bit 7) |
| ''DHCP_MAGIC_B0..B3'' | 99,130,83,99 | Magic Cookie 0x63825363 |
| ''DHCP_HTYPE_ETHER'' | 1 | Hardware-Typ Ethernet |
----
===== Result-Struct (DHCP_RESULT_SIZE = 72 Bytes) =====
Wird vom Caller alloziert und mit Nullen initialisiert. ''DhcpParseReply'', ''DhcpDiscover'', ''DhcpRequest'' und ''DhcpLease'' schreiben hinein.
Alle IP-Adressen sind im selben gepackten Format wie ''IPPack()'' aus ''std.net.types'' gespeichert (Big-Endian uint32 in int64).
^ Offset-Konstante ^ Offset ^ Inhalt ^
| ''DHCP_RESULT_IP'' | 0 | Zugewiesene IP-Adresse |
| ''DHCP_RESULT_MASK'' | 8 | Subnetzmaske |
| ''DHCP_RESULT_GW'' | 16 | Default Gateway |
| ''DHCP_RESULT_DNS'' | 24 | Primärer DNS-Server (erster aus Option 6) |
| ''DHCP_RESULT_LEASE'' | 32 | Lease-Zeit in Sekunden |
| ''DHCP_RESULT_SERVER'' | 40 | DHCP-Server-IP (Option 54) |
| ''DHCP_RESULT_XID'' | 48 | Transaction ID |
| ''DHCP_RESULT_TYPE'' | 56 | Nachrichtentyp (2=OFFER, 5=ACK, 6=NAK) |
| ''DHCP_RESULT_OK'' | 64 | 1 = Lease erfolgreich erhalten |
----
===== Funktionen =====
==== Paket-Builder ====
^ Signatur ^ Beschreibung ^
| ''DhcpBuildDiscover(buf: int64, mac: int64, xid: int64): int64'' | Baut DHCPDISCOVER-Paket in ''buf'' (≥ ''DHCP_MIN_LEN'' Bytes). ''mac'' = Zeiger auf 6-Byte-MAC. Broadcast-Flag gesetzt; Parameter Request List: Subnet, Router, DNS, Domain. Gibt ''DHCP_MIN_LEN'' zurück. |
| ''DhcpBuildRequest(buf: int64, mac: int64, xid: int64, offeredIP: int64, serverIP: int64): int64'' | Baut DHCPREQUEST-Paket. ''offeredIP'' und ''serverIP'' aus dem vorherigen OFFER übernommen (Option 50 + 54 werden gesetzt). Gibt ''DHCP_MIN_LEN'' zurück. |
==== Reply-Parser ====
^ Signatur ^ Beschreibung ^
| ''DhcpParseReply(buf: int64, len: int64, result: int64): void'' | Parst DHCP-Server-Antwort (OFFER oder ACK). Prüft OP=REPLY und Magic Cookie. Setzt ''DHCP_RESULT_OK=1'' bei OFFER oder ACK; 0 bei NAK, ungültigem Paket oder zu kurzem Puffer. Füllt IP, Mask, GW, DNS, Lease, Server, Type. |
==== High-Level Lease-Funktionen ====
^ Signatur ^ Beschreibung ^
| ''DhcpDiscover(mac: int64, xid: int64, result: int64): int64'' | Sendet DHCPDISCOVER (UDP-Broadcast) und wartet bis zu 5 Sekunden auf DHCPOFFER. Gibt 0 zurück wenn OFFER erhalten, -1 bei Fehler oder Timeout. |
| ''DhcpRequest(mac: int64, xid: int64, result: int64): int64'' | Sendet DHCPREQUEST (liest offeredIP und serverIP aus ''result'') und wartet bis zu 5 Sekunden auf DHCPACK. Gibt 0 nur zurück wenn ''DHCP_RESULT_TYPE = DHCP_MSG_ACK''. |
| ''DhcpLease(mac: int64, result: int64): int64'' | Vollständiger DORA-Prozess in einem Aufruf: Discover → Offer → Request → Acknowledge. XID wird aus den ersten 4 MAC-Bytes abgeleitet. Gibt 0 bei erfolgreichem Lease, -1 bei Fehler zurück. |
----
===== DORA-Ablauf =====
Client Server (255.255.255.255:67)
| |
|── DHCPDISCOVER (Broadcast) ────>| xid = MAC[0..3]
|<─ DHCPOFFER ────────────────────| yiaddr=192.168.1.100, srv=192.168.1.1
| |
|── DHCPREQUEST (Broadcast) ─────>| opt50=192.168.1.100, opt54=192.168.1.1
|<─ DHCPACK ──────────────────────| Lease bestätigt, Lease-Zeit: 43200s
''DhcpLease()'' führt den gesamten Ablauf durch. ''DhcpDiscover()'' und ''DhcpRequest()'' erlauben manuelle Steuerung (z. B. für Fehlerbehandlung oder Logging zwischen den Schritten).
----
===== Codebeispiel — Manueller DORA-Ablauf =====
import std.net.dhcp;
import std.net.types;
import std.alloc;
fn acquireLease(mac: int64): int64 {
var result: int64 := alloc(DHCP_RESULT_SIZE);
var xid: int64 := peek8(mac);
xid := (xid << 8) | peek8(mac + 1);
xid := (xid << 8) | peek8(mac + 2);
xid := (xid << 8) | peek8(mac + 3);
if (DhcpDiscover(mac, xid, result) < 0) {
free(result, DHCP_RESULT_SIZE);
return 0 - 1;
}
var offeredIP: int64 := peek64(result + DHCP_RESULT_IP);
var srvIP: int64 := peek64(result + DHCP_RESULT_SERVER);
// Hier könnte Validierung des OFFER stattfinden ...
if (DhcpRequest(mac, xid, result) < 0) {
free(result, DHCP_RESULT_SIZE);
return 0 - 1;
}
var ip: int64 := peek64(result + DHCP_RESULT_IP);
var mask: int64 := peek64(result + DHCP_RESULT_MASK);
var gw: int64 := peek64(result + DHCP_RESULT_GW);
var dns: int64 := peek64(result + DHCP_RESULT_DNS);
var lease: int64 := peek64(result + DHCP_RESULT_LEASE);
// IP-Adresse z. B. an Netzwerk-Interface binden ...
// Lease-Erneuerung nach lease/2 Sekunden nötig
free(result, DHCP_RESULT_SIZE);
return 0;
}
----
===== Hinweise =====
* **Berechtigung**: ''DhcpDiscover'' und ''DhcpRequest'' binden Port 68 — erfordert root oder ''CAP_NET_BIND_SERVICE''. Auf Linux genügt: ''setcap cap_net_bind_service+ep ./programm''.
* **IP-Format**: Alle IP-Adressen im Result-Struct verwenden das ''IPPack''-Format aus ''std.net.types'' (Big-Endian uint32 in int64). Zum Anzeigen: ''IpToStr()'' verwenden.
* **Timeout**: ''DhcpDiscover'' und ''DhcpRequest'' warten je maximal 5 Sekunden auf eine Antwort (''SocketCanRead(fd, 5000)''). Kein automatischer Retry.
* **Kein Lease-Renewal**: Diese Unit macht keine automatische Lease-Erneuerung. Die Anwendung muss nach ''lease/2'' Sekunden ''DhcpLease'' erneut aufrufen.
* **XID**: ''DhcpLease'' leitet die Transaction ID aus den ersten 4 MAC-Bytes ab. Bei mehreren gleichzeitigen Clients mit ähnlichen MACs kann es zu XID-Kollisionen kommen — in diesem Fall ''DhcpDiscover/DhcpRequest'' direkt mit einer zufälligen XID verwenden.
* **DNS**: Nur der erste DNS-Server (Byte 0–3 von Option 6) wird gespeichert. Mehrere DNS-Server werden ignoriert.
* **BOOTP-Kompatibilität**: Pakete sind mindestens 300 Bytes lang (''DHCP_MIN_LEN'') für ältere BOOTP-Server.
----
===== Quelldatei =====
^ Unit ^ Datei ^
| ''std.net.dhcp'' | ''std/net/dhcp.lyx'' |
Letzte Aktualisierung: 2026-06-17