/*
** 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.vermittlungsschicht;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.StringTokenizer;
import java.util.NoSuchElementException;
import filius.Main;
import filius.exception.VerbindungsException;
import filius.hardware.NetzwerkInterface;
import filius.hardware.knoten.InternetKnoten;
import filius.rahmenprogramm.I18n;
import filius.software.netzzugangsschicht.EthernetFrame;
import filius.software.system.InternetKnotenBetriebssystem;
import filius.software.transportschicht.TcpSegment;
import filius.software.transportschicht.UdpSegment;
/**
* Diese Klasse implementiert die Funktionalitaet des Internet-Protokolls. Das
* heisst, dass Pakete richtig weitergeleitet werden und eingehende Segmente an
* die Transportschicht weitergeleitet werden.
*/
public class IP extends VermittlungsProtokoll implements I18n {
private static final long serialVersionUID = 1L;
/** String-Konstante fuer die IP-Adresse Localhost (127.0.0.1) */
public static final String LOCALHOST = "127.0.0.1";
/** Puffer fuer eingehende IP-Pakete fuer TCP */
private LinkedList<IpPaket> ipPaketListeTCP = new LinkedList<IpPaket>();
/** Puffer fuer eingehende IP-Pakete fuer UDP */
private LinkedList<IpPaket> ipPaketListeUDP = new LinkedList<IpPaket>();
/**
* Der Thread zur Ueberwachung des IP-Pakete-Puffers der Ethernet-Schicht
*/
private IPThread thread;
private LinkedList<IpPaket> paketListe = new LinkedList<IpPaket>();
/**
* Konstruktor zur Initialisierung der Systemsoftware
*
* @param systemsoftware
*/
public IP(InternetKnotenBetriebssystem systemsoftware) {
super(systemsoftware);
Main.debug.println("INVOKED-2 ("+this.hashCode()+") "+getClass()+" (IP), constr: IP("+systemsoftware+")");
}
public static long inetAton(String ipStr) {
long ipAddr = 0;
int octet;
StringTokenizer ipToken = new StringTokenizer(ipStr, ".");
try {
for (int i = 0; i < 4; i++) {
try {
octet = Integer.parseInt(ipToken.nextToken());
} catch (java.lang.NumberFormatException e) {
return -1;
}
if (0 > octet || octet > 255) {
return -1;
}
ipAddr += octet;
if (i < 3) {
ipAddr <<= 8;
}
}
} catch (NoSuchElementException e) {
return -1;
}
if (ipToken.hasMoreTokens()) {
return -1;
}
return ipAddr;
}
public static String inetNtoa(long ipAddr) {
String ipStr = "";
ipStr = "." + (ipAddr & 255);
ipAddr >>= 8;
ipStr = "." + (ipAddr & 255) + ipStr;
ipAddr >>= 8;
ipStr = "." + (ipAddr & 255) + ipStr;
ipAddr >>= 8;
ipStr = (ipAddr & 255) + ipStr;
return ipStr;
}
public static String ipCheck(String ip) {
long ipAddr = inetAton(ip);
if (ipAddr == -1) {
return null;
}
return inetNtoa(ipAddr);
}
/** Hilfsmethode zum Versenden eines Broadcast-Pakets */
private void sendeBroadcast(IpPaket ipPaket) {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (IP), sendeBroadcast("+ipPaket.toString()+")");
InternetKnoten knoten;
NetzwerkInterface nic;
ListIterator it;
InternetKnotenBetriebssystem bs;
// Damit Broadcast-Pakete nicht in Zyklen gesendet werden,
// wird das Feld Time-to-Live (TTL) auf 1 gesetzt. Damit
// wird es von keinem Knoten weitergeschickt
ipPaket.setTtl(1);
knoten = (InternetKnoten) holeSystemSoftware().getKnoten();
bs = (InternetKnotenBetriebssystem) holeSystemSoftware();
it = knoten.getNetzwerkInterfaces().listIterator();
while (it.hasNext()) {
nic = (NetzwerkInterface) it.next();
// Broadcast-Nachrichten werden nur im lokalen Rechnernetz
// verschickt
if (gleichesRechnernetz(ipPaket.getSender(), nic.getIp(), nic
.getSubnetzMaske())) {
bs.holeEthernet().senden(ipPaket, nic.getMac(),
"FF:FF:FF:FF:FF:FF", EthernetFrame.IP);
}
}
}
/**
* Hilfsmethode fuer Pakete, die fuer den eigenen Knoten bestimmt sind
*
* @param segment
* das zu verarbeitende Segment
*/
private void benachrichtigeTransportschicht(IpPaket paket) {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (IP), benachrichtigeTransportschicht("+paket.toString()+")");
if (paket.getSegment() instanceof TcpSegment) {
synchronized (ipPaketListeTCP) {
ipPaketListeTCP.add(paket);
ipPaketListeTCP.notify();
}
}
else if (paket.getSegment() instanceof UdpSegment) {
synchronized (ipPaketListeUDP) {
ipPaketListeUDP.add(paket);
ipPaketListeUDP.notify();
}
}
}
/**
* Hilfsmethode zum versenden eines Unicast-Pakets. Hier wird unterschieden,
* ob sich die Ziel-IP-Adresse im lokalen Rechnernetz befindet oder ueber
* das Gateway verschickt werden muss.
*
* @param paket
* das zu versendende IP-Paket
*/
private void sendeUnicast(IpPaket paket) {
sendeUnicast(paket, false);
}
private void sendeUnicast(IpPaket paket, boolean setzeSender) {
if (this.isLocal(paket.getEmpfaenger())) {
// Paket ist an diesen Rechner gerichtet
if (setzeSender) {
paket.setSender("127.0.0.1");
}
benachrichtigeTransportschicht(paket);
return;
}
InternetKnotenBetriebssystem bs =
(InternetKnotenBetriebssystem) holeSystemSoftware();
String[] route = bs.getWeiterleitungstabelle()
.holeWeiterleitungsZiele(paket.getEmpfaenger());
if (route == null) {
// Es wurde keine Route gefunden, ueber die das Paket versendet
// werden koennte.
// Es muss ein ICMP Destination Unreachable: Network Unreachable
// (3/0) zurueckgesendet werden:
if (!setzeSender) {
bs.holeICMP().sendeICMP(3, 0, paket.getSender());
}
return;
}
String gateway = route[0];
String schnittstelle = route[1];
InternetKnoten knoten = (InternetKnoten) bs.getKnoten();
NetzwerkInterface nic = knoten.getNetzwerkInterfaceByIp(schnittstelle);
String netzmaske = nic.getSubnetzMaske();
if (setzeSender) {
paket.setSender(schnittstelle);
}
if (gleichesRechnernetz(paket.getEmpfaenger(), schnittstelle, netzmaske)) {
// adressierter Knoten befindet sich im lokalen Rechnernetz
sendeUnicastLokal(paket, paket.getEmpfaenger(), nic);
} else {
// adressierter Knoten ist ueber Gateway zu erreichen
sendeUnicastLokal(paket, gateway, nic);
}
}
private void sendeUnicastLokal(IpPaket paket, String ziel, NetzwerkInterface nic) {
InternetKnotenBetriebssystem bs =
(InternetKnotenBetriebssystem)holeSystemSoftware();
String zielMacAdresse = bs.holeARP().holeARPTabellenEintrag(ziel);
if (zielMacAdresse != null) {
// MAC-Adresse konnte bestimmt werden
bs.holeEthernet().senden(paket, nic.getMac(), zielMacAdresse,
EthernetFrame.IP);
} else {
// Es konnte keine MAC-Adresse bestimmt werden.
// Es muss ein ICMP Destination Unreachable: Host Unreachable
// (3/1) zurueckgesendet werden:
bs.holeICMP().sendeICMP(3, 1, paket.getSender());
}
}
/**
* Methode zum Versenden eines IP-Pakets. Zunaechst wird die
* Sender-IP-Adresse an Hand der Weiterleitungstabelle bestimmt (die
* Schnittstelle, ueber die das Paket versendet wird). Dann wird das Paket
* erzeugt und schliesslich an die Methode weiterleitenPaket() uebergeben.
*
* @param zielIp
* Gibt die Ziel-IP-Adresse an
* @param protokoll
* Der Parameter Protokoll gibt die Protokollnummer an. Dabei
* steht die Nummer 6 fuer das Protokoll TCP
* @param segment -
* Enthaellt das erzeugte Segment mit den Nutzdaten.
* @throws VerbindungsException
*/
public void senden(String zielIp, int protokoll, int ttl, Object segment) {
senden(zielIp, null, protokoll, ttl, segment);
}
public void senden(String zielIp, String quellIp, int protokoll, int ttl, Object segment) {
IpPaket paket = new IpPaket();
paket.setEmpfaenger(zielIp);
paket.setProtocol(protokoll);
paket.setTtl(ttl);
paket.setSegment(segment);
if (zielIp.equals("255.255.255.255")) {
if (quellIp == null) {
quellIp = ((InternetKnotenBetriebssystem)
holeSystemSoftware()).holeIPAdresse();
}
paket.setSender(quellIp);
sendeBroadcast(paket);
} else {
sendeUnicast(paket, true);
}
}
/**
* Methode zum Weiterleiten eines IP-Pakets. Zunaechst wird geprueft, ob das
* Feld Time-to-Live-Feld (TTL) noch nicht abgelaufen ist (d. h. TTL
* groesser 0). Wenn diese Bedingung erfuellt ist, wird zunaechst geprueft,
* ob es sich um einen Broadcast handelt. Ein solches Paket wird nicht
* weitergeleitet sondern nur an die Transportschicht weitergegeben. Sonst
* wird die Weiterleitungstabelle nach einem passenden Eintrag abgefragt.
* Anhand des zurueckgegebenen Eintrags wird geprueft, ob das Paket fuer den
* eigenen Rechner ist oder ob ein Unicast-Paket verschickt werden muss.
*
* @param ipPaket
* das zu versendende IP-Paket
*/
public void weiterleitenPaket(IpPaket paket) {
if (paket.getEmpfaenger().equals("255.255.255.255")) {
// Broadcast, darf nicht weitergeleitet werden.
// Lokal verarbeiten:
benachrichtigeTransportschicht(paket);
} else if (paket.getTtl() <= 0) {
// TTL ist abgelaufen.
// (wird in IPThread.verarbeiteDatenEinheit()
// dekrementiert, bevor diese Funktion aufgerufen
// wird)
// ICMP Timeout Expired In Transit (11/0) zuruecksenden:
InternetKnotenBetriebssystem bs =
(InternetKnotenBetriebssystem) holeSystemSoftware();
bs.holeICMP().sendeICMP(11, 0, paket.getSender());
} else {
// TTL ist nicht abgelaufen.
// Paket weiterleiten:
sendeUnicast(paket);
}
}
/**
* Methode fuer den Zugriff auf den Puffer fuer eingehende Segmente
*
* @param protokollTyp
* der Protokolltyp UDP oder TCP (Konstanten der Klasse
* TransportProtokoll)
* @return die Liste mit Segmenten fuer UDP- oder TCP-Segmente
*/
public LinkedList<IpPaket> holePaketListe(int protokollTyp) {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (IP), holePaketListe("+protokollTyp+")");
if (protokollTyp == IpPaket.TCP)
return ipPaketListeTCP;
else if (protokollTyp == IpPaket.UDP)
return ipPaketListeUDP;
else
return null;
}
/**
* Hier wird der Thread zur Ueberwachung des Puffers fuer eingehende
* IP-Pakete der Netzzugangsschicht
*/
public void starten() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (IP), starten()");
thread = new IPThread(this);
thread.starten();
}
/** Der Thread zur Ueberwachung des IP-Pakete-Puffers */
public void beenden() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (IP), beenden()");
thread.beenden();
}
public IPThread getIPThread() {
return thread;
}
}