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.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)

View File

@@ -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: