Verantwortlich: Prof. Anja Feldmann, Nils Kammenhuber, Olaf Maennel, Arne L. Wichmann
Wir bemühen uns, in dieser FAQ folgende Kennzeichnung zu verwenden (nur in graphischen Browsern sichtbar):
![]() |
Punkt ist vor kurzem neu hinzugekommen |
![]() |
Punkt wurde vor kurzem wesentlich geändert |
![]() |
Punkt ist schon eine Zeitlang in der FAQ und wurde nicht mehr geändert (...oder einer von uns hat mal wieder vergessen, den Punkt als neu oder geändert zu markieren... ;-) ) |
print-Anweisungen
aus.fork()
angelegt habe, miteinander Daten austauschen können?
Geh' in den Flur des Lehrstuhl Feldmann und frag irgendjemanden der Anwesenden, ob er/sie Dir mit seinem/ihrem Transponder den Praktikumsraum öffnen kann.
Sollte keiner mehr dort sein, bleibt Dir wohl nichts anderes übrig, als z.B. in die "Sunhalle" zugehen und Dich von dort aus remote einzuloggen.
Übrigens soll in den nächsten Tagen das Schloss ausgetauscht werden und dann zumindest tagsüber problemlosen Zugang ermöglichen.
Einfach mit ssh auf irgendeinen der folgenden Rechner einloggen:
viper.net.informatik.tu-muenchen.de slowworm.net.informatik.tu-muenchen.de housesnake.net.informatik.tu-muenchen.de seasnake.net.informatik.tu-muenchen.de adder.net.informatik.tu-muenchen.de cobra.net.informatik.tu-muenchen.de boa.net.informatik.tu-muenchen.de copperhead.net.informatik.tu-muenchen.de [python.net.informatik.tu-muenchen.de]
Bis der password:-Prompt kommt, kann es u.U. eine Zeitlang dauern, also nicht die Geduld verlieren.
Wenn Du einen Laptop hast, kannst Du ihn einfach an eines der freien Ethernetkabel anschließen, die an den Switches auf den Praktikumstischen dranhängen und dann mit Deinem Laptop arbeiten. (Außerdem soll wohl in absehbarer Zeit das Funk-LAN im Gebäude funktionieren.)
Siehe auch Wie greife ich auf die Rechner im Praktikumsraum zu?
Bitte auf prakt-server einloggen und dort mit 'passwd' ändern. Bis das neue Paßwort auf allen Rechnern gültig ist, kann es unter Umständen einige Zeit dauern, also das alte Paßwort nicht sofort vergessen.
Die Lösungen müssen...
Angenommen, mein eigenes Login lautete meinLogin und das meines Teampartners partnerLogin. Die Uhrzeit sei 15:00h (und 27 Sekunden). Dann gebe ich meine Lösung für die 1. Übung folgendermaßen ab:
meinLogin@viper> mkdir meinLogin_partnerLogin
meinLogin@viper> cp loesungsfile1 loesungsfile2 loesungsfile3 usw meinLogin_partnerLogin/
meinLogin@viper> date +%H%M%S
150027
meinLogin@viper> tar -czf meinLogin_partnerLogin_150027.tgz meinLogin_partnerLogin/
meinLogin@viper> cp meinLogin_partnerLogin_150027.tgz ~inetprak/abgabe/1.uebung/
meinLogin@viper> chmod g+r ~inetprak/abgabe/1.uebung/meinLogin_partnerLogin_150027.tgz
meinLogin@viper>
Schon abgegebene Lösungen können bis zur Abgabedeadline upgedatet werden (d.h., man kann z.B. sicherheitshalber z.B. ein paar Tage vor der Deadline schonmal eine halbfertige Lösung abgeben, ohne Nachteile zu erwarten.) Dazu gibt man einfach nochmal eine komplette Lösung wie oben beschrieben mit aktuellem Datum ab.
Siehe auch Welche Sprachen sind erlaubt?
| Befehl | liefert |
|---|---|
| man perl | generelle Übersicht |
| man perlsyn | Syntax von Perl |
| man perlfunc | die in Perl eingebauten Funktionen |
| man perldata | Infos zu den Datentypen von Perl |
| man perlrun | u.A. trickreiche Parameter beim Aufruf von
Perl, die einem viel Schreibarbeit sparen können
(z.B. perl -n -a) |
| ... | ... |
| man perltoc | Übersicht über die anderen Manpages zum Thema Perl |
Sofern nicht genauer spezifiziert, sind folgende Sprachen erlaubt:
grep, sort,
cut,...) benutzen. In diesen Skripten dürfen auch
selbstgeschriebene Perl-, C-, C++ oder Java-Programme aufgerufen werden.
Beispielsweise wäre eine Kombination wiezcat datei.gz | java mein.tolles.Programm | ./meinperlskript | perl -n -e '...(mein Perl-Einzeiler...)' | sort
$CLASSPATH bei Java-Programmen, oder
Umleiten der Ausgabe Deines Programmes in einen Shellbefehl wie
z.B. sort, usw.), dann schreibe einfach ein kurzes
Shellskript, das diese Aufgaben automatisch erledigt.
Für das dritte Aufgabenblatt (und die weiteren Blätter) sind
Java und das Perl-Modul IO::Socket wieder erlaubt.
Beim RFC-Editor.
Angenommen, das Verzeichnis heiße dir. Dann:
Es ist ein mit dem Befehl gzip komprimiertes File. Zum Ausgeben auf Standardausgabe benutze entweder
In Perl kannst Du es recht trickreich auslesen mit
open(DATEIHANDLE, "zcat dateiname|")
so dass das File also beim Auslesen gleich automatisch dekomprimiert wird.
Du brauchst nicht immer ein Programm zu schreiben; Du kannst manchmal wichtige Informationen durch eine einzige Zeile in der Shell herausbekommen.
Benutze hierzu die sogenannten Pipes. Wenn z.B. der Inhalt der Datei zu groß ist, kannst Du die Ausgabe des Dekompressionskommandos in den Pager less pipen, und zwar mit zcat dateiname.gz | less.
Du kannst diese Pipes sogar schachteln, was sie sehr, sehr mächtig
macht. Ein Beispiel:
zcat name.gz | grep "http" | sort -n -r | uniq | head >ausgabe.txt
Wohlgemerkt: Das ist eine einzige Zeile, die man in die Shell eintippt. Diese recht komplexe Aufgabe wurde also komplett ohne irgendein Programm oder Skript gelöst.
zcat logfile.gz | perl -e 'while(<>){s/\s+/ /g;@l=split;@f=split("/",$l[3]);$p{$f[1]}++;}foreach $i (sort keys %p) {print "$i:$p{$i}\n";}'
Natürlich ist dieser Einzeiler schrecklich unübersichtlich und somit quasi unlesbar. Daher nochmal in kommentierter Form:
| zcat logfile.gz | Ausgabe des Logfiles auf die Standardausgabe (vgl. oben) |
| | | Umleiten von Standardausgabe auf Standardeingabe |
| perl -e | Aufruf von Perl mit der restlichen Kommandozeile als auszuführendes Skript |
| while() {} | While-Schleife |
| <> | Zeilenweises Lesen der Standardeingabe (in Variable
$_) |
| while(<>) {} | Durchlaufen aller Zeilen der Standardeingabe |
| s/\s+/ /g; | globales Ersetzen von mehreren Spaces durch ein Space |
| @l=split; | Aufspalten einer Zeile in ein Array namens l
mit dem Space als Trennzeichen |
| @f=split ("/", $l[3]); | Aufspalten des 4ten Wortes (Trennzeichen ist "/") |
| $p{$f[1]}; | Benutzt 2ten Eintrag von f als Hash in Hashtabelle
p |
| $p{$f[1]}++; | Automatisches Erhöhen des Wertes in der Hashtabelle |
| keys %p | Liste aller Hashes der Hashtabelle p
Hash := Hashkey |
| sort keys %p | Sortiere die obige Liste von Hashes |
| foreach $i (sort keys %p) {} | Für jeden Eintrag (der dann bei jedem Durchlauf
jeweils in der Variable i gespeichert wird)
in der sortierten Liste von Hashes |
| print "$i:$p{$i}\n"; | Gib den Wert von i und den Wert des Hashes
aus |
Und wie Du siehst, kann man Perl also auch "on-the-fly" benutzen, also ohne eine extra Skriptdatei anzulegen. Das ist natürlich recht unübersichtlich, also muss man solche "Einzeiler" dann gut dokumentieren.
Mehr Infos, wie man Perl trickreich aufrufen kann und sich dadurch eine Menge Schreibarbeit sparen kann, findest Du übrigens auf der Manpage man perlrun.
Damit ist gemeint, daß alle Werte durch ihren Logarithmus ersetzt werden sollen.
Für Netzwerkverhältnisse ist das ein kleines Logfile. Ihr werdet im Praktikum daher auch lernen, wie man mit solch großen Datenmengen umgeht (z.B. was man tut, wenn das zu analysierende File gar nicht komplett ins RAM reinpasst...).
Es ist also nicht bloße Schikane, sondern durchaus Praxisorientierung.
Wer ein Internet-Praktikum macht, der sollte auch einmal "von Hand" mit Sockets umgegangen sein, d.h. die entsprechenden Systemaufrufe verwendet haben. Java oder auch das Perl-Modul IO::Socket abstrahieren den Umgang mit Sockets aber zu stark von der Systemebene. Daher sind sie für das zweite Aufgabenblatt ausnahmsweise nicht erlaubt.
Du musst nicht, wir empfehlen es Dir nur. Einer der zahlreichen positiven Aspekte von Perl ist dabei auch der, dass man schon mit nur wenigen Kenntnissen der Sprache sehr weit kommt. Perls Motto ist there's more than one way to do it, was man auch so interpretieren kann, dass man auch schon als Anfänger in Perl "alles" machen kann, nur dass eben ein Perl-Profi die Sachen z.T. schneller und eleganter hinbekommt.
In diesem Praktikum wirst Du Perl insbesondere gebrauchen können für:
Auch im Alltag wird Dir Perl später gute Dienste leisten. Übrigens sind viele sog. CGI-Skripte im WWW-Bereich in Perl geschrieben.
Lies' Dir unsere "Schnellinfo-Seite" über TCP und/oder die Folien des Vortrags über Socketprogrammierung durch.
Vermutlich lässt im Moment gerade ein anderer Praktikumsteilnehmer ein Programm auf dem gleichen Port laufen. => Versuch' Dein Glück auf einem anderen Rechner, oder nimm eine andere Portnummer.
Wenn Du die Portnummer aus diesem Grund änderst, dann schreibe dies ins README-File hinein.
Stelle bitte zunächst sicher, dass in der Shellvariable
$PATH auch das Verzeichnis /usr/local/bin
(oder alternativ /usr/local/java oder
/usr/local/j2sdk1.3.1) enthalten ist. Ansonsten kann die
Shell java natürlich nicht ausführen.
Sollte das der Fall sein und Java aber trotzdem nicht ausführbar
sein, dann
schreib uns bitte eine Mail, damit wir es auf dem entsprechenden Rechner
nachinstallieren können. (
)
Solange wir uns nicht gerührt haben, kannst Du Dich (z.B. mit dem Kommando ssh) auf einem anderen der Schlangenrechner einloggen, auf dem Java installiert ist.
Das TO_SERVER-Filehandle des Sockets wird nicht automatisch
geflusht, wenn man mit print() hineinschreibt.
Verwende daher einfach send() statt print(),
dann brauchst Du Dich gar nicht um das Flushing zu kümmern.
Du kannst zwar
das Autoflush mit $|=1 (möglichst zu Beginn des
Programms) einschalten, aber das löst die Probleme nicht immer.
print-Anweisungen
aus.Dieses Problem hat die gleichen Ursachen wie das
Problem, dass die Daten nicht beim Server ankommen
und kann daher auch genauso umgangen werden, nämlich mit
$| = 1; am Programmanfang.
Der Server soll die auf dem Blatt spezifizierten Meldungen (wie z.B. 200 1298798612 Bytes oder 551 Current directory only) an den Client senden. (Logisch -- der Client muss ja irgendwie wissen, woran er ist.)
Es wäre sinnvoll, wenn der Client diese Meldung nicht einfach nur schweigend entgegennehmen, sondern daraus eine für den menschlichen Benutzer verständliche Fehlermeldung generieren würde.
Siehe auch die nächste Frage.
Der get-Befehl des Clients ist logischerweise ein String, den der Client zum Server schickt, um ihn anzuweisen, dass der Server dem Client das angegebene File schicken soll.
Siehe auch die vorige Frage.
Bedenke, dass beim Auslesen via <FILEHANDLE>
immer eine ganze Zeile gelesen wird. Aber solange kein \n
kommt...
Anscheinend gibt es Probleme bei der Verwendung von receive()
auf den Schlangenrechnern: Beim Auslesen von Daten aus einem Socket mit
mit receive() kommen einfach keine Daten an, obwohl der Code
auf anderen (Debian-Linux-) Rechnern einwandfrei funktioniert.
Die Ursache dieses wirklich sehr seltsamen
Phänomens ist uns leider auch nicht bekannt. Die einzige Abhilfe
scheint zu sein, stattdessen read() oder
<SOCKET> zu verwenden. :-(
Die Antwort hierauf folgt unmittelbar aus dem vierten Aufzählungspunkt auf dem Aufgabenblatt... (und zwar insbesondere aus dem zweiten Teilsatz)
In der Vorlesung wurde gesagt, wie die Sache bei anderen Filesharing-Diensten läuft (nämlich häufig so, dass die Enden der Kette die Datei dann mit einer direkten TCP-Verbindung zwischen sich übertragen). Das verursacht zwar weniger Traffic, ist andererseits aber nicht so gut anonymisierbar.
Wenn Du fork() verwendest, dann sollte der Prozess, der
von STDIN (sprich: aus der Tastatur) einliest, immer der
Vater sein.
Normalerweise wird zwar STDIN an den Kindprozess weitervererbt;
allerdings kann es sein, dass Du beim Auslesen von STDIN von
verschiedenen Kindprozessen mit seltsamen Signalen wie z.B.
SIGTTYIN o.ä. hantieren musst, was ziemlich eklig ist.
(Und wenn Du es nicht tust, wird der entsprechende Kindprozess einfach
blockieren.)
Daher ist es einfacher, den Vaterprozess für STDIN
(also Tastatureingaben) verantwortlich zu machen.
open-Befehl erlaubt es, nicht einfach nur aus einer Datei
zu lesen, sondern dabei auch noch Kommandos auszuführen.
Vermutlich willst Du also in Dein Programm etwas wie
open(DATEI, "zcat dump.gz | tcpdump-patched -r - |");reinschreiben. Wichtig: Das abschließende
|-Symbol
am Ende der Befehlspipe nicht vergessen.- als Dateinamensangabe
sagt dem tcpdump, dass es nicht aus einer Datei (-r)
lesen soll, sondern stattdessen von der Standardeingabe. (Ein -
statt eines Dateinamens hat übrigens auch bei vielen anderen
Unixprogrammen diese Semantik.)
Unter Unixdateisystemen gibt es spezielle Dateien, die sog.
FIFOs (oder named pipes). Eine solche FIFO
(von "first in, first out") ist, salopp formuliert, ein
|-Shellpipe, der man einen Dateinamen gegeben hat.
Das heißt: Wenn ein Prozess Daten dort reinschreibt, dann wird er
so lange blockiert, bis ein anderer Prozess Daten dort herausliest --
denn die Daten werden nicht auf die Platte geschrieben, sondern
(ähnlich wie z.B. in einer TCP-Verbindung) als Byte-Strom von einem
Prozess zu einem anderen übertragen.
Man legt eine solche Fifo mit dem Kommando
mkfifo dateiname an. Anschließend lässt
man z.B. den Befehl
zcat dump.gz | tcpdump ... > dateiname &im Hintergrund laufen (deswegen auch das
&-Symbol am Ende der Zeile) und liest dann einfach
aus der Fifo dateiname wie aus einer ganz normalen Datei.
Nur mit dem Unterschied, dass man in der Fifo nicht zurückspringen
kann o.ä.; sie realisiert zwar ähnlich wie eine TCP-Verbindung
einen Byte-Stream, der allerdings im Gegensatz zur TCP-Verbindung
unidirektional ist.
Falls Du das File irgendwie sortieren willst, dann kannst Du
das auch dem (recht schnellen) Unixbefehl sort
überlassen. Vermutlich wirst Du etwas wie
... | sort -n -t '|' +4711 |...in irgendeine Kommando-Pipeline einbauen.
'|' das Argument
zu -t ist und daher mit ' gequotet wird.
Ansonsten würde es ja von der Shell als Pipe-Operator interpretiert.Klassische Unix-Befehle, die immer mal wieder in langen Pipes auftauchen, sind z.B.
cat datei | grep 'bla.*blubb'alle Zeilen, in denen irgendwo bla, gefolgt von blubb vorkommt.
' (Hochkommata) eingefasst werden, da ansonsten die Shell
versucht,
Sonderzeichen wie *, [, ! usw. als
Dateinamen-Pattern zu interpretieren.
In Java ist es schwierig, non-blocking I/O (also wie z.B. mit
select() in Perl/C/C++) zu realisieren. Das hat zur Folge,
dass das Java-Template sozusagen zwei voneinander unabhängige
lines of execution hat, was die Implementierung der
Zustandsübergangsmaschine deutlich anders als in Perl/C/C++
gestaltet:
KeyListener
auf ein leeres Fenster angesetzt wird. Die Java Virtual Machine führt
also automatisch bei jedem Tastendruck in dieses Fenster letztendlich die
Routine reagiere_auf_tastendruck() aus, die Du noch mit Fleisch
füllen musst.while(1<27)) durchlaufen.Die Timeout-Routinen kannst Du ignorieren, da sie erst für das nächste Übungsblatt (auf dem RDT 3.0 implementiert werden soll) relevant sind. Außerdem bist Du nicht dazu verpflichtet, unser Template zu übernehmen; Du kannst auch gerne einen eigenen, völlig anderen Ansatz wählen. Und außerdem haben wir mittlerweile die etwas verwirrende Bemerkung über die Green Threads angepasst.
Verwende die pack()-Funktion. (Siehe z.B.
man perlfunc oder perldoc -f pack)
Am einfachsten ist es vermutlich, die Daten iterativ zu
pack()en; beginne also zuerst mit dem ersten Headerelement
und hänge dann die anderen an:
$data .= pack(...bla...); $data .= pack(...blubb...); ... $data .= pack(...body...);
Zum Empfang kannst Du dann entsprechend unpack()
verwenden.
Hierzu ein paar Tips:
zcat,
tcpdump-patched und --falls Du es benötigst--
sort.Damit der RDT-Header einigermaßen einfach zu lesen ist, sind die Sequenzummern/ACK-Nummern 16 Bit groß. Allerdings nehmen sie, wie im Buch spezifiziert, nur die Werte 0 und 1 an.
Kaum zu glauben, aber wahr: Für das uralte Problem, wie man einen 16-bit-Wert (oder einen 32-bit-Wert) in zwei Bytes (bzw. vier Bytes) hintereinander im Speicher ablegt, existieren zwei verschiedene Standards, nämlich big endian und little endian.
Mehr Infos über endianness findest Du z.B. hier:
Das sollte jetzt gefixt sein. Großes Sorry!
Nein. RDT 3.0 hat ja das gleiche Paketformat, nur dass hier noch zusätzlich Packet Losses auftreten können. D.h., wenn Du RDT 3.0 korrekt implementiert hast, dann hast Du damit schon automatisch RDT 2.2 erschlagen.
Gar nichts, denn das sollte bei RDT nicht vorkommen. :-) (Steht auch irgendwo relativ versteckt im Buch drin.)
In der Realität kommt sowas natürlich durchaus vor (wenn auch recht selten). Du kannst Dir ja mal kurz überlegen...
Du kannst dafür z.B. die Tools netcat und hexdump verwenden, z.B. so:
netcat -u -l -p 4812 | hexdump -...(Die Option -u bringt netcat dazu, standardmäßig UDP statt TCP zu verwenden.)
fork()
angelegt habe, miteinander Daten austauschen können?
Da fork() verschiedene Prozesse anlegt und unter Unix
Prozesse getrennte Speicherbereiche haben, können sie nicht einfach
auf die gleichen Variablen zugreifen.
Für dieses Problem gibt es mehrere Herangehensweisen:
fork()) evtl. nur einen einzigen
verwendest, mit dem Du dann allerdings mit Hilfe der
select()-Funktion sog. non-blocking I/O
(nicht-blockierende Ein-/Ausgabe) machst.
Das kriegst Du auf Unixrechnern normalerweise ganz einfach durch Anschauen der
Datei /etc/services heraus. Allerdings haben manche Protokolle
seltsame Namen; z.B. taucht DNS nicht etwa als dns,
sondern als domain auf.
Wir haben jetzt sowohl /usr/sbin/tcpdump als auch /usr/local/bin/tcpdump-patched mit einem s-Bit versehen. Das heißt im Klartext:
Sofern man an der Konsole sitzt, braucht man einfach nur /usr/sbin/tcpdump ohne Parameter einzugeben. (Ist man allerdings per ssh eingeloggt, dann würde beim ersten gesnifften Netzwerkpaket von tcpdump eine Ausgabezeile generiert und per ssh als mindestens ein weiteres Ethernetpaket zum Anwender geschickt, was nat&uum;rlich dazu führt, dass tcpdump ein dieses zweite Paket ebenfalls snifft und ausgibt, usw. usw. -- also Vorsicht)
Bitte benutzt tcpdump nicht zur Spionage anderer Leute, zum Hacken von Passwörtern oder zu ähnlichen Dingen, sondern lediglich zu Test, Debugging und Untersuchung von Programmen und Protokollen!
Du musst Dir in diesem Fall merken, dass der Client noch eine weitere Anfrage stellen muss. Sobald die schon bestehende TCP-Verbindung abgebaut ist (Aufgabenteil f) bzw. sobald in der bestehenden TCP-Verbindung alle Daten übertragen wurden (Aufgabenteil g), baust Du eine neue TCP-Verbindung auf (Aufgabenteil f) bzw. stellst gleich die neue GET-Anfrage (Teil g).
Die Dauer, bis die Nutzdaten am Client angekommen sind, bestimmt sich hier sinnvollerweise als das Zeitintervall
Einige kryptografische Protokolle und Dokumentation dazu:
ssh
PGP
SSL
IPsec
Einfach nur als Text senden; also genauso, wie es auf dem Aufgabenblatt steht. Keine komplizierten Header aus irgendwelchen RFCs verwenden.
Der Router hat eine Konfiguration und anhand dieser Konfiguration weiß er, von welchen Peers (Router/Port) er Nachrichten empfangen kann und von welchen nicht. Um Fehler, Missbrauch, etc. zu reduzieren wird beim BGP-Handshake als erstes die AS-ID ausgetauscht.
Sprich: ihr braucht für Euren "Software-Router" auch so eine Konfigurationsmöglichkeit... die könnte z.B. so aussehen:
router bgp 65111 bgp router-id 131.159.14.22 neighbor 131.159.14.23 remote-as 65111 neighbor 131.159.14.24 remote-as 65112
Dann baut Router 131.159.14.22 mit Router 131.159.14.23 und
131.159.14.24 eine BGP-Verbindung auf. Bei BGP ist der Port
normalerweise immer 179. Ihr solltet natürlich wegen Aufgabenteil (c)
die remote-Ports irgendwo in der Konfiguration mit spezifizieren.
Vermutlich ist es zu Anfang am Einfachsten, die Konfiguration erstmal
hart in den Code reinzuhacken.
Was in der Aufgabenstellung spezifiziert ist, ist wie die Router Nachrichten ausstauschen...