Mittwoch, 12. Dezember 2007

Objekte als Ressourcenverwaltung

Problem
Oftmals wird der Sprache C++ angekreidet, dass sie keine automatische Ressourcenverwaltung besässe. In der Tat ist es nicht einfach Ressourcenlecks zu vermeiden. Es gibt aber Techniken, welche diesem Problem entgegen wirken wie zum Beispiel das Verfahren „Ressourcenerwerb ist Initialisierung“ (RAII – Resource Acquisition Is Initialization).


Lösung
Die angeforderte Ressource wird direkt nach ihrer Initialisierung dem Objekt übergeben, welches diese verwaltet. Und sehr wichtig dabei ist die Freigabe der Ressource wenn sie nicht mehr benötigt wird. Ich spreche hier intelligente Zeiger mit Referenzzählung (RCSP – Reference-Counting Smart Pointer) an.


Prinzip
Im ersten Teil dieses Posts werde ich das Prinzip solcher Pointers erläutern und anhand eines Beispieles zeigen wie diese aufgebaut sind. Danach werde ich ein praktisches Beispiel listen anhand der Klasse shared_ptr der Boost-Bibliothek.


Objektorientierung
Die Objektorientierung macht es möglich: der Destruktor jedes Objektes wird spätestens dann aufgerufen, wenn dessen Variable ihren Gültigkeitsbereich verlässt.


Einfacher Fall
Eine angeforderte und initialisierte Ressource wird nun also einem intelligenten Pointer zur Verwaltung übergeben. Der Zugriff auf die Ressource passiert also nur noch über den Pointer. Nun muss dafür gesorgt werden, dass im Destruktor des Pointers dieselbe Ressource wieder frei gegeben wird.


Erweiterter Fall
Wird die Ressource nun über mehrere intelligente Pointer erreichbar, darf sie erst freigegeben werden wenn keiner dieser Pointer mehr auf sie zeigt. Hierfür wird eine Referenzzählung im intelligenten Zeiger benötigt, was nicht weiter schwierig ist zu implementieren.


Grenzen
So wird einiges einfacher doch im Gegensatz zur Garbage Collection ist es so nicht möglich Kreisreferenzen zu brechen.



Beispiel 1
Folgendes Listing zeigt das Prinzip eines solchen smart pointer. Die Ressource ist in diesem Fall Speicher für ein Objekt der Klasse Person. Die Mainfunktion zeigt diverse Fälle wie solch ein Pointer reagiert. Für das Verständnis wichtig ist ebenfalls die Ausgabe dieses Programms.


Achtung:
Verwenden sie bitte nicht diese Implementierung eines smart pointer. Dieses Programm habe ich nur zur Veranschaulichung geschrieben. Es besitzt noch diverse Fehlerquellen. Lassen sie sich vom darauf folgenden Beispiel inspirieren, wenn sie ebenfalls smart pointer in ihre Programme einbauen möchten.


    1 #include <cstdlib>
