diff --git a/src/dhcp_server.py b/src/dhcp_server.py index 94cf038..4e035e0 100644 --- a/src/dhcp_server.py +++ b/src/dhcp_server.py @@ -82,7 +82,7 @@ class DHCPServer: self.interface: Optional[str] = None self.ip_start: 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._active_clients: List[str] = [] # list of MACs @@ -92,8 +92,11 @@ class DHCPServer: self._log = deque(maxlen=500) self._status = "idle" + # NEW: configurable DNS servers for DHCP Option 6 + self.dns_servers: List[str] = [] + # -------------------- 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(): raise RuntimeError("Server is already running.") self.interface = interface @@ -101,9 +104,21 @@ class DHCPServer: self.subnet_mask = mask # 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) + # 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._thread = threading.Thread(target=self._run_loop, name="DHCPWorker", daemon=True) self._thread.start() @@ -227,18 +242,16 @@ class DHCPServer: # Build pool from the network containing start_ip with provided mask net = ipaddress.IPv4Network((start_ip, mask), strict=False) 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: start_idx = all_hosts.index(ipaddress.IPv4Address(start_ip)) except ValueError: - # pick the first host >= start_ip, else 0 start_idx = 0 for i, h in enumerate(all_hosts): if int(h) >= int(ipaddress.IPv4Address(start_ip)): start_idx = i break pool = [str(h) for h in all_hosts[start_idx:]] - # Ensure server_ip isn't handed out if start_ip in pool: pool.remove(start_ip) return pool @@ -346,16 +359,21 @@ class DHCPServer: op, htype, hlen, hops, xid, secs, flags, ciaddr, yiaddr_b, siaddr, giaddr_b, chaddr, sname, filef) - # Options: message type, server id, subnet mask, router (server), lease time - options = [ + # Options: message type, server id, subnet mask, router (server), DNS list, lease time + options: List[Tuple[int, bytes]] = [ (53, bytes([msg_type])), (54, siaddr), # server identifier ] if self.subnet_mask: options.append((1, ip2bytes(self.subnet_mask))) # subnet mask if self.server_ip: - options.append((3, ip2bytes(self.server_ip))) # router - options.append((6, ip2bytes(self.server_ip))) # DNS (for lab convenience) + options.append((3, ip2bytes(self.server_ip))) # router (gateway) + + # 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 return hdr + build_options(options) diff --git a/src/gui.py b/src/gui.py index 397a53e..0892f00 100644 --- a/src/gui.py +++ b/src/gui.py @@ -2,7 +2,7 @@ import os import socket import tkinter as tk from tkinter import messagebox, ttk -from typing import Optional +from typing import Optional, List from dhcp_server import DHCPServer import utils @@ -55,7 +55,7 @@ class DHCPApp(tk.Frame): def __init__(self, master: tk.Tk) -> None: super().__init__(master) master.title("Simple DHCP Server (Lab)") - master.minsize(680, 460) + master.minsize(700, 500) self.pack(fill="both", expand=True, padx=12, pady=12) # Single server instance @@ -65,10 +65,12 @@ class DHCPApp(tk.Frame): form = ttk.Frame(self) form.pack(fill="x", pady=(0, 8)) + # Zeile 0 ttk.Label(form, text="Interface:").grid(row=0, column=0, sticky="w") 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.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") 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.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 --- 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_stop = ttk.Button(btns, text="Stop", command=self.stop_server, state="disabled") self.btn_start.pack(side="left") @@ -126,14 +137,19 @@ class DHCPApp(tk.Frame): ip, mask = utils.get_iface_ipv4_config(iface) if ip: self.ip_var.set(ip) + # sinnvolles Default: Primary DNS = Interface-IP + self.dns1_var.set(ip) if mask: self.mask_var.set(mask) + # Secondary DNS bleibt leer, damit du frei wählen kannst def _set_controls_enabled(self, enabled: bool): state = "normal" if enabled else "disabled" self.if_menu.configure(state=state) self.ip_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_stop.configure(state="disabled" if enabled else "normal") @@ -141,19 +157,29 @@ class DHCPApp(tk.Frame): if self.server and self.server.is_running(): messagebox.showinfo("Info", "Server läuft bereits.") return - # Big fat warning + # Fette Warnung if not messagebox.askyesno("Warnung", WARNING_TEXT, icon="warning"): return iface = self.if_var.get().strip() ip = self.ip_var.get().strip() mask = self.mask_var.get().strip() + dns1 = self.dns1_var.get().strip() + dns2 = self.dns2_var.get().strip() + if not iface: messagebox.showerror("Fehler", "Bitte ein Netzwerk-Interface wählen.") return + + # Eingaben prüfen/normalisieren try: ip = utils.format_ip_address(ip) 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: messagebox.showerror("Ungültige Eingabe", str(e)) return @@ -173,7 +199,8 @@ class DHCPApp(tk.Frame): # --- Start --- try: 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._set_controls_enabled(False) except Exception as e: