Added DNS

This commit is contained in:
2025-09-11 12:48:41 +02:00
parent 044973230e
commit 4025353855
2 changed files with 61 additions and 16 deletions

View File

@@ -82,7 +82,7 @@ class DHCPServer:
self.interface: Optional[str] = None self.interface: Optional[str] = None
self.ip_start: Optional[str] = None self.ip_start: Optional[str] = None
self.subnet_mask: Optional[str] = None self.subnet_mask: Optional[str] = None
self.server_ip: Optional[str] = None # inferred from interface IP self.server_ip: Optional[str] = None # we use start_ip as server identifier in this lab build
self._leases: Dict[str, Tuple[str, float]] = {} # MAC -> (IP, expiry_ts) self._leases: Dict[str, Tuple[str, float]] = {} # MAC -> (IP, expiry_ts)
self._active_clients: List[str] = [] # list of MACs self._active_clients: List[str] = [] # list of MACs
@@ -92,8 +92,11 @@ class DHCPServer:
self._log = deque(maxlen=500) self._log = deque(maxlen=500)
self._status = "idle" self._status = "idle"
# NEW: configurable DNS servers for DHCP Option 6
self.dns_servers: List[str] = []
# -------------------- Public API -------------------- # -------------------- Public API --------------------
def start(self, interface: str, start_ip: str, mask: str) -> None: def start(self, interface: str, start_ip: str, mask: str, dns_servers: Optional[List[str]] = None) -> None:
if self.is_running(): if self.is_running():
raise RuntimeError("Server is already running.") raise RuntimeError("Server is already running.")
self.interface = interface self.interface = interface
@@ -101,9 +104,21 @@ class DHCPServer:
self.subnet_mask = mask self.subnet_mask = mask
# build pool based on interface network # build pool based on interface network
self.server_ip = start_ip # We use the provided IP as "server identifier"; typically you'd use iface IP self.server_ip = start_ip # in this lab version we use the provided IP as "server identifier"
self._pool = self._build_pool(start_ip, mask) self._pool = self._build_pool(start_ip, mask)
# configure DNS (filter invalid entries); default to [server_ip, 8.8.8.8]
self.dns_servers = []
for d in (dns_servers or []):
try:
socket.inet_aton(d)
self.dns_servers.append(d)
except Exception:
# ignore invalid entries silently
continue
if not self.dns_servers and self.server_ip:
self.dns_servers = [self.server_ip, "8.8.8.8"]
self._stop_evt.clear() self._stop_evt.clear()
self._thread = threading.Thread(target=self._run_loop, name="DHCPWorker", daemon=True) self._thread = threading.Thread(target=self._run_loop, name="DHCPWorker", daemon=True)
self._thread.start() self._thread.start()
@@ -227,18 +242,16 @@ class DHCPServer:
# Build pool from the network containing start_ip with provided mask # Build pool from the network containing start_ip with provided mask
net = ipaddress.IPv4Network((start_ip, mask), strict=False) net = ipaddress.IPv4Network((start_ip, mask), strict=False)
all_hosts = list(net.hosts()) all_hosts = list(net.hosts())
# Begin at start_ip position (or nearest host) and go upwards, excluding start_ip itself for server identity # Begin at/after start_ip; exclude start_ip (we use it as server/gateway)
try: try:
start_idx = all_hosts.index(ipaddress.IPv4Address(start_ip)) start_idx = all_hosts.index(ipaddress.IPv4Address(start_ip))
except ValueError: except ValueError:
# pick the first host >= start_ip, else 0
start_idx = 0 start_idx = 0
for i, h in enumerate(all_hosts): for i, h in enumerate(all_hosts):
if int(h) >= int(ipaddress.IPv4Address(start_ip)): if int(h) >= int(ipaddress.IPv4Address(start_ip)):
start_idx = i start_idx = i
break break
pool = [str(h) for h in all_hosts[start_idx:]] pool = [str(h) for h in all_hosts[start_idx:]]
# Ensure server_ip isn't handed out
if start_ip in pool: if start_ip in pool:
pool.remove(start_ip) pool.remove(start_ip)
return pool return pool
@@ -346,16 +359,21 @@ class DHCPServer:
op, htype, hlen, hops, xid, secs, flags, op, htype, hlen, hops, xid, secs, flags,
ciaddr, yiaddr_b, siaddr, giaddr_b, chaddr, sname, filef) ciaddr, yiaddr_b, siaddr, giaddr_b, chaddr, sname, filef)
# Options: message type, server id, subnet mask, router (server), lease time # Options: message type, server id, subnet mask, router (server), DNS list, lease time
options = [ options: List[Tuple[int, bytes]] = [
(53, bytes([msg_type])), (53, bytes([msg_type])),
(54, siaddr), # server identifier (54, siaddr), # server identifier
] ]
if self.subnet_mask: if self.subnet_mask:
options.append((1, ip2bytes(self.subnet_mask))) # subnet mask options.append((1, ip2bytes(self.subnet_mask))) # subnet mask
if self.server_ip: if self.server_ip:
options.append((3, ip2bytes(self.server_ip))) # router options.append((3, ip2bytes(self.server_ip))) # router (gateway)
options.append((6, ip2bytes(self.server_ip))) # DNS (for lab convenience)
# DHCP Option 6: one option with N*4 bytes for N DNS servers
if self.dns_servers:
dns_bytes = b"".join(ip2bytes(ip) for ip in self.dns_servers)
options.append((6, dns_bytes))
options.append((51, struct.pack("!I", LEASE_SECONDS))) # lease time options.append((51, struct.pack("!I", LEASE_SECONDS))) # lease time
return hdr + build_options(options) return hdr + build_options(options)