2 #include <iostream>
3 #include <string>
4
5 using namespace std;
6
7 /****************************************************
8 * Dummy-Objekt Person
9 ****************************************************/
10 class Person
11 {
12 public:
13 Person(string name) : name(name){}
14
15 string getString()const {return name;}
16 void setName(string str) { name = str; }
17
18 private:
19 string name;
20 };
21
22 /****************************************************
23 * RAII-Objekt SmartPtr
24 *
25 * Wichtige Funktionen von SmartPtr
26 * - SmartPtr()
27 * - SmartPtr(T *t)
28 * - SmartPtr& operator=(SmartPtr const& r)
29 * - T* operator->()const
30 * - ~SmartPtr()
31 ****************************************************/
32 template
33 class SmartPtr
34 {
35 public:
36 // Konstruktor (immer vor Verwenung der Attribute initialisieren)
37 SmartPtr() : type(0), references(0){}; // nur zu Testzwecken
38 SmartPtr(T *t) : type(t), references(new int(1)) {};
39
40 // Kopierzuweisungskonstruktor (sollte immer *this zurückgeben)
41 SmartPtr& operator=(SmartPtr const& r)
42 {
43 // Selbstzuweisung handeln
44 if(this == &r) return *this;
45
46 // mit swap wäre es eleganter gelösst
47 type = r.type;
48 references = r.references;
49
50 *references = *references + 1;
51 return *this;
52 }
53
54 // Destruktor
55 // Zerstört das Objekt erst wenn keine Referenz mehr darauf zeigt!!
56 ~SmartPtr()
57 {
58 *references = *references - 1;
59
60 if(*references == 0){
61 delete type;
62 delete references;
63 cout << "Object destroyed..." << endl;
64 }
65 }
66
67 // "Dereferenzierung" Zugriff auf das Objekt.
68 T* operator->()const
69 {
70 return type;
71 }
72
73 void what()
74 {
75 cout << "Mein Name ist: " << type->getString() << endl;
76 cout << " Referenzen: " << *references << endl;
77 }
78 private:
79 T* type;
80 int* references;
81
82 // Kopierkonstruktor ist privat um diese Möglichkeit auszuschliessen.
83 SmartPtr(SmartPtr const& r);
84 };
85
86 /****************************************************
87 * Testfunktion
88 *
89 * - SmartPtr - Test
90 * - Normale Zuweisung
91 * - Referenzzählung
92 * - Selbstzuweisung
93 * - Objektzerstörung
94 ****************************************************/
95 int main(int argc, char *argv[])
96 {
97 {
98 // Neue Person erstellen und in einem SmartPtr festhalten.
99 cout << endl << "Neue Person." << endl << endl;
100 SmartPtr p1(new Person("Hans Muster"));
101 cout << "p1. "; p1.what(); cout << endl;
102 cout << endl;
103
104 {
105 // Weiterer SmartPtr deklarieren
106 SmartPtr p2;
107 // Zuweisung => gemeinsame Nutzung von Person
108 p2 = p1;
109 // Name der Person ändern über p2
110 p2->setName("Muster Hans");
111
112 cout << "Geaenderte Person ueber p2" << endl << endl;
113 cout << "p1. "; p1.what(); cout << endl;
114 cout << "p2. "; p2.what(); cout << endl;
115 }
116
117 cout << endl;
118 cout << "p2 ist ausserhalb des Sichtbarkeitsbereiches." << endl << endl;
119 cout << "p1. "; p1.what(); cout << endl << endl;
120
121 p1 = p1;
122 cout << "Selbstzuweisung von p1." << endl << endl;
123 cout << "p1. "; p1.what(); cout << endl;
124 }
125
126 system("PAUSE");
127 return EXIT_SUCCESS;
128 }




Beispiel 2
Natürlich gibt es für intelligente Pointer bereits bewährte Bibliotheken. Ich empfehle den shared_ptr von Boost. Im folgenden Listing sehen sie wie dieser zu handhaben ist, wie zuvor mit der entsprechenden Ausgabe.

    1 #include <cstdlib>
2 #include <iostream>
3 #include <string>
4 #include "shared_ptr.hpp"
5
6 using namespace std;
7 using namespace boost;
8
9 class Person
10 {
11 public:
12 Person(string name) : name(name){}
13
14 string getName()const {return name;}
15 void setName(string str) {name = str;}
16 private:
17 string name;
18 };
19
20 void what(string name, long refs);
21
22 int main(int argc, char *argv[])
23 {
24 // Neue Person erstellen und p1 übergeben
25 cout << endl << "Neue Person." << endl << endl;
26 shared_ptr p1(new Person("Hans Muster"));
27 cout << "p1."; what(p1->getName(), p1.use_count());
28 cout << endl;
29 {
30 // p2 initialisieren mit p1 (Kopierkonstruktor)
31 shared_ptr p2(p1);
32 p2->setName("Muster Hans");
33 cout << "Geaenderte Person ueber p2" << endl << endl;
34 cout << "p1."; what(p1->getName(), p1.use_count());
35 cout << "p2."; what(p2->getName(), p2.use_count());
36 }
37 cout << endl;
38 cout << "p2 ist ausserhalb des Sichtbarkeitsbereiches." << endl << endl;
39
40 p1 = p1;
41 cout << "Selbstzuweisung von p1." << endl << endl;
42 cout << "p1."; what(p1->getName(), p1.use_count());
43
44 system("PAUSE");
45 return EXIT_SUCCESS;
46 }
47
48 void what(string name, long refs)
49 {
50 cout << " Mein Name ist: " << name << endl;
51 cout << " Referenzen: " << refs << endl;
52 }



