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