/* ** 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.Iterator; import java.util.LinkedList; import java.util.StringTokenizer; import filius.Main; import filius.hardware.Verbindung; import filius.rahmenprogramm.EingabenUeberpruefung; import filius.software.clientserver.UDPServerAnwendung; import filius.software.transportschicht.Socket; /** * In dieser Klasse und in DHCPServerMitarbeiter wird ein Sever fuer Dynamic * Host Configuration Protocol implementiert. <br /> * In dieser Klasse werden die Einstellungen des Server verwaltet. Das Protokoll * zur Vereinbarung einer IP-Adresse wird durch DHCPServerMitarbeiter * realisiert. D. h. zu jeder eingehenden Anfrage wird ein neuer Mitarbeiter * erzeugt. */ public class DHCPServer extends UDPServerAnwendung { /** niedrigste IP-Adresse, die durch diesen DHCP-Server vergeben wird */ private String untergrenze; /** hoechste IP-Adresse, die durch diesen DHCP-Server vergeben wird */ private String obergrenze; /** settings for DHCP server; not necessarily equal to operating system settings **/ private String dhcpGateway = "0.0.0.0"; private String dhcpDNS = "0.0.0.0"; private boolean ownSettings = false; /** * wie lange ein DHCP-Eintrag gueltig sein soll bzw. die IP-Adresse einer * MAC zugewiesen bleibt (Standardwert) */ private long ttlvoneinemEintrag = 86400000; /** Die zuletzt vergebene IP-Adresse */ private String letztevergebeneIPm = null; // has server already been started (entirely), i.e., can it be contacted by a client private boolean started = false; /** Liste mit vergebenen IP-Adressen mit zugehoeriger MAC-Adresse */ private LinkedList<IPEintrag> ipListe = new LinkedList<IPEintrag>(); /** Konstruktor, in dem der UDP-Port 67 gesetzt wird. */ public DHCPServer() { super(); port = 67; } /** * Zur Pruefung, ob eine bestimmte IP-Adresse bereits vergeben ist. <br /> * Zunaechst wird dazu die Liste vergebener IP-Adressen aufgeraeumt (d.h. * abgelaufene Eintraege entfernt) mit Aufruf von cleanUp() */ private boolean istIPAdresseFrei(String ip) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (DHCPServer), istIPAdresseFrei("+ip+")"); cleanUp(); synchronized (ipListe) { for (int i = 0; i < ipListe.size(); i++) { IPEintrag ipE = (IPEintrag) ipListe.get(i); if (ipE.getIp().equalsIgnoreCase(ip)) { return false; } } } return true; } public boolean useInternal() { return ownSettings; } public void setOwnSettings(boolean val) { this.ownSettings = val; } /** * Ein eventuell vorhandener Eintrag zur uebergebenen MAC-Adresse wird * geloescht. */ public boolean gibMACFrei(String macAdresse) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (DHCPServer), gibMACFrei("+macAdresse+")"); synchronized (ipListe) { for (int i = 0; i < ipListe.size(); i++) { IPEintrag ipE = (IPEintrag) ipListe.get(i); if (ipE.getMAC().equalsIgnoreCase(macAdresse)) { ipListe.remove(i); i--; } } } return true; } /** * Methode, um die naechste freie IP-Adresse zu erhalten. * <ol> * <li> Beginnend mit der Untergrenze werden alle IP-Adressen im Intervall * von Untergrenze bis Obergrenze geprueft, ob sie noch zu vergeben sind. * </li> * <li> die gefundene IP-Adresse wird zunaechst mit einer kurzen TTL * reserviert, d.h. in die Liste vergebener IP-Adressen eingetragen (TTL = 4 * sek. mal Verzoegerungsfaktor der Verbindungsleitungen)</li> * <li> Rueckgabe der reservierten IP-Adresse </li> * </ol> * */ synchronized String reserviereFreieIP(String maddr) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (DHCPServer), reserviereFreiIP("+maddr+")"); String freieIP; synchronized (ipListe) { freieIP = letztevergebeneIPm; while (freieIP != null && !istIPAdresseFrei(freieIP)) { freieIP = naechsteIPAdresse(freieIP); } } if (freieIP != null) { reserviereIPAdresse(maddr, freieIP, System.currentTimeMillis() + (long) (Verbindung.holeRTT() * 20)); letztevergebeneIPm = freieIP; } return freieIP; } /** * Weisst einer mac-Adresse eine IP für die angegebene ttl zu. Diese * Funktion wird normalerweise nur von den DHCPServerMitarbeitern * ausgeführt, kann aber auch "manuell" aufgerufen werden. Das richtige * Setzen der ttl ist hierbei zu beachten. Sobald System.currentMills() >= * ttl ist, ist die time to life abgelaufen. die ttl muss also entsprechend * hoch gesetzt werden. <br /> * <b>Achtung! </b> Wenn die angeforderte Adresse bereits vergeben ist, * erfolgt keine Zuweisung! * * @param mac * @param ip * @param ttl * wenn 0, dann wird die Standard-TTL verwendet * @return */ synchronized boolean reserviereIPAdresse(String mac, String ip, long ttl) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (DHCPServer), reserviereIPAdresse("+mac+","+ip+","+ttl+")"); if (istIPAdresseFrei(ip)) { synchronized (ipListe) { IPEintrag ipE = new IPEintrag(); ipE.setMAC(mac); if (ttl == 0) ipE.setTtl(System.currentTimeMillis() + ttlvoneinemEintrag); else ipE.setTtl(ttl); // LEASETIME BEACHTEN! ipE.setIp(ip); ipListe.add(ipE); } return true; } else return false; } /** * Entfernt abgelaufene Eintraege. Wird automatisch vor jeder Suche nach der * naechsten freien IP-Adresse ausgefuehrt. */ private void cleanUp() { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (DHCPServer), cleanUp()"); IPEintrag ipE; Iterator it; LinkedList<IPEintrag> abgelaufeneEintraege; abgelaufeneEintraege = new LinkedList<IPEintrag>(); synchronized (ipListe) { it = ipListe.listIterator(); while (it.hasNext()) { ipE = (IPEintrag) it.next(); if (ipE.getTtl() < System.currentTimeMillis()) { abgelaufeneEintraege.add(ipE); } } it = abgelaufeneEintraege.listIterator(); while (it.hasNext()) { ipListe.remove(it.next()); } } } /** * Berechnet die folgende IP-Adresse an Hand der Netzmaske. Wenn die * Obergrenze erreicht ist, wird <b>nicht</b> wieder bei der Untergrenze * begonnen. <br /> * Dazu wird zunaechst geprueft, ob die Netzkennung der uebergebenen * IP-Adresse identisch zu denen der Unter- und Obergrenze ist. <br /> * Dann wird geprueft, ob die Rechnerkennung der uebergebenen IP-Adresse * zwischen Unter- und Obergrenze liegt. <br /> * Schliesslich wird die naechste Rechnerkennung zurueck gegeben. * * @param ip * Die IP-Adresse, zu der die folgende Adresse gesucht wird. * @return die Folgeadresse oder null, wenn es keine Adresse im Intervall * zwischen Unter- und Obergrenze gibt, die auf die uebergebene * Adresse folgt. */ private String naechsteIPAdresse(String ip) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (DHCPServer), naechsteIPAdresse("+ip+")"); // die Netzmaske als eine Zahl long maske = 0; // zu jeder IP-Adresse // [0] die vollstaendige Adresse, // [1] die Netzkennung und // [2] die Rechnerkennung // jeweils als 4-Tupel von Zahlen long[] adresse, untergrenze, obergrenze; StringTokenizer tokenizer; long tmp; String naechsteAdresse; // -------------------------------------------------------- // Initialisierung der Arrays tokenizer = new StringTokenizer(getSubnetzmaske(), "."); for (int i = 0; i < 4; i++) { maske = maske * 256 + Integer.parseInt(tokenizer.nextToken()); } tokenizer = new StringTokenizer(ip, "."); adresse = new long[3]; adresse[0] = 0; for (int i = 0; i < 4; i++) { adresse[0] = adresse[0] * 256 + Integer.parseInt(tokenizer.nextToken()); } adresse[1] = adresse[0] & maske; adresse[2] = adresse[0] & (4294967295l - maske); tokenizer = new StringTokenizer(getUntergrenze(), "."); untergrenze = new long[3]; untergrenze[0] = 0; for (int i = 0; i < 4; i++) { untergrenze[0] = untergrenze[0] * 256 + Integer.parseInt(tokenizer.nextToken()); } untergrenze[1] = untergrenze[0] & maske; untergrenze[2] = untergrenze[0] & (4294967295l - maske); tokenizer = new StringTokenizer(getObergrenze(), "."); obergrenze = new long[3]; obergrenze[0] = 0; for (int i = 0; i < 4; i++) { obergrenze[0] = obergrenze[0] * 256 + Integer.parseInt(tokenizer.nextToken()); } obergrenze[1] = obergrenze[0] & maske; obergrenze[2] = obergrenze[0] & (4294967295l - maske); // -------------------------------------------------------- // Pruefung der Netzkennung und der Rechnerkennung der uebergebenen // IP-Adresse: // im gueltigen Intervall zwischen Unter- und Obergrenze? if ((adresse[1] != untergrenze[1]) || (adresse[1] != obergrenze[1]) || (adresse[2] < untergrenze[2]) || (adresse[2] >= obergrenze[2]) || untergrenze[2] == obergrenze[2]) { //Main.debug.println(getClass() + " naechsteIPAdresse():" //+ "\n\tkeine weitere Adresse im Intervall verfuegbar" //+ "\n\t " + untergrenze[0] + " (" + getUntergrenze() //+ ") - " + obergrenze[0] + " (" + getObergrenze() + ")"); return null; } // -------------------------------------------------------- // naechste IP-Adresse im Intervall berechnen und Rueckgabe als String tmp = adresse[0] + 1; naechsteAdresse = (tmp / 16777216) + "." + ((tmp % 16777216) / 65536) + "." + ((tmp % 65536) / 256) + "." + (tmp % 256); return naechsteAdresse; } public String getObergrenze() { return obergrenze; } public void setObergrenze(String obergrenze) { this.obergrenze = obergrenze; } public String getUntergrenze() { return untergrenze; } public void setUntergrenze(String untergrenze) { this.untergrenze = untergrenze; } public String getDnsserverip() { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (DHCPServer), getDnsserverip()"); if(ownSettings) { return dhcpDNS; } else { String dns; dns = getSystemSoftware().getDNSServer(); if (dns == null || dns.equals("")) { dns = "0.0.0.0"; } return dns; } } public void setDnsserverip(String ip) { this.dhcpDNS = ip; } public String getGatewayip() { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (DHCPServer), getGatewayip()"); if(ownSettings) { return dhcpGateway; } else { String gateway; gateway = getSystemSoftware().getStandardGateway(); if (gateway == null || gateway.equals("")) { gateway = "0.0.0.0"; } return gateway; } } public void setGatewayip(String ip) { this.dhcpGateway = ip; } public String getSubnetzmaske() { return getSystemSoftware().holeNetzmaske(); } public void starten() { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (DHCPServer), starten()"); started = false; ipListe = new LinkedList<IPEintrag>(); letztevergebeneIPm = untergrenze; super.starten(); /* do { started = (socket != null); // klären, ob Socket bereits fertig... if (!started) { try { Thread.sleep(100); } catch (InterruptedException e) {} } } while (!started); Main.debug.println("DHCP server started..."); */ } /** * Diese Methode wird bei der ersten eingehenden DHCP-Anfrage aufgerufen. * Der DHCP-Server verfuegt naemlich nur ueber einen Mitarbeiter, weil es * nur einen Port mit der Gegenstelle "0.0.0.0:68" gibt. */ protected void neuerMitarbeiter(Socket socket) { Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (DHCPServer), neuerMitarbeiter("+socket+")"); DHCPServerMitarbeiter dhcpMitarbeiter; dhcpMitarbeiter = new DHCPServerMitarbeiter(this, socket); dhcpMitarbeiter.starten(); mitarbeiter.add(dhcpMitarbeiter); //Main.debug.println("\tSocket-Zieladresse: " + socket.holeZielIPAdresse() + ":" //+ socket.holeZielPort()); } }