Added DNS
This commit is contained in:
@@ -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)
|
||||||
|
39
src/gui.py
39
src/gui.py
@@ -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:
|
||||||
|
Reference in New Issue
Block a user