Mittwoch, 5. Dezember 2007

Versenden eines Ethernet - Frames mit richtiger Frame Check Sequence

Problem
Als ich in einer Projektarbeit einen „Rapid Spanning Tree“ - Treiber für die Linux – Bridge implementierte hatte ich Probleme ein Ethernet – Frame korrekt nach Standard zu versenden. Da im Internet und in Büchern nur spärlich Informationen hierzu vorhanden sind werde ich zwei wichtige Erkenntnisse in diesem Post besonders hervorheben; Die Länge eines Ethernet – Frames und das korrekte Erstellen einer Frame Check Sequence (FCS).

Länge
Im IEEE 802.3 Standard ist definiert, dass ein Frame mindestens 64 Byte gross sein muss. Ist das zu versendende Packet nun kürzer, muss der Rest mindestens bis auf 64 Byte gepaddet werden. In meiner Variante wird einfach herumliegender Speicher gelesen, was einwenig unschön ist. Ich gebe es ja zu. ;)


// Length of a Frame
size = length + 2*ETH_ALEN + 2 + 4; // + 4 wegen CRC-32
if (size < 64) size = 64; // Ethernet Standard min 64 Byte

Im Sourcefile br_stp_bpdu.c Version 1.3 des 2.6er Kernels ist dies nicht korrekt implementiert.


Frame Check Sequence
Über die Art und Weise wie und über welche Teile eines Ethernet – Frames eine FCS tatsächlich generiert werden muss ist es schwierig Informationen zu erhalten. Meine Lösung überprüfte ich mit dem Programm Wireshark Version 0.99.5. Und zwar wird die FCS über den gesamten Datenbereich des sk_buff berechnet, natürlich exklusive der CRC.


// IEEE 802.3 CRC-32 (ganzes Ethernet-Packet ohne die CRC selbst)
fcs = ~(crc32_le(~0, skb->data, skb->len - 4));

In der besagten Version der Linux – Bridge wird keine FCS angehängt.


Sourcecode der gesamten Funktion
Hier noch das Listing der gesamten Funktion, damit der Leser an einem konkreten Beispiel sehen kann, wie ein Ethernet – Frame verschickt werden kann.

In dem „Rapid Spanning Tree Protocol“ (RSTP) werden zur Kommunikation zwischen den Bridges sogenannte „Bridge Protocol Data Units“ (BPDU) verschickt. In dieser Funktion wird ein BPDU in ein Ethernet – Frame gepackt. Der Funktion wird das entsprechende Portdevice, das Datenfeld und die Länge des Datenfeldes übergeben. Mit dem NF_HOOK – Makro übergibt man das fertige Frame dem Kernel.

static void br_send_bpdu(struct net_bridge_port *p, unsigned char *data, int length)
{
struct net_device *dev;
struct sk_buff *skb;
int size;
u32 fcs;

if (!p->br->stp_enabled)
return;

// Length of a Frame
size = length + 2*ETH_ALEN + 2 + 4; // + 4 wegen CRC-32
if (size < 64) size = 64; // Ethernet Standard min 64 Byte

dev = p->dev;

if ((skb = dev_alloc_skb(size)) == NULL) {
printk(KERN_INFO "br: memory squeeze!\n");
return;
}

skb->dev = dev;
skb->protocol = htons(ETH_P_802_2);
skb->mac.raw = skb_put(skb, size);
memcpy(skb->mac.raw, bridge_ula, ETH_ALEN);
memcpy(skb->mac.raw+ETH_ALEN, dev->dev_addr, ETH_ALEN);
skb->mac.raw[2*ETH_ALEN] = 0;
skb->mac.raw[2*ETH_ALEN+1] = length;
skb->nh.raw = skb->mac.raw + 2*ETH_ALEN + 2;
memcpy(skb->nh.raw, data, length);

// - 4 wegen CRC-32
memset(skb->nh.raw + length, 0xa5, size - length - 2*ETH_ALEN - 2 - 4);

// IEEE 802.3 CRC-32 (ganzes Ethernet-Packet ohne die CRC selbst)
fcs = ~(crc32_le(~0, skb->data, skb->len - 4));

memcpy(skb->nh.raw + length + (size - length - 2*ETH_ALEN - 2 - 4), &fcs, 4);

NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_OUT, skb, NULL, skb->dev, dev_queue_xmit);
}

CD rippen ohne Administratorenrechte

Problem
Arbeitet man unter Windows 2k/XP mit Administratorenrechten öffnet man gleichzeitig auch jedem Virus Tür und Tore, um das System zu zerstören. Auch wird das System unwissentlich vom Benutzer falsch bedient. Darum ist es empfehlenswert mit einem Hauptbenutzer seinen Täglichen Arbeiten nach zu gehen um das System von diesen zwei Faktoren zu schützen.

Beispiel

Unter Windows 2k/XP gibt es einige Applikationen die scheinbar Administratorenrechte voraussetzen. So zum Beispiel auch jede Software, mit der CDs gerippt werden können, und welche die NT SCSI Bibliothek benutzt. „CDex Audio CD extractor“ ist beispielsweise eine solche Software.

Lösung
Damit diese Software trotz eingeschränkten Benutzerrechten auf die besagte Bibliothek zugreifen kann eine lokale Sicherheitsrichtlinie gesetzt werden. Diese findet man unter: Sicherheitseinstellungen/Lokale Richtlinien/Sicherheitsoptionen und heisst: Geräte: Zugriff auf CD-ROM-Laufwerke auf lokal angemeldete Benutzer beschränken. Diese muss aktiviert werden. Hierfür sind Administratorenrechte erforderlich.

Tipp:

  • Rechtsklick auf Dos-Shell
  • Ausführen als…
  • Administratoren Passwort eingeben
  • secpol.msc ausführen

Dienstag, 27. November 2007

Terminal Server auf einem Domain Controller

Szenario
In einem KMU wird ein Windows Server 2003 angeschafft, der sowohl als Domänenkontroller sowie auch als Terminalserver fungieren soll, da nur ein Computer für den Einsatz als Server zu Verfügung steht.

Vorteil: Es wird nur eine Hardware benötigt.
Nachteil: Domänenkontroller muss sich die Performance mit dem Terminalserver teilen.

Vorgaben
  • Eine ausgewählte Gruppe von Benutzern sollen auf den Terminalserver zugreifen können, jedoch mit starken Restriktionen. Auf den Arbeitsstationen aber sollen sie normale Benutzerberechtigungen zugeteilt bekommen.
  • Die Restriktionen sollen nur auf dem Terminalserver angewendet werden, damit andere Computer nicht davon betroffen sind.
  • Den Domänenadministratoren ist es gestattet mit voller Zugriffsberechtigungen auf dem Terminalserver zu arbeiten.
Benennung
In der hier gezeigten Lösung wurden folgende Namen definiert:

Domäne: experiment.ch
Domänenkontroller: EXPERIMENT


Mögliche Konfiguration
Es wird eine globale Gruppe „Terminal Server Users“ erstelle, der alle Benutzer angehören die auf den Terminalserver zugreifen müssen. Wo in der Domäne sich diese Gruppe befindet kann nach belieben festgelegt werden.

In den lokalen Richtlinien des Terminalserver Computers wird der Gruppe die Berechtigung für das „Anmelden über Terminaldienste zulassen“ erteilt.

Pfad: Richtlinien für Lokaler Computer/Computerkonfiguration/Windows-Einstellungen/Lokale Richtlinien/Zuweisen von Benutzerrechten

Ebenfalls bekommt die Gruppe Benutzerzugriff auf die Terminalserververbindung in der Terminalserverkonfiguration.

Die Benutzer der Gruppe „Terminal Server Users“ sollten nun bereits auf den Terminalserver zugreifen können, noch aber ohne Restriktionen.

Auf die Organisationseinheit „Domain Controllers“, in der sich das Computerobjekt des Domänenkontroller befindet wird eine neue Gruppenrichtlinie angewendet.

Zentral bei dieser Gruppenrichtlinie ist der Loopbackverarbeitungsmodus. Dieser bewirkt, dass nicht die Benutzereinstellungen des Benutzers sondern diejenige dieser Gruppenrichtlinie angewandt wird, wenn sich ein Benutzer an ein Computer dieser Organisationseinheit anmeldet.

Pfad: Terminal Server Users Policy/Computerkonfiguration/Administrative Vorlagen/System/Gruppenrichtlinien

Weiter können anschliessend die Restriktionen unter der Benutzerkonfiguration eingestellt werden.

Achtung: Wird die Gruppenrichtlinie nun so eingesetzt, würden auch die Domänenadministratoren eingeschränkt werden, was meist nicht erwünscht ist. Deswegen müssen noch Berechtigungen auf die Gruppenrichtlinien vergeben werden.

In den Sicherheitseigenschaften sollten folgende Anpassungen vorgenommen werden:

  • Gruppe „Authentifizierte Benutzer“ entfernen
  • Der Gruppe „Domänen-Amins“ expliziet die Gruppenrichtlinienübernahme verweigern
  • Die Gruppe „Terminal Server Users“ hinzufügen mit Lese- und Gruppenrichtlinienübernahme Berechtigung
  • Computerobjekt des Terminalservers hinzufügen mit Lese- und Gruppenrichtlinienübernahme Berechtigung

Die Gruppe „Authentifizierte Benutzer“ ist eigentlich ein Sicherheitsprinzipal, wird somit vom System verwaltet und kann nicht ohne Weiteres verändert werden. Auch können die dazugehörigen Benutzer nicht direkt aufgelistet oder beeinflusst werden.

Begründung für die Sicherheitseinstellung
Würde die Gruppe „Authentifizierte Benutzer“ nicht entfernt werden, gälte die Gruppenrichtlinie für alle Objekte die sich an der Domäne erfolgreich angemeldet haben. Das heisst nicht nur für sämtliche Benutzer sondern auch für alle Computer in dieser Organisationseinheit (Domain Controllers). Ist dies gewünscht muss man bei der Gruppe „Domänen-Admins“ die Gruppenrichtlinienübernahme verweigern, damit der Administrator sich nicht selbst einschränkt. Will man aber die Gruppenrichtlinie nur explizit auf die „Terminal Server Users“ anwenden muss die Gruppe „Authentifizierte Benutzer“ entfernt werden. Implizit entfernt man so auch das Computerobjekt des Domänenkontrollers. Darum muss der Computer „EXPERIMENT“ wieder für das Lesen und die Gruppenrichtlinienübernahme berechtig werden, da sonst die Richtlinie mangels Zugriffsberechtigung herausgefiltert wird, obwohl die Benutzergruppe „Terminal Server Users“ die benötigten Berechtigungen hätte. So kann man auch klar steuern auf welchem Computern die Gruppenrichtlinie gilt.


Nützliche Tools
Damit Änderungen an den Richtlinien sofort wirken, kann man den Befehl gpupdate /force in der Shell ausführen.

Bei komplexeren Domänenstrukturen oder Richtlinienhierarchien ist das Tool gpresult nicht weg zu denken. Dort kann man anzeigen lassen welche Gruppenrichtlinien bei einem Benutzer auf einem Bestimmten Computer angewandt und welche gefiltert werden.

Syntax: gpresult /u Benutzername /s Computername


Donnerstag, 15. November 2007

Go!

In meinem jugendlichen Leichtsinn habe ich nun in einer freien Stunde einen Blog eröffnet. Somit gehöre ich auch zum heutigen Hype und zur modernen Bewegung des Web 2.0. Also kann ich nun überall Respekt ernten indem ich mich einen Blogger nenne. Schön nicht? Gelassen werde ich also auch überall meinen Senf dazu geben, den wahrscheinlich niemand interessieren wird.

---

In meinem Weblog werde ich technische Probleme und deren Lösungen aufzeigen, welchen ich in irgendeiner Art und Weise begegnet bin. Dies sollte anderen mit selben Problemen weiter helfen, und für mich sollte es eine Art Problem-History sein.

Man wird sehen wie oft und motiviert ich schreiben werde.

Hoffen wir auf ein Wiederlesen. (höhö)