Komponentenkleber
Was geht nun hinter den Kulissen in den beiden POE-Komponenten vor? Abbildung 1 zeigt das Diagramm mit den Server- und Client-Komponenten sowie den jeweils genutzten Portnummern. Der im Port-Forwarder auf Port 25 lauschende TCP-Server spannt pro Client jeweils eine TCP-Client-Komponente auf, um sie mit dem Tunnel zu verbinden.
Abbildung 1: Der Mailclient unterhält sich mit dem Port 25 des Forwarders, dessen Client wiederum mit dem Tunnel kommuniziert.
Als Parameter erwartet die Klasse den Port »port_from« (auf dem der Server lauscht), »port_to« (den Tunnelport) sowie zwei Callback-Routinen. Die Komponente springt die unter »port_bound« abgelegte Subroutinen-Referenz an, sobald der Server sich mit Port 25 verbunden hat und damit keine Root-Rechte mehr benötigt.
Beim Aufgeben der Root-Rechte ist darauf zu achten, dass dies in der richtigen Reihenfolge für effektive und reale User-IDs geschieht, sonst kann der Daemon hinterher die Root-Rechte wiederherstellen [2]. Wären hier mehrere Threads gleichzeitig zugange, müsste »PoCoTimedProcess« intern aufpassen, dass keine Race Condition den Tunnel zweimal startet. Aber in der von POE bereitgestellten Ein-Prozess-ein-Thread-Umgebung genügt ein einfacher Variablencheck ohne jedes Locking - robust, einfach hinzuschreiben und später leicht zu begreifen!
Der zweite Callback des Forwarders, »client_connect«, wird jedes Mal angesprungen, wenn ein Mailclient an Port 25 andockt. Die im Callback ausgeführte Methode »launch()« der Komponente »PoCoTimedProcess« fährt daraufhin den Tunnel hoch, falls dieser noch nicht besteht. Intern stellt »PoCoForwarder« jeder Clientverbindung eine eigene POE-Komponente vom Typ PoCo::Client::TCP zur Seite, die mit dem Tunnelport Kontakt aufnimmt. Während also PoCo::Server::TCP beliebig viele Clients betreut, nutzt es für jede Clientverbindung eine separate PoCo::Client::TCP-Komponente.
Closures: Verwirrend schön
Zeile 23 in Listing 2 zeigt, wie die Komponente den Callback »port_bound« implementiert. Der in Zeile 17 erzeugte POE-TCP-Server springt nach erfolgreichem Start den Zustand »Started« an. Dort kramt »PoCoForwarder« die von »minimail« definierte Subroutinen-Referenz aus dem Objekthash »$self« und verzweigt dorthin. Der in Minimail definierte Callback-Code tut den Rest.
|
Listing 2: |
|---|
01 ###########################################
02 package PoCoForwarder;
03 use strict;
04 use Log::Log4perl qw(:easy);
05 use POE::Component::Server::TCP;
06 use POE::Component::Client::TCP;
07 use POE;
08
09 ###########################################
10 sub new {
11 ###########################################
12 my($class, %options) = @_;
13
14 my $self = { %options };
15
16 my $server_session =
17 POE::Component::Server::TCP->new(
18 ClientArgs => [$self],
19 Port => $self->{port_from},
20 ClientConnected => &client_connect,
21 ClientInput => &client_request,
22 Started => sub {
23 $self->{port_bound}->(@_) if
24 defined $self->{port_bound};
25 },
26 );
27
28 return bless $self, $class;
29 }
30
31 ###########################################
32 sub client_connect {
33 ###########################################
34 my ($kernel, $heap, $session, $self) =
35 @_[ KERNEL, HEAP, SESSION, ARG0];
36
37 $self->{client_connect}->(@_) if
38 defined $self->{client_connect};
39
40 my $client_session =
41 POE::Component::Client::TCP->new(
42 RemoteAddress => "localhost",
43 RemotePort => $self->{port_to},
44 ServerInput => sub {
45 my $input = $_[ARG0];
46 # $heap is the tcpserver's (!) heap
47 $heap->{client}->put( $_[ARG0] );
48 },
49 Connected => sub {
50 $_[HEAP]->{connected} = 1; },
51 Disconnected => sub {
52 $kernel->post( $session, "shutdown" );
53 },
54 ConnectError => sub {
55 $_[HEAP]->{connected} = 0;
56 $kernel->delay('reconnect', 1);
57 },
58 ServerError => sub {
59 ERROR $_[ARG0] if $_[ARG1];
60 $kernel->post( $session, "shutdown" );
61 },
62 );
63
64 $heap->{client_heap} = $kernel->
65 ID_id_to_session( $client_session )->
66 get_heap();
67 }
68
69 ###########################################
70 sub client_request {
71 ###########################################
72 my ($kernel, $heap, $request) =
73 @_[ KERNEL, HEAP, ARG0 ];
74
75 return if # tunnel not up yet, discard
76 ! $heap->{client_heap}->{connected};
77
78 $heap->{client_heap}->
79 {server}->put( $request );
80 }
81
82 1;
|
Zu beachten ist, dass »$self« sich gar nicht im Scope des dem Zustand »Started« zugewiesenen Handlers befindet. Vielmehr stammt sie aus dem Konstruktor »new()« der Klasse »PoCoForwarder«, aber die Subroutine verwandelt sich dadurch in eine so genannte Closure, die die lexikalische Variable »$self« umschließt und die daher auch nach dem Verlassen des Konstruktor-Scope (aber nur innerhalb des Callback) gültig bleibt.
Andererseits sorgt der Parameter »Client-Args« in Zeile 18 dafür, dass die Serverkomponente den Objekthash »$self« auch als Argument »ARG0« mitliefert, falls sie die Callback-Funktion »client_connect()« anspringt. Dort ruft Zeile 37 den von Minimail gesetzten Callback »client_connect()« auf, der den Tunnel hochfährt. Nun ergibt sich allerdings ein Timingproblem, denn es ist schwer vorherzusagen, wann der Tunnel steht. Deswegen versucht der Client eventuell sich mit einem Port zu verbinden, auf dem niemand lauscht.
Doch das ist kein Problem, denn in diesem Fall springt der TCP-Client den Zustand »ConnectError« ab Zeile 54 an, der mit der POE-Kernelfunktion »delay()« einfach einen Reconnect-Event für eine Sekunde später in POEs Terminkalender einträgt. Dieses Spielchen setzt sich eventuell ein paar Runden fort, doch irgendwann steht der Tunnel, der TCP-Client verbindet sich erfolgreich mit dem nun arbeitenden Port und wechselt deshalb in den Zustand »Connected« (ab Zeile 49).
Falls Minimail ein Kommando sendet, verzweigt der TCP-Server zum Zustand »client_request« und damit zum gleichnamigen Handler ab Zeile 70. Der prüft, ob der Tunnel bereits steht, und verwirft den Client-Text, falls die Verbindung down ist. Im SMTP-Protokoll meldet sich der Server zuerst mit einer Grußmeldung - deswegen wird ein wohlerzogener Client nicht losplappern, bevor der Tunnel steht. Bei anderen Protokollen (zum Beispiel HTTP) wäre dies anders, in diesem Fall müsste die Forwarder-Komponente die Clientkommandos puffern, bis die Verbindung steht, und sie dann im Auftrag des Clients gebündelt an den Server weiterleiten.
Ist der Tunnel hingegen betriebsbereit, wurde vorher im Zustand »Connected« die Heap-Variable »connected« auf 1 gesetzt. Um die Nachricht an den Tunnel weiterzuleiten, kramt Zeile 78 aus dem in »client_heap« zwischengespeicherten Heap des TCP-Clients die Tunnelreferenz hervor und schickt ihr mit »put()« den eingegangenen Text. An dieser Stelle ist allerdings zu beachten, dass die Funktion »client_request()« ein Callback der Serversession ist, die vom Client, der in einer anderen Session arbeitet, und seinem Heap nichts weiß. Die Heap-Variable »client_heap« in der Serversession löst das Problem.
Kommen jetzt Nachrichten aus dem Tunnel zurück, dann wechselt der TCP-Client in den Zustand »ServerInput« ab Zeile 43, der daraufhin seinerseits den Text mit der im Heap gespeicherten Clientreferenz und der Methode »put()« an Minimail zurückgibt.
Für den Fall, dass Minimail sich von dem TCP-Server abkoppeln sollte, springt dieser daraufhin in den Zustand »Disconnected«. Das wiederum bewirkt, dass der Handler dort einen Shutdown-Event an die laufende Session schickt, was die Client-Server-Verbindung schließlich trennt. Die Komponente »PoCoTimedProcess.pm« in Listing 3 hingegen kümmert sich um nichts anderes als um den Auf- und Abbau des Tunnels.
|
Listing 3: |
|---|
001 ###########################################
002 package PoCoTimedProcess;
003 use strict;
004 use warnings;
005 use POE;
006 use POE::Wheel::Run;
007 use Log::Log4perl qw(:easy);
008
009 ###########################################
010 sub new {
011 ###########################################
012 my($class, %options) = @_;
013
014 my $self = { %options };
015 bless $self, $class;
016 }
017
018 ###########################################
019 sub launch {
020 ###########################################
021 my($self) = @_;
022
023 $poe_kernel->post($self->{session},'up');
024 }
025
026 ###########################################
027 sub spawn {
028 ###########################################
029 my($self) = @_;
030
031 $self->{session} =
032 POE::Session->create(
033 inline_states => {
034 _start => sub {
035 my($h,$kernel) = @_[HEAP, KERNEL];
036
037 $h->{is_up} = 0;
038 $h->{command} = $self->{command};
039 $h->{timeout} = $self->{timeout};
040 $h->{heartbeat} =
041 $self->{heartbeat};
042 $kernel->yield('keep_alive');
043 $kernel->yield('heartbeat');
044 },
045 sig_child => sub {
046 delete $_[HEAP]->{wheel};
047 },
048 heartbeat => &heartbeat,
049 up => &up,
050 down => &down,
051 keep_alive => sub {
052 $_[HEAP]->{countdown} =
053 $_[HEAP]->{timeout};
054 },
055 closing => sub {
056 $_[HEAP]->{is_up} = 0;
057 },
058 })->ID();
059 }
060
061 ###########################################
062 sub heartbeat {
063 ###########################################
064 my($kernel, $heap) = @_[KERNEL, HEAP];
065
066 $kernel->delay( "heartbeat",
067 $heap->{heartbeat});
068
069 if( $heap->{is_up} ) {
070 INFO "Process is up for another ",
071 $heap->{countdown}, " seconds";
072
073 $heap->{countdown} -=
074 $heap->{heartbeat};
075
076 if($heap->{countdown} <= 0) {
077 INFO "Time's up. Shutting down";
078 $kernel->yield("down");
079 return;
080 }
081 }
082 }
083
084 ###########################################
085 sub up {
086 ###########################################
087 my ($heap, $kernel) = @_[ HEAP, KERNEL ];
088
089 if($heap->{is_up}) {
090 INFO "Is already up";
091 $_[KERNEL]->yield('keep_alive');
092 return 1;
093 }
094
095 my($prog, @args) = @{ $heap->{command} };
096
097 $heap->{wheel} =
098 POE::Wheel::Run->new(
099 Program => $prog,
100 ProgramArgs => [@args],
101 CloseEvent => "closing",
102 ErrorEvent => "closing",
103 StderrEvent => "ignore",
104 );
105
106 my $pid = $heap->{wheel}->PID();
107 INFO "Started process $pid";
108
109 $kernel->sig_child($pid, "sig_child");
110 $kernel->sig( "INT" => "down" );
111 $kernel->sig( "TERM" => "down" );
112
113 $_[KERNEL]->yield('keep_alive');
114 $heap->{is_up} = 1;
115 }
116
117 ###########################################
118 sub down {
119 ###########################################
120 my ($heap, $kernel) = @_[ HEAP, KERNEL ];
121
122 if(! $heap->{is_up}) {
123 INFO "Process already down";
124 return 1;
125 }
126
127 INFO "Killing pid ", $heap->{wheel}->PID;
128 $heap->{wheel}->kill();
129 $heap->{is_up} = 0;
130 $kernel->sig_handled();
131 }
132
133 1;
|
Diesen Artikel als PDF kaufen
Express-Kauf als PDF
Umfang: 6 Heftseiten
Preis € 0,99
(inkl. 19% MwSt.)
Als digitales Abo
Weitere Produkte im Medialinx Shop »
Versandartikel
Onlineartikel
Alle Rezensionen aus dem Linux-Magazin
- Buecher/07 Bücher über 3-D-Programmierung sowie die Sprache Dart
- Buecher/06 Bücher über Map-Reduce und über die Sprache Erlang
- Buecher/05 Bücher über Scala und über Suchmaschinen-Optimierung
- Buecher/04 Bücher über Metasploit sowie über Erlang/OTP
- Buecher/03 Bücher über die LPI-Level-2-Zertifizierung
- Buecher/02 Bücher über Node.js und über nebenläufige Programmierung
- Buecher/01 Bücher über Linux-HA sowie über PHP-Webprogrammierung
- Buecher/12 Bücher über HTML-5-Apps sowie Computer Vision mit Python
- Buecher/11 Bücher über Statistik sowie über C++-Metaprogrammierung
- Buecher/10 Bücher zu PHP-Webbots sowie zur Emacs-Programmierung
Insecurity Bulletin
Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...





