/* ** This file is part of Filius, a network construction and simulation software. ** ** Originally created at the University of Siegen, Institute "Didactics of ** Informatics and E-Learning" by a students' project group: ** members (2006-2007): ** André Asschoff, Johannes Bade, Carsten Dittich, Thomas Gerding, ** Nadja Haßler, Ernst Johannes Klebert, Michell Weyer ** supervisors: ** Stefan Freischlad (maintainer until 2009), Peer Stechert ** Project is maintained since 2010 by Christian Eibl <filius@c.fameibl.de> ** and Stefan Freischlad ** Filius is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 2 of the License, or ** (at your option) version 3. ** ** Filius is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied ** warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR ** PURPOSE. See the GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with Filius. If not, see <http://www.gnu.org/licenses/>. */ package filius.software.dhcp; import java.util.Hashtable; import java.util.StringTokenizer; import filius.Main; import filius.software.clientserver.ServerMitarbeiter; import filius.software.transportschicht.Socket; import filius.software.transportschicht.UDPSocket; /** * <p> Der DHCPServerMitarbeiter hat eine andere Funktion als dies bei * sonstigen ServerMitarbeiter-Objekten der Fall ist. Dieser Mitarbeiter * verarbeitet alle eingehenden UDP-Segmente, weil alle DHCP-Clients mit * der Absender-Adresse 0.0.0.0 und dem UDP-Port 68 Nachrichten verschicken. * Daher funktioniert das Demultiplexen auf der Transportschicht nicht. </p> * * <p> Dieser Mitarbeiter verwaltet daher fuer jede MAC-Adresse, zu der eine * Verbindung besteht, einen eigenen Zustand in einer Hash-Tabelle, der ueber das * folgende Verhalten entscheidet. Der Socket wird nicht geschlossen, weil immer * auf weitere eingehende Verbindungsanfragen gewartet wird. </p> * * Die verwendeten DHCP-Nachrichten sind stark vereinfacht, weil sie * beobachtet werden können. Der Aufbau einer DHCP-Nachricht besteht aus * folgenden Komponenten: * <ul> * <li> Nachrichtentyp (s.u.) </li> * <li> Client-IP-Adresse: caddr </li> * <li> Client-MAC-Adresse: maddr </li> * <li> Server-IP-Adresse: saddr </li> * </ul> * * DHCP kennt folgende Nachrichtentypen (Quelle: de.wikipedia.org): * <ul> * <li> DHCPDISCOVER: Ein Client ohne IP-Adresse sendet eine * Broadcast-Anfrage nach Adress-Angeboten an den/die DHCP-Server im lokalen * Netz. </li> * <li> DHCPOFFER: Der/die DHCP-Server antworten mit entsprechenden Werten * auf eine DHCPDISCOVER-Anfrage. </li> * <li> DHCPREQUEST: Der Client fordert (eine der angebotenen) * IP-Adresse(n), weitere Daten sowie Verlaengerung der Lease-Zeit von einem * der antwortenden DHCP-Server. </li> * <li> DHCPACK: Bestaetigung des DHCP-Servers zu einer * DHCPREQUEST-Anforderung </li> * <li> DHCPNAK: Ablehnung einer DHCPREQUEST-Anforderung durch den * DHCP-Server </li> * <li> DHCPDECLINE: Ablehnung durch den Client, da die IP-Adresse schon * verwendet wird. * <li> * <li> DHCPRELEASE: Der Client gibt die eigene Konfiguration frei, damit * die Parameter wieder für andere Clients zur Verfuegung stehen. </li> * <li> DHCPINFORM: Anfrage eines Clients nach Daten ohne IP-Adresse, z. B. * weil der Client eine statische IP-Adresse besitzt. </li> * </ul> * Durch diesen DHCP-Server werden nicht alle Befehle unterstuetzt. */ public class DHCPServerMitarbeiter extends ServerMitarbeiter { private String angeboteneAdresse = ""; Hashtable<String,String> macIPAdresse = new Hashtable<String,String>(); public DHCPServerMitarbeiter(DHCPServer server, Socket socket) { super(server, socket); } /** * Ablauf einer DHCP-Anfrage (Quelle: de.wikipedia.org): <br /> * <p> * Wenn ein Client erstmalig eine IP-Adresse benötigt, schickt er eine * DHCPDISCOVER-Nachricht (mit seiner MAC-Adresse) als Netzwerk-Broadcast an * die verfuegbaren DHCP-Server (es kann durchaus mehrere davon im gleichen * Subnetz geben). Dieser Broadcast hat als Absender-IP-Adresse 0.0.0.0 und * als Zieladresse 255.255.255.255, da der Absender noch keine IP-Adresse * besitzt und seine Anfrage "an alle" richtet. Dabei ist der UDP-Quellport * 68 und der UDP-Zielport 67. Die DHCP-Server antworten mit DHCPOFFER und * machen Vorschlaege für eine IP-Adresse. Dies geschieht ebenfalls mit einem * Broadcast an die Adresse 255.255.255.255 mit UDP-Quellport 67 und * UDP-Zielport 68. </p> * * <p> * Der Client darf nun unter den eingetroffenen Angeboten (DHCP-Offers) * waehlen. Wenn er sich für eines entschieden hat (z. B. wegen laengster * Lease-Zeit oder wegen Ablehnung eines speziellen, evtl. falsch * konfigurierten DHCP-Servers, oder einfach für die erste Antwort), * kontaktiert er per Broadcast und einem im Paket enthaltenen * Serveridentifier den entsprechenden Server mit der Nachricht DHCPREQUEST. * Alle eventuellen weiteren DHCP-Server werten dies als Absage fuer ihre * Angebote. Der vom Client ausgewaehlte Server bestaetigt in einer * DHCPACK-Nachricht (DHCP-Acknowledged) die IP-Adresse mit den weiteren * relevanten Daten, oder er zieht sein Angebot zurück (DHCPNAK, siehe auch * sonstiges). * </p> * * <p> * Bevor der Client sein Netzwerkinterface mit der zugewiesenen Adresse * konfiguriert, sollte er noch pruefen, ob nicht versehentlich noch ein * anderer Rechner die Adresse verwendet. Dies geschieht ueblicherweise durch * einen ARP-Request mit der soeben zugeteilten IP-Adresse. Antwortet ein * anderer Host im Netz auf diesen Request, so wird der Client die * vorgeschlagene Adresse mit einer DHCPDECLINE-Nachricht zurueckweisen. * </p> */ protected void verarbeiteNachricht(String nachricht) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (DHCPServerMitarbeiter), verarbeiteNachricht("+nachricht+")"); String body; StringTokenizer st; String caddr; String maddr; String saddr; String befehl; DHCPServer dhcpServer; dhcpServer = (DHCPServer) server; st = new StringTokenizer(nachricht, " "); befehl = st.nextToken(); caddr = st.nextToken(); maddr = st.nextToken(); saddr = st.nextToken(); // der Client sucht einen DHCP-Server if (befehl.equalsIgnoreCase("DHCPDISCOVER")) { angeboteneAdresse = dhcpServer.reserviereFreieIP(maddr); //if (angeboteneAdresse == null) Main.debug.println("Adresse null"); //else if (maddr == null) Main.debug.println("MAC null"); macIPAdresse.put(maddr, angeboteneAdresse); ((UDPSocket) socket).sendeBroadcast("DHCPOFFER " + caddr + " " + maddr + " " + saddr + " " + angeboteneAdresse); } // der Client fordert die angebotene IP-Adresse an else if ((befehl.equalsIgnoreCase("DHCPREQUEST"))) { angeboteneAdresse = macIPAdresse.get(maddr); body = st.nextToken(); if (angeboteneAdresse != null && body.trim().equalsIgnoreCase(angeboteneAdresse)) { dhcpServer.gibMACFrei(maddr); dhcpServer.reserviereIPAdresse(maddr, body, 0); ((UDPSocket) socket).sendeBroadcast("DHCPACK " + caddr + " " + maddr + " " + saddr + " " + body + " " + dhcpServer.getSubnetzmaske() + " " + dhcpServer.getGatewayip() + " " + dhcpServer.getDnsserverip()); } else { ((UDPSocket) socket).sendeBroadcast("DHCPNAK " + caddr + " " + maddr + " " + saddr + " " + body); } macIPAdresse.remove(maddr); } // Die zugewiesene IP-Adresse eines Rechner wird wieder freigegeben else if (befehl.equalsIgnoreCase("DHCPRELEASE")) { dhcpServer.gibMACFrei(maddr); } // Abfrage der Standardeinstellungen (ohne neue IP-Adresse) else if (befehl.equalsIgnoreCase("DHCPINFORM")) { ((UDPSocket) socket).sendeBroadcast(caddr + " " + maddr + " " + saddr + " " + dhcpServer.getDnsserverip() + " " + dhcpServer.getGatewayip() + " " + dhcpServer.getSubnetzmaske()); } // Client hat IP-Adresse abgelehnt else if (befehl.equalsIgnoreCase("DHCPDECLINE")) { dhcpServer.gibMACFrei(maddr); macIPAdresse.remove(maddr); } else { Main.debug.println("ERROR ("+this.hashCode()+"): unbekannten DHCP-Nachrichtentyp empfangen: "+befehl); } } }