View File

@@ -2,7 +2,7 @@ import os
import socket import socket
import tkinter as tk import tkinter as tk
from tkinter import messagebox, ttk from tkinter import messagebox, ttk
from typing import Optional from typing import Optional, List
from dhcp_server import DHCPServer from dhcp_server import DHCPServer
import utils import utils
@@ -55,7 +55,7 @@ class DHCPApp(tk.Frame):
def __init__(self, master: tk.Tk) -> None: def __init__(self, master: tk.Tk) -> None:
super().__init__(master) super().__init__(master)
master.title("Simple DHCP Server (Lab)") master.title("Simple DHCP Server (Lab)")
master.minsize(680, 460) master.minsize(700, 500)
self.pack(fill="both", expand=True, padx=12, pady=12) self.pack(fill="both", expand=True, padx=12, pady=12)
# Single server instance # Single server instance
@@ -65,10 +65,12 @@ class DHCPApp(tk.Frame):
form = ttk.Frame(self) form = ttk.Frame(self)
form.pack(fill="x", pady=(0, 8)) form.pack(fill="x", pady=(0, 8))
# Zeile 0
ttk.Label(form, text="Interface:").grid(row=0, column=0, sticky="w") ttk.Label(form, text="Interface:").grid(row=0, column=0, sticky="w")
self.if_var = tk.StringVar() self.if_var = tk.StringVar()
self.if_menu = ttk.OptionMenu(form, self.if_var, None, *utils.get_network_interfaces(), command=self.on_iface_change) self.if_menu = ttk.OptionMenu(form, self.if_var, None, *utils.get_network_interfaces(), command=self.on_iface_change)
self.if_menu.grid(row=0, column=1, sticky="ew", padx=(6, 16)) self.if_menu.grid(row=0, column=1, sticky="ew", padx=(6, 16))
form.columnconfigure(1, weight=1)
ttk.Label(form, text="Server/Start-IP:").grid(row=0, column=2, sticky="w") ttk.Label(form, text="Server/Start-IP:").grid(row=0, column=2, sticky="w")
self.ip_var = tk.StringVar(value="") self.ip_var = tk.StringVar(value="")
@@ -80,11 +82,20 @@ class DHCPApp(tk.Frame):
self.mask_entry = ttk.Entry(form, textvariable=self.mask_var, width=16) self.mask_entry = ttk.Entry(form, textvariable=self.mask_var, width=16)
self.mask_entry.grid(row=0, column=5, sticky="w", padx=(6, 16)) self.mask_entry.grid(row=0, column=5, sticky="w", padx=(6, 16))
form.columnconfigure(1, weight=1) # Zeile 1 DNS Felder
ttk.Label(form, text="Primary DNS:").grid(row=1, column=0, sticky="w", pady=(6, 0))
self.dns1_var = tk.StringVar(value="")
self.dns1_entry = ttk.Entry(form, textvariable=self.dns1_var, width=16)
self.dns1_entry.grid(row=1, column=1, sticky="w", padx=(6, 16), pady=(6, 0))
ttk.Label(form, text="Secondary DNS:").grid(row=1, column=2, sticky="w", pady=(6, 0))
self.dns2_var = tk.StringVar(value="")
self.dns2_entry = ttk.Entry(form, textvariable=self.dns2_var, width=16)
self.dns2_entry.grid(row=1, column=3, sticky="w", padx=(6, 16), pady=(6, 0))
# --- Buttons --- # --- Buttons ---
btns = ttk.Frame(self) btns = ttk.Frame(self)
btns.pack(fill="x", pady=(0, 8)) btns.pack(fill="x", pady=(8, 8))
self.btn_start = ttk.Button(btns, text="Start", command=self.start_server) self.btn_start = ttk.Button(btns, text="Start", command=self.start_server)
self.btn_stop = ttk.Button(btns, text="Stop", command=self.stop_server, state="disabled") self.btn_stop = ttk.Button(btns, text="Stop", command=self.stop_server, state="disabled")
self.btn_start.pack(side="left") self.btn_start.pack(side="left")
@@ -126,14 +137,19 @@ class DHCPApp(tk.Frame):
ip, mask = utils.get_iface_ipv4_config(iface) ip, mask = utils.get_iface_ipv4_config(iface)
if ip: if ip:
self.ip_var.set(ip) self.ip_var.set(ip)
# sinnvolles Default: Primary DNS = Interface-IP
self.dns1_var.set(ip)
if mask: if mask:
self.mask_var.set(mask) self.mask_var.set(mask)
# Secondary DNS bleibt leer, damit du frei wählen kannst
def _set_controls_enabled(self, enabled: bool): def _set_controls_enabled(self, enabled: bool):
state = "normal" if enabled else "disabled" state = "normal" if enabled else "disabled"
self.if_menu.configure(state=state) self.if_menu.configure(state=state)
self.ip_entry.configure(state=state) self.ip_entry.configure(state=state)
self.mask_entry.configure(state=state) self.mask_entry.configure(state=state)
self.dns1_entry.configure(state=state)
self.dns2_entry.configure(state=state)
self.btn_start.configure(state="normal" if enabled else "disabled") self.btn_start.configure(state="normal" if enabled else "disabled")
self.btn_stop.configure(state="disabled" if enabled else "normal") self.btn_stop.configure(state="disabled" if enabled else "normal")
@@ -141,19 +157,29 @@ class DHCPApp(tk.Frame):
if self.server and self.server.is_running(): if self.server and self.server.is_running():
messagebox.showinfo("Info", "Server läuft bereits.") messagebox.showinfo("Info", "Server läuft bereits.")
return return
# Big fat warning # Fette Warnung
if not messagebox.askyesno("Warnung", WARNING_TEXT, icon="warning"): if not messagebox.askyesno("Warnung", WARNING_TEXT, icon="warning"):
return return
iface = self.if_var.get().strip() iface = self.if_var.get().strip()
ip = self.ip_var.get().strip() ip = self.ip_var.get().strip()
mask = self.mask_var.get().strip() mask = self.mask_var.get().strip()
dns1 = self.dns1_var.get().strip()
dns2 = self.dns2_var.get().strip()
if not iface: if not iface:
messagebox.showerror("Fehler", "Bitte ein Netzwerk-Interface wählen.") messagebox.showerror("Fehler", "Bitte ein Netzwerk-Interface wählen.")
return return
# Eingaben prüfen/normalisieren
try: try:
ip = utils.format_ip_address(ip) ip = utils.format_ip_address(ip)
utils.validate_subnet_mask(mask) utils.validate_subnet_mask(mask)
dns_list: List[str] = []
if dns1:
dns_list.append(utils.format_ip_address(dns1))
if dns2:
dns_list.append(utils.format_ip_address(dns2))
except Exception as e: except Exception as e:
messagebox.showerror("Ungültige Eingabe", str(e)) messagebox.showerror("Ungültige Eingabe", str(e))
return return
@@ -173,7 +199,8 @@ class DHCPApp(tk.Frame):
# --- Start --- # --- Start ---
try: try:
self.server = DHCPServer() self.server = DHCPServer()
self.server.start(iface, ip, mask) # NEU: DNS-Liste an den Server übergeben
self.server.start(iface, ip, mask, dns_servers=dns_list)
self.status_var.set("Server gestartet.") self.status_var.set("Server gestartet.")
self._set_controls_enabled(False) self._set_controls_enabled(False)
except Exception as e: except Exception as e: