FreeBSD
Systemadministration under FreeBSD
Um es kurz zu machen: Es gibt derzeit absolut KEIN echtes Cluster-Filesystem (wie z.B. GFS oder OCFS2) für FreeBSD. Auch andere Projekte, welche sich mit verteilten Dateisystemen beschäftigen wie z.B. GlusterFS, PVFS oder DRBD sind entweder nicht nach FreeBSD portiert, oder die Portierung ist sehr alt und läuft häufig nicht unter einem aktuellen FreeBSD.
Da ich jedoch vier Filesysteme auf gleichem Stand halten muß (und zwar muß die Aktualisierung binnen Sekunden nach einem Upload erfolgen), habe ich einen kleinen Workaround entwickelt, der dies über rsync und das FreeBSD audit-System ermöglicht. Die Idee das audit-System hierzu zu nutzen habe ich von Luke Marsden, der die Filesystem-Aktivitäten mit audit_control und einigen Python-Scripts überwacht.
Als erstes muß das audit_system aktiviert und konfiguriert werden. Das event-auditing ist Teil des FreeBSD-Systems selbst und muß im Kernel aktiviert werden.
Folgende Zeile muß in der Kernel-Konfiguration hinzugefügt werden:
options AUDIT
Danach muß der Kernel neu compiliert und installiert werden. Das Vorgehen hierzu ist im FreeBSD Handbook zu finden.
Als nächstes muß die folgende Zeile in der /etc/rc.conf ergänzt werden:
auditd_enable=”YES”
Soweit so gut – nun möchte auch das audit-system selbst eine Konfiguration haben. Hierzu öffnet man die Datei /etc/security/audit_control und ändert die config wie folgt:
dir:/var/audit
flags:fc,fd,fw
minfree:20
naflags:lo
policy:cnt
filesz:0
Das war es schon. Nun kann man das audit-system starten indem man folgenden Befehl ausführt:
/etc/rc.d/auditd start
oder indem man das System via reboot neu startet.
Falls rsync noch nicht auf dem System installiert ist, kann man dies einfach und schnell aus den ports nachholen:
cd /usr/ports/net/rsync
make
make install
Dabei sollte es keine Probleme geben dürfen.
Als nächstes muß nun ein alternativer Pfad zum Daten-Verzeichnis über einen symbolischen Link (“Alias”) erstellt werden; warum dieser benötigt wird werde ich später erklären.
ln -s /path/to/your/data/ /alternative_data_path/
Nun sollte rsync so konfiguriert werden, daß es als daemon läuft. Hierzu erstellen, bzw. verändern wir die Config-Datei /etc/rsyncd.conf wie folgt:
max connections = 5
log file = /var/log/rsync.log
timeout = 30[shareName]
comment = Name of this “Rsync mount”
path = /alternative_data_path/
read only = no
list = yes
uid = validUser
gid = validGroup
hosts allow = ,
hosts deny = *
Um rsync nun zu starten wird der folgende Befehl ausgeführt:
/usr/local/bin/rsync –config=/etc/rsyncd.conf –daemon
Es ist vermutlich eine gute Idee den rsynd mit einem entsprechenden Tool zu überwachen (z.B. mit den daemontools) damit sichergestellt ist, daß der rsync-Service immer verfügbar ist (in diesem Fall muß rsyncd dann mit der Option –no-detach option gestartet werden).
#!/usr/bin/perl ## # This software is published under the Apchae 2.0 licenses. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Author: Erik Scholtz # Web: http://blog.elitecoderz.net ### # We are strict, cauz we are elitecoderz! use strict; use threads qw(yield); use threads::shared; use Thread::Semaphore; # No caching $|=1; ################ # Configuration my $debug = 1; # 0/1 to enable logging to the console or disable my $path = '/path/to/your/data/'; # Path to sync my @cmds; # Syncer commands that should be executed $cmds[0] = '/usr/local/bin/rsync -raz --progress --size-only /path/to/symboliclink/data/<!--target--> rsync:///shareName/<!--target-->'; $cmds[1] = '/usr/local/bin/rsync -raz --progress --size-only /path/to/symboliclink/data/<!--target--> rsync:///shareName/<!--target-->'; $cmds[2] = '/usr/local/bin/rsync -raz --progress --size-only /path/to/symboliclink/data/<!--target--> rsync:///shareName/<!--target-->'; ############################################################################### # DO NOT CHANGE ANYTHING BELOW THIS LINE, UNLESS YOU KNOW WHAT YOU ARE DOING! # ############################################################################### ### # Set Threads yield threads->yield(); # SetUp some thread-shared variables my $commands :shared; $commands = &share([]); my $run :shared; $run = &share({}); $run->{'status'} = 1; my $sema :shared; $sema = &share({}); # Local array where all syncer-threads are stored my @threads; # Create a thread for each syncer my $maxid = -1; for (my $i=0;$i<=$#cmds;$i++) { print "Starting syncer $i\n" if $debug; $sema->{$i} = Thread::Semaphore->new(0); my $syncer = threads->create('syncJob',$run,$sema,$i,$commands,$path,$cmds[$i],$debug); push(@threads,$syncer); $maxid = $i; } # Create the Checker thread, which cleanup the jobs and ensures the function of all syncers $sema->{'checker'} = Thread::Semaphore->new(0); my $syncer = threads->create('JobChecker',$run,$sema,$commands,$maxid,$debug); push(@threads,$syncer); # Create the audit thread print "Starting audit\n" if $debug; my $auditthread = threads->create('audit',$run,$sema,$commands,$path,$maxid,$debug); print "Waiting for audi to terminatet\n" if $debug; $auditthread->join(); # If the audit-thread gets joinable, we have to terminate everything # Terminate all threads and cleanup $run->{'status'} = 0; while ($#threads >=0 ) { my $worker = shift(@threads); print "Shutdown of syncer ...\n" if $debug; $worker->join(); } print "Shutdown clean completed\n" if $debug; exit(0); ######################################################################################################################################## ######################################################################################################################################## #################################################################################################### # audit thread sub audit { my $r = shift; my $sp = shift; my $c = shift; my $p = shift; my $m = shift; my $d = shift; print " audit started ...\n" if $d; # open listener on the audit device open(STATUS, "/usr/sbin/praudit /dev/auditpipe |") || die "can't fork: $!"; while (<STATUS>) { my $line = $_; last if ($line eq '' || $r->{'status'}<=0); # Terminate if audit terminated if ($line =~ /path,$p(.+)/) { # Check if the changed file is in the observed path my $file = $1; print "Change detected on file: $file\n" if $d; my $hash :shared; # Create a command for the syncers $hash = &share({}); $hash->{'file'} = $file; $hash->{'status'} = ''; $hash->{'time'} = ''; for (my $j=0;$j<=$m;$j++) { # init job done charta $hash->{$j} = 'no'; } if (1) { lock($c); push(@{$c},$hash); print "Added new job for $file\n" if $d; } for (my $j=0;$j<=$m;$j++) { # wakeup syncers $sp->{$j}->up(); } $sp->{'checker'}->up(); } } close STATUS || die "audit not closed correctly: $! $?"; print " audit terminated ...\n" if $d; return(0); } #################################################################################################### # syncer thread sub syncJob { my $r = shift; my $sp = shift; my $id = shift; my $c = shift; my $p = shift; my $e = shift; my $d = shift; print " syncer $id started ...\n" if $d; while ($r->{'status'}>0) { if ($#{$c}>=0) { # if there are any jobs to be done for (my $j=0; $j<=$#{$c}; $j++) { next if ($c->[$j]->{$id} eq 'ok'); # if my job is already done skip this job and check next my $file = $c->[$j]->{'file'}; if (-e $p.$file) { # check if the file is existing $c->[$j]->{$id} = 'working'; # mark this job as being worked on my $dif = 1; while ($dif>0) { # check if the file is in upload and changes size within 1,5 secs print "Checking Filesize ...\n" if $d; my $ssize = -s $p.$file; sleep(1.5); my $eesize = -s $p.$file; $dif = $eesize - $ssize; print "Checking Filesize $ssize - $eesize = $dif\n" if $d; } my $cm = $e; $cm =~ s/<!--target-->/$file/g; system($cm); # rsync to other server } lock($c); $c->[$j]->{$id} = 'ok'; # mark job as done for me } } $sp->{$id}->down(); } print " syncer $id terminated ...\n" if $d; return(0); } #################################################################################################### # checker thread that checks if all jobs are done sub JobChecker { my $r = shift; my $sp = shift; my $c = shift; my $m = shift; my $d = shift; print " checker started ...\n" if $d; while ($r->{'status'}>0) { while ($#{$c} >= 0 && $r->{'status'}>0) { print " Checker loop ...\n" if $d; my $rem = 0; foreach my $job (@{$c}) { # loop through all jobs my $mem = 'ok'; for (my $j=0;$j<=$m;$j++) { # check job done charta if ($job->{$j} eq 'no') { # job not handled $mem = 'no' if ($mem ne 'working'); # job not handled (may never override a job in progress state) } elsif ($job->{$j} eq 'working') { # job in progress (always overrides not handled) $mem = 'working'; } } # Job not completed if ($mem eq 'no') { if ($job->{'time'} eq '') { # Set timestamp to know, how long this job is already waiting $job->{'time'} = time; } else { # Job already got a timestamp my $watch = time - $job->{'time'}; print "Job age: $watch\n" if $d; if (time - $job->{'time'} > 300) { # Job has waited for more than 5 minutes. terminate program print "TIME FOR JOB EXCEEDED - shutting down syncer"; $r->{'status'} = 0; for (my $j=0;$j<=$m;$j++) { # wakeup syncers $sp->{$j}->up(); $sp->{'checker'}->up(); # wakeup ourself } } } } elsif ($mem eq 'working') { # job in progress - just actualize the timestamp $job->{'time'} = time; } else { $job->{'status'} = 'complete'; # job is completely done and is marked for being removed $rem = 1; } } # Job to remove available if ($rem > 0) { lock($c); # lock the command-queue my @arr; for (my $j=0;$j<=$#{$c};$j++) { # store all not handled jobs / drop completed jobs my $ex = shift(@{$c}); if ($ex->{'status'} ne 'complete') { push(@arr,$ex); } } for (my $j=0;$j<=$#arr;$j++) { # put all stored (not finished) jobs back into the command queue push(@{$c},$arr[$j]); } } print " Checker reloop ...\n" if $d; sleep(1); } print " Checker sleeping (".$#{$c}.")...\n" if $d; $sp->{'checker'}->down(); } print " checker terminated ...\n" if $d; return(0); }
Dieses Script ist das Herzstück des Ganzen: Über das audit-system hört es auf Veränderungen von Dateien; bei einer Änderung (oder neu anlegen) einer Datei wird die Datei via rsync direkt auf die anderen Systeme kopiert. Und das ist der Punkt, warum der symbolische Link so wichtig ist. Wenn das Script die Datei via rsync auf ein zweites System kopiert, so registriert das audit-system dort die Änderung und informiert das dort installierte Script. Dieses würde dann direkt die Änderung auf den ersten Server zurück kopieren, wodurch das Script auf dem ersten Server wiederum über eine Änderung informiert würde, usw … Ohne den symbolischen Link würde also eine Endlos-Kopier-Schleife entstehen.
Das Script wird auf jedes System kopiert, welches mit den jeweils anderen Systemen synchron gehalten werden soll. Ich empfehle dringend auch dieses Script mit den daemontools zu überwachen. Dann muß das Script wie folgt angepasst werden:
$debug kann entweder 0 (keine Debuging Ausgabe) oder 1 (mit Ausgabe) sein.
$path sollte der physikalische Pfad zu den Daten sein.
Für jedes System welches synchronisiert werden soll muß folgende Zeile ergänzt werden. Bitte unbedingt beachten, daß die Nummer in den eckigen Klammern ($cmds["Nummer"]) um jeweils eins erhöht werden muß:
$cmds[0] = ‘/usr/local/bin/rsync -raz –progress –size-only /path/to/symboliclink/data/ rsync:///shareName/‘;
BEVOR irgendetwas auf dem eigenen System geändert wird, muß unbedingt ein komplettes BackUp erstellt werden. Die Benutzung des Scriptes und des HowTos erfolgt auf eigene Gefahr. Wenn es also aufgrund der Anwendung dieses Scriptes oder Howtos zu irgendwelchen Datenverlusten kommt, dann kann man mich dafür nicht verantwortlich machen.
Um möglichst eine “Echtzeitsynchronisierung” hinzubekommen, startet das Script für jedes System welches synchronisiert werden soll einen eigenen Threas. Deswegen muß die Perl-Installation “thread-enabled” sein.
Nach über einem Jahr Zusammenarbeit mit Danny Braniss in dem wir mehrere tausend Optionen, Einstellungen und Konfigurationen getestet haben, konnte ich nun das iStore iSCSI-device unter FreeBSD zum Laufen bringen.
Zur Erinnerung. Der folgende Fehler trat auf, wenn man versuchte ein UFS Filesystem anzulegen:
newfs -O2 /dev/da0s1
/dev/da0s1: 782023.5MB (1601584044 sectors) block size 16384, fragment size 2048
using 4256 cylinder groups of 183.77MB, 11761 blks, 23552 inodes.
super-block backups (for fsck -b #) at:
160, 376512, 752864, … … …
1601377920
internal error: can’t find block in cyl 0
Im dangerously dedicated mode:
“# newfs -O2 /dev/da0“
Die Erstellung des Filesystems schlug mit dieser Meldung fehl:
newfs -O2 /dev/da0
/dev/da0: 782023.5MB (1601584044 sectors) block size 16384, fragment size 2048
using 4256 cylinder groups of 183.77MB, 11761 blks, 23552 inodes.
super-block backups (for fsck -b #) at:
160, 376512, 752864, … … …
1601377920
internal error: cg 0: bad magic number
Den entscheidenten Hinweis erhielt ich bei einem Test mit einem PetaStor-System, welches auf Anhieb und ohne Probleme perfekt unter FreeBSD funktionierte. Den letzten Teil des Puzzles fand ich dann in der FreeBSD-FS Mailingliste. Mit den folgenden Befehlen kann das Filesystem geschrieben werden:
“# gpart create -s GPT da0"
# gpart show da0
# gpart add -b 34 -s 20971519 -t freebsd-ufs -l AnosLabel da0
# newfs -O2 /dev/da0p1
Wichtiger Hinweis: Die Zahl 20971519 ist durch die device-Größe zu ersetzen, welche einem von gpart show da0 ausgegeben wird.
Alles was man dazu benötigt, um einen SOCKS5-Tunnel über SSH zu fahren ist ein openSSH auf dem “SOCKS-Server” und den bereits benutzten und bekannten “Proxifier”, ebenso wie unter Windows-Systemen das Programm PuTTY.
Als erstes muß der sichere Tunnel aufgebaut werden (unter OS X hierzu in der Konsole folgenden Befehl eingeben):
ssh -2 -N -D 8080 user@<ip of your SOCKS5-Host>
zum Beispiel:
ssh -2 -N -D 8080 myuser@192.168.1.11
Unter Windows muß PuTTY wie folgt konfiguriert werden:
Den Hostname (fim Beispiel: 192.168.1.11 – hier muß aber die IP Ihres SSH/SOCKS-Servers eingetragen werden!) im Bereich “Sessions” eintragen.
Dann im Reiter “Tunnels” den Source Port auf 8080 stellen
Dann “Dynamic” unter dem Punkt Destination Port anwählen
Open anklicken um den Tunnel aufzubauen
Als nächstes wird der Proxifier gestartet und folgende Einstellung vorgenommen:

Ich finde es nützlich (aber nicht unbedingt notwendig) die folgende Regel noch den Proxification Rules hinzuzufügen:

Das wars: openSSH hat einen integrierten SOCKS-Proxy. Proxifier sendet nun den gesamten Datenverkehr über den verschlüsselten SSH-Tunnel, und der SOCKS-proxy in openSSH auf der anderen Seite schickt den Datenverkehr an die richtige Zieladresse, bzw. leitet den eingehenden Datenverkehr wieder korrekt zurück.
nachdem ich google nach SOCKS durchsucht hatte fand ich eine häufig empfohlene Implementierung von SOCKS5: Dante
Außerdem gibt es eine sehr nette Übersicht über verschiedene Implementierungen auf wikipedia: http://en.wikipedia.org/wiki/SOCKS
Die Installation ist sehr einfach: Unter SuSE findet man ein passendes rpm welches einfach installiert wird, unter FreeBSD findet man Dante in den ports (cd /usr/ports/net/dante/; make; make install ). Interessanter wird nun die Konfiguration von Dante, die ich heute erklären möchte (ich habe nirgendwo ein HowTo gefunden und mußte deswegen die gesamte Dokumentation lesen):
Das config-file findet man unter /etc/sockd.conf (/usr/local/etc/sockd.conf unter FreeBSD). Dieses muß in der folgenden Weise angepasst werden:
#Logfile fuer Dante konfigurieren
logoutput: /var/log/dante.log#IP und Port auf der Dante fuer eingehende Verbindungen warten soll
internal: <IP address of your dante server> port = 80#IP/Interface welches Dante ausgehend benutzen soll
#Name des Interface erhaelt man ueber ifconfig
external: eth0
#Alternative: >external: <IP address that should be used>#Authentifizierung: deaktiviert, da eine statische IP-Adresse authentifizierung genug ist.
method: username none#unprivileged user
user.notprivileged: nobody
Soviel mal zu den Grundlagen – nun zum interessanten Teil:
#Wer darf den SOCKS-Tunnel konnektieren?
client pass {
from: <your ip here>/32 port 1-65535 to: 0.0.0.0/0
}#Loopback darf ebenfalls den Tunnel verwenden
client pass {
from: 127.0.0.0/8 port 1-65535 to: 0.0.0.0/0
}#Alle anderen nicht
client block {
from: 0.0.0.0/0 to: 0.0.0.0/0
log: connect error
}#Nach erfolgreicher Verbindung: wohin darf konnektiert werden?
#Loopback darf niemand von aussen konnektieren
block {
from: 0.0.0.0/0 to: 127.0.0.0/8
log: connect error
}#Verbindungen von aussen an den Client sind erlaubt
pass {
from: <your ip here>/32 to: 0.0.0.0/0
protocol: tcp udp
}pass {
from: 127.0.0.0/8 to: 0.0.0.0/0
protocol: tcp udp
}#Der Rest wird geblockt
block {
from: 0.0.0.0/0 to: 0.0.0.0/0
log: connect error
}
Kleiner Hinweis: dieses Beispiel erlaubt den Zugang von einer IP-Adresse (/32) aus, selbstverständlich kann auch von mehreren aus der Zugang erlaubt werden. Wer mit dem Subnetting und der Slash-Notation kämpft, der findet hier eine passende Hilfe: Subnet Cheat Sheet
nach dieser recht einfachen Konfiguration sollte der Dante-Dienst ohne Probleme sich starten lassen über folgenden Befehl:
/etc/rc.d/sockd start
(Unter FreeBSD muß zuerst sockd_enable=”YES” in die /etc/rc.conf eingetragen werden, und dann der Befehl: /usr/local/etc/rc.d/sockd start ). Ab jetzt kann man im Logfile /var/log/dante.log zusehen was passiert.
Ok – damit kommen wir zum komplizierten Teil: OS X davon zu überzeugen mit SOCKS5 zusammenzuarbeiten.
Als erste (und nicht erfolgreiche) Idee kam mir, dies über die Systemeinstellungen unter Netzwerk zu konfigurieren. (Systemeinstellungen öffnen, Netzwerk und dort “Weitere Optionen” anklicken, dann auf den Reiter “Proxies”. In diesem Fenster den SOCKS Proxy aktivieren und die IP des Servers und den zugehörigen Port eintragen; die Änderungen speichern und aktivieren.) Leider helfen diese Einstellungen nur bei Cocoa and WebKit-basierten Programmen (und da selbst diese Applikationen nicht alle die Systemeinstellungen benutzen ist man hier auch nur zu 95% abgedeckt).

Screenshot Systempreferences network german
Thunderbird und Firefox zum Beispiel benutzen nicht die Systemeinstellungen. Beide Programme kann man so konfigurieren, daß sie einen SOCKS-Tunnel benutzen, aber um ehrlich zu sein: Ich habe keine Lust an jedem Ort wo ich hinkomme meine Programme neu zu konfigurieren bevor ich arbeiten kann. Deswegen machte ich mich auf die Suche nach einem generellen SOCKS-Proxy für OS X.
Nach einigem Suchaufwand konnte ich Proxifier finden – ein kommerzielles Produkt welches leicht zu installieren, konfigurieren und mit nur ein paar Klicks zu benutzen ist. Außerdem gibt es auch eine Version für Windows und ich denke (getestet habe ich es nicht), diese wird ebenso gut funktionieren wie die Mac-Variante für OS X.
Die Konfiguration ist sehr einfach und benötigt die ausführliche Dokumentation auf der Homepage von Proxifier eigentlich nicht. Für dieejenigen, die gerne Dokumentationen lesen und sich somit besser fühlen: Hier ist die Anleitung für OS X zu finden: http://www.proxifier.com/mac/documentation/ProxifierHelp.html
Nachdem Proxifier gestartet und konfiguriert ist war ich direkt wieder online – unter “umgehen” der Firewall meines Kunden indem nun aller Datentransfer über den Port 80 “geroutet” wird.
ACHTUNG: Dante und SOCKS ist nicht mit einem VPN zu verwechseln (auch wenn es sich so anfühlt)! Der gesamte Datenverkehr läuft unverschlüsselt und kann mit jedem Packetsniffer im Klartext mitgelesen werden!