22.01.2014

"Im Internet steht aber ..." - Teil 2 - etwas versöhnlicher, aber nicht viel

Die Geschichte mit dem verkauften Handy hat noch eine Fortsetzung gefunden.

Offensichtlich bin ich zu gutmütig. Aus meinem Angebot, das Handy einzurichten, ist eine fast einstündige Telefonsitzung geworden, weil die Käuferin des Handys die Passwörter nicht aufschreiben konnte.

Aber von Anfang an ... (ich mach jetzt latürnich nicht nochmal den Scherz mit Asterix und dem "Lorbeerkranz des Cäsar").


Ich verabredete mich nochmals mit dem Bruder, um das Handy vor Ort so weit wie möglich einzurichten. Ich erklärte ihr am Telefon genau, was mitzubringen und zu notieren war.

Obwohl nicht nachgefragt, war auch das alte, kaputte Handy mit im Beutel dabei. Der Bildschirm war so richtig schön kaputt und zersplittert. Aus dem Handy konnte ich dann SIM und SD-Karte aus- und ins neue(re) Handy einbauen.

Was dann natürlich nicht - zumindest nicht leicht erkennbar - auf dem Notizzettel stand, war das Passwort zum Google-Konto. Nach einigem Hin und her mit dem Telefon konnte ich immerhin klären, was Name und Passwort des heimischen WLANs sein sollte, und die PIN der SIM-Karte hat auf Anhieb funktioniert.

Die WLAN-Zugangsdaten habe ich dann quasi blind eingegeben und gehofft, dass keine Fehler beim Aufschreiben oder Abtippen passiert sind. Immerhin hatte das Handy dann mit der SIM-Karte Internetzugang, so dass ich guter Dinge war, den Rest der Einrichtung schnell abzuschließen.

Zuerst wollten wir uns deshalb bei einem erneuten Telefonat darauf einigen, dass ich ein neues Google-Konto einrichte, damit wenigstens ihr geliebtes Whatsapp wieder funktioniert. Ohne Google-Konto kein Play Store, und ohne Play Store kein Whatsapp. Soweit, so klar.

Als ich dann aber fragte, ob im alten Google-Konto wichtige Daten gespeichert seien, kam der Dame langsam, aber gewaltig die Erkenntnis, dass ohne das alte Konto auch keine Kontaktdaten auf das neue Handy synchronisiert werden können. Also: es muss unbedingt wieder ein Zugang zum alten Google-Konto beschafft werden. Es sollten wohl auch soviele Kontakte sein, dass das Wiederbeschaffen und Wiedereingeben richtig schmerzhaft sei.

Google ist eine extrem mißtrauische Firma, wenn man auf "Passwort vergessen" klickt. Eine Wiederherstellungs-Email kann nur geschickt werden, wenn man sich vier Tage lang nicht angemeldet hatte. Ansonsten muss man den Termin der Erstellung des Google-Kontos genau wissen, den Tag der letzten Benutzung, und eine Sicherheitsfrage beantworten. Da ich selbst aber auch eher paranoid bin und mir die Vorstellung schrecklich vorkommt, dass jemand Fremdes mein Emailkonto kapert, finde ich diese Vorsichtsmaßnahmen prinzipiell gut ;)

Zwischendurch musste ich lang und breit das Mißverständnis bekämpfen, dass nach der Einrichtung eines neuen Kontos die Daten des alten Kontos wieder zur Verfügung stünden. Weia!

Wir sind dann so verblieben, dass ich eine kurze Anleitung mitgebe, wie auf dem Handy nachträglich ein Google-Konto eingerichtet wird, wenn sie mit Hilfe ihres anderen Emailkontos ein neues Google-Passwort bekommen hat, und danach Whatsapp einrichtet.

Zwischendurch musste ich mir noch mehrfach anhören, dass ja angeblich "nur die SIM-Karte einlegen, PIN eingeben, und alles ist wie auf dem alten Handy" sowas von gelogen sei. Als ob ich Schuld daran war, dass sie das Google-Passwort nicht zur Hand hat!

Sowas ärgert mich eigentlich am meisten. Vielleicht ist es ja gar nicht an mich gerichtet, aber trotzdem nehme ich solche Vorwürfe persönlich. Natürlich, so einfach ist es wirklich: man bekommt ein neues Handy, SIM rein, einschalten, den Fragen auf dem Bildschirm folgen, PIN, Google-Email und -Passwort eingeben, und danach müsste tatsächlich das Handy mehr oder weniger wieder so aussehen wie das alte, zumindest, was Google-Kontakte, und eigentlich sogar Whatsapp angeht - diese Daten werden nämlich auf dem Server gespeichert und bei Anmeldung wieder zurück auf's Handy gespielt.

Wenn ich geahnt hätte, wie gnadenlos manche Leute Hilfsbereitschaft ausnutzen, hätte ich der Käuferin lieber den billigen Triumph gönnen sollen, dass sie das Handy zurückgeben konnte, auch wenn sie darauf keinen rechtlichen Anspruch hat. Diese ganze Geschichte hat mich soviel Zeit und Nerven gekostet, dass es mich selbst jetzt, ein paar Tage später, immer noch ärgert, dass ich mich darauf eingelassen habe,

Und das alles nur, weil es mir leidtut, wenn gute Technik sinnlos im Regal liegt, statt benutzt zu werden.

20.01.2014

"Im Internet steht aber ..." - beliebte Behauptungen

Letztens - ist jetzt schon ein paar Wochen her - hab ich eines meiner Android-Testgeräte verkauft. Leider verlief die Geschichte sehr unschön, und ich bin nach wie vor ziemlich neben der Spur.

Das Blatt mit der Beschreibung des Telefons (als Spässeken sogar mit qr-code mit Link zur (damaligen) Handygalerie der c't, die jetzt zu techstage.de umgezogen wurde) hing eine ganze Zeitlang irgendwo an einem Schwarzen Brett (so richtig echt aus Holz, kein Forum im Internet ...).

Ich hatte den Aushang schon fast vergessen - erstaunlicherweise hing er ziemlich lang dort - rief dann jemand die Telefonnummer auf dem Inserat an; er wollte dieses Gerät für seine Schwester kaufen, weil ihr Telefon kaputt war und sie dringend und schnell ein neues Gerät brauche. Allerdings kenne er sich selbst mit Handys nicht besonders gut aus.

Nun denn, aufgeladen, zur Kontrolle nochmal eingeschaltet, alle Daten gelöscht, nochmal Reset auf Werkszustand, am nächsten Tag bei einem Treffen das Gerät mit Ladekabel übergeben.

Eine Woche später ein erneuter überraschender Anruf des Im-Auftrag-Käufers - er bittet mich im Namen seiner Schwester, das Gerät zurückzunehmen, weil es ihr zu kompliziert sei. Die Sache ist ihm sichtlich peinlich, weil er jetzt natürlich zwischen den Stühlen steht. Ich biete ihm an, direkt mit der Schwester zu sprechen, damit er aus der Geschichte 'raus ist, und schon eine Stunde später bimmelt mein Handy. Ich kann aber erst abends von zuhause aus anrufen, weil ich ein paar dringende Arbeiten hatte.

Natürlich will ich das Gerät nicht zurücknehmen, ich bin im Gegenteil froh, dass es nicht unbenutzt bei mir zuhause herumliegt. Bei der Übergabe hatte ich auch deutlich gesagt, dass es keinerlei Garantie oder Gewährleistung geben kann, weil ich es auch schon gebraucht gekauft hatte.

Das Gespräch ist dann verständlicherweise ziemlich unerfreulich für beide Seiten gelaufen: die Dame erklärt mir, dass ihre Tochter ihr ebenfalls als Überraschung ein Handy gekauft hat, angeblich dasselbe Modell, das sie vorher hatte (Sony Xperia), und dass sie mit dem LG gar nicht zurecht käme. Es habe ihre Kontakte nicht und auch nicht ihr Whatsapp. Angeblich funktioniert es auch nicht, wenn man die SIM-Karte einlegt.

Nach mehreren Versuchen behauptet sie dann plötzlich, sie habe "im Internet" gefunden, dass ich das Gerät auf jeden Fall zurücknehmen muss, weil sie ein 14-tägiges Rückgaberecht habe.

Au Weia! Ob die aktuelle PIAAC-Studie doch recht hat, was die Lesekompetenz angeht? Beim Verkauf von privat gibt es die Möglichkeit, Gewährleistung vollständig auszuschließen, und das habe ich auch deutlich getan. Ich habe ihr erklärt, dass ich dem Bruder bei der Übergabe diese Tatsache mitgeteilt habe, aber weil ihr Bruder ihr das nicht weitergegeben habe, gelte es für sie nicht, und sie habe Anspruch auf Rückgabe. Dieser merkwürdigen Rechtsauffassung habe ich natürlich vehement widersprochen.

Wir sind dann schlussendlich so verblieben, dass ich ihr erklärt habe, wie sie mit dem Gerät die Grundeinstellung durchführt, also WLAN einrichten, Google-Konto einrichten, und danach aus dem Play Store Whatsapp installieren.

Beim vorherigen Handy habe das angeblich ihre Tochter für sie eingerichtet. Frag ich mich an dieser Stelle im Gespräch doch: warum macht die Tochter das dieses Mal nicht auch? War die Tochter beleidigt, dass die Mutter sich selbst um ein Ersatzgerät gekümmert hat?

Ein äußerst unerfreulicher Verlauf. Als Technik-Freak war ich froh, dass ein Gerät mit einem fast aktuellen Android 4.2.2 und brauchbaren technischen Daten (Dualcore, 800x480, 8 GB, 8 MP, SD) wieder "normal" benutzt wird, statt bei mir zu vergammeln und alle paar Wochen nur mal kurz zum Experimentieren eingeschaltet zu werden.

Noch dazu ein Anruf bei der Käuferin auf dem Handy auf meine Kosten, der über 20 Minuten gedauert hat. Da vergeht mir die Lust, nochmal Gebrauchtteile zu verkaufen, wenn dann hinterher so ein Gemecker kommt.

Letztes Jahr zum Geburtstag hab ich mir ein gebrauchtes Galaxy Nexus gegönnt und dafür das Galaxy S verkauft. Die Käuferin war (nach ein bißchen Gefeilsche um den Preis und einen wönzigen Nachlass von mir) überglücklich mit dem Gerät und ist über's ganze Gesicht strahlend aus dem Haus gegangen. Das hat mich richtig gefreut, dass mein altes Galaxy S sozusagen in "gute Hände" verkauft wurde.

Wenn man sich ein Smartphone kauft, erwarte ich auch den Willen, sich soweit einzuarbeiten, oder es meinetwegen von einem Bekannten einrichten zu lassen. Aber doch nicht beim ersten Anzeichen von Schwierigkeiten gleich alles fallen zu lassen und dem Verkäufer mit hanebüchenen juristischen Behauptungen "aus dem Internet" zu drohen ...

18.01.2014

Erbsen zählen - Perl und CGI - Dritter Teil

Im dritten Teil beschreibe ich nun das Interessanteste: die Auswertung der Daten in Text- und Grafikform, deren Prinzip ich im ersten Teil und Sammlung im zweiten Teil beschrieben habe.

Genau wie schon früher beschrieben, verwende ich das Grafikmodul GD für Perl. Dort kann man ein paar Daten hineinstecken und dann mit einem Aufruf unterschiedliche Schaubilder erzeugen lassen, als "line"-Graph, "bar"-Graph und viele andere. Der Output ist dann HTTP-konform eine Grafikdatei im gewünschten Format, z.B. PNG oder GIF, zusammen mit einer Headerzeile und dem passenden MIME-Typ.

Beim Design gibt es zwei grundsätzliche Überlegungen:
  1. Die Logdateien sammeln Daten pro überwachtem Hostname, ich muss also noch eine Summe bilden, um den Verbrauch pro Host im Verhältnis zum Rest beurteilen zu können.
  2. Ich würde gern sowohl eine Tabelle mit den aktuellen Daten sehen als auch einen Graphen, aus dem man Trends ablesen kann. Ich brauche also im CGI-Skript eine Fallunterscheidung - Text oder Grafik.
Hier kommt das komplette CGI-Skript. An ein paar Stellen füge ich Kommentare in rot oder blau ein, wenn es etwas zu erklären gibt.

Eine grundsätzliche Bemerkung vorneweg: die Datenübergabe erfolgt immer in Form von Referenzen (in C wären das Zeiger auf Datenstrukturen), deshalb verwende ich im Skript i.a. auch gleich Variablen, Arrays und Hashes, die Referenzen enthalten, damit die Übergabe an GD nicht noch unnötig Datenformate umwandeln muss.

Um das CGI-Skript bequem testen zu können, habe ich einen "Testmodus" eingebaut. Man kann generell Skripte, die das CGI-Modul verwenden, auch auf der Kommandozeile aufrufen. Die Argumente, die normalerweise in der Request-URL nach dem "?" folgen, schreibt man einfach als Pärchen mit "name=wert" hinter den Skriptnamen in die Kommandozeile, wie man hier am Beispiel sieht.
# ./accounting.pl scale=1000 testmode=99 xmax=400 ymax=300
Bei diesem Testmodus ist noch zu bedenken, dass natürlich die Umgebungsvariablen des Webservers nicht gesetzt  sind (QUERY_STRING, PATH_INFO, die SSL_*-Variablen, wenn das Skript mit https aufgerufen wurde, usw.). Ggfs. müsste man diese Variablen manuell mit passenden gefälschten Inhalten setzen, damit das Skript an den entsprechenden Stellen sinnvolle Werte bekommt.

Dem Skript kann man einen Parameter "filter" übergeben, um nur einen ganz bestimmten Hostnamen auszufiltern. Deshalb gibt es eine etwas unübersichtliche Fallunterscheidung, ob ein Filter gesetzt ist oder nicht.

Mit "mode=1" wird eine Grafik in voller Größe mit mehreren Graphen und der Gesamtsumme angezeigt; "mode=16" erzeugt eine Tabelle in Textform mit einer Zeile pro Hostname in der Logdatei und hinter jedem Hostnamen in einer Extraspalte eine wönzig kleine Grafik mit der Gesamtsumme und dem Verbrauch dieses Hosts.

#!/usr/bin/perl -w

use strict 'vars';
use strict 'refs';

use GD::Graph;
use GD::Graph::lines;
use GD::Graph::bars;
use GD::Graph::hbars;

# Die GD-Module zeichnen die verschiedenen Typen von Graphen
use CGI qw(:standard);

#use lib "/usr/local/bin";

my @data;

# Array mit Beschriftung, x-Skala und ein oder mehreren y-Datenpunkten
my %hosts;

# Array zum Speichern der Daten pro Hostname
my $graph;
my $format;
my ($xmax,$ymax);

# Größe des auszugebenden Bilds
my $query;
my $range;
my $filter;

# Regexfilter für Hostnamen
# mode=0 full graph mode all hosts
# mode=9 sum only graph
# mode=16 text mode
# mode=99 debug text mode
my $mode;
my @x;
my @y1;

# y1=summierte Daten IN
my @y2;

# y2=summierte Daten OUT
my $scale;
#my $offset;
my $title="LTE volume statistics";

# X-Beschriftung
# werte1
# werte2
# ...
@data=(
);

%hosts=(
# "dummy.moeller-seeling.local" => { "IN " => [ 0 ], "OUT" => [ 0 ] }
);

my $max=0;
my $lines=0;

# logfile inbound, outbound
my ($login,$logout);


$query=new CGI();
$mode=$query->param( 'mode' ) || 0;
$range=$query->param( 'range' ) || "60";
#$offset=$query->param( 'offset' ) || "0";
$scale=$query->param( 'scale' ) || "2500";
$filter=$query->param( 'filter' ) || "";
$xmax=$query->param( 'xmax' ) || "800";
$ymax=$query->param( 'ymax' ) || "600";
$login=$query->param( 'infile' ) || "/var/log/lte-acct.in";
$logout=$query->param( 'outfile' ) || "/var/log/lte-acct.out";
$graph = GD::Graph::lines->new($xmax, $ymax);
$format = $graph->export_format;

# 20140108-110202 IN  i9100.moeller-seeling.local              91M
# 20140108-110202 IN  lifetab.moeller-seeling.local            42M
# 20140108-110202 IN  nexus.moeller-seeling.local              8748K
# 20140108-110202 IN  vettie68.moeller-seeling.local           143M

#my $d;

sub readfile {
  my ($file,$tag,$y)=@_;

  if (open(F,"<",$file)) {
    my ($dt,$old)=("","");

      while (<F>) {
      my ($name,$num,$unit,$hm,$m);

      chomp;
# 20140107-184506 IN  lifetab.moeller-seeling.local            0
# dieser Regex zerlegt eine Zeile, $tag ist IN oder OUT      if ($filter) {
        ($name,$num,$unit,$dt,$hm,$m)=($filter,$7,$8,"$1$2$3-$4$5$6","$4$5",$5)
          if (/^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2}) $tag\s+$filter.*?\s*(\d+)([KMG]?$)/);
      }
      else {
        ($name,$num,$unit,$dt,$hm,$m)=($7,$8,$9,"$1$2$3-$4$5$6","$4$5",$5)
          if (/^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2}) $tag\s+([a-z.-][0-9a-z.-]+)\s*(\d+)([KMG]?)/);
      }
      if ($name && defined($num) && defined($unit)) {

        ++$lines;
        if ($unit eq "")  { $num>>=10; }
#       if ($unit eq "K") { $num<<=10; }
        if ($unit eq "M") { $num<<=10; }
        if ($unit eq "G") { $num<<=20; }
# alle Zahlenwerte sind in KB        if ($num>1000) {
          if ($ymax<100) { push(@x,$m); }
          else { push(@x,$hm); }
          $num>>=10;
          push(@{$hosts{$name}->{$tag}},$num);
if ($mode==99) { print STDERR "# <$_>\n# $hm $tag $filter $num $unit | $max\n"; }

# in den Logfiles stehen kumulierte Daten pro Host
# alle Werte vom selben Timestamp addieren für Gesamtsumme
          if ($dt eq $old) { $num+=pop(@{$y}); }
#         else { $num+=$offset; }
          push(@{$y},$num);
          $old=$dt;

# Timestamp aufheben für Vergleich mit nächster Zeile
          if ($max<$num) { $max=$num; }

# Maximum merken für y-Skalierung
if ($mode==99) { print STDERR "# @{$y}\n"; }
        }
      }

    }
    close(F);
  }
}

readfile($login, "IN ",\@y1);
readfile($logout,"OUT",\@y2);

if ($mode==99) {
  local($,=", ");
  foreach my $h (keys(%hosts)) {
    print STDERR "# h $h\n";
    print STDERR "# i @{$hosts{$h}->{'IN '}}\n";
    print STDERR "# o @{$hosts{$h}->{'OUT'}}\n";
  }
  print STDERR "# Sum\n";
  print STDERR "# i @y1\n";
  print STDERR "# o @y2\n";
}


# maximal die letzten $range werte aus der Datei darstellen


if ($range>0) {
  splice(@x,0,-$range);
  splice(@y1,0,-$range);
  splice(@y2,0,-$range);
  $lines=$range;
}
$data[0]=\@x;

# mode=9 nur die Summe
# mode !=9 alle Hostnamen auch einzeln zeigen
if ($mode!=9) {
  foreach my $h (keys(%hosts)) {

# inbound ist interessanter
# man könnte aber in und out anzeigen
    push(@data,$hosts{$h}->{'IN '});
#   push(@data,$hosts{$h}->{'OUT'});
  }
}
# sum of all hosts IN
push(@data,\@y1);
# sum of all hosts OUT
#push(@data,\@y2);

$max=$scale*int($max/$scale+1);

# sinnvolle obere y-Grenze ausrechnen

if ($mode==99) { print STDERR "# max=$max\n"; }

# Daten übergeben$graph->set(
  x_label       => 'Volume Date/Time, ' . $lines . " samples",
  y_label       => 'max ' . $max . ' GB',
  title         => 'LTE volume (GB)',
  y_max_value   => $max,
  y_tick_number => 20,
  y_label_skip  => ($ymax<200) ? 5: 2,
  x_labels_vertical => 1,
  x_label_skip  => $lines / 12,
) or die $graph->error;

# optional die Farben selbst festlegen
#$graph->set( dclrs => [ qw(green lred blue cyan) ] );

# gibt es überhaupt daten?

if ($lines>0) {
  if ($mode<16) {

# bilddatei erzeugen
# binmode ist nur mit windows-webserver wichtig
    print header("image/$format");
    binmode STDOUT;
    print $graph->plot(\@data)->$format();
  }

# textmodus output als tabelle
  elsif ($mode==16) {
    print CGI::header();

    print <<__HEADER__;
<HTML>
<HEAD>
<TITLE>$title</TITLE>
<META http-equiv="refresh" content="300" />
<style type="text/css">
th {
  padding : 3px;
  text-align : left;
}
td {
  padding : 3px;
  text-align : right;
}
</style>
</HEAD>
__HEADER__

print "<!-- ";
print join( " -->\n<!-- ",
           map( "$_ => $ENV{$_}", sort(keys(%ENV)))
          );
print "-->\n";

    print <<__BODY__;
<BODY>
<H1>$title</H1>
<table border="1">
<tr>
<th> Hostname </th>
<th> Download </th>
<th> Upload </th>
</tr>
__BODY__

    foreach my $h (keys(%hosts)) {
      print "<tr>\n";
      print "<th> $h</th>\n";
      print "<td>",pop(@{$hosts{$h}->{'IN '}})," MB </td>\n";
      print "<td>",pop(@{$hosts{$h}->{'OUT'}})," MB </td>\n";

# hinter jeden hostnamen einen wönzigen Detailgraphen anzeigen
if (defined($ENV{'HTTP_HOST'})) {
      print "<td><IMG SRC=http://",$ENV{'HTTP_HOST'},$ENV{'SCRIPT_NAME'},"?filter=$h&xmax=300&ymax=100&scale=",int($max/10),"</IMG></td>\n";
}
      print "</tr>\n";
    }
    print "<tr>\n";
    print "<th style='text-align:right'> &Sigma; </th>\n";
    print "<td>",pop(@y1)," MB </td>\n";
    print "<td>",pop(@y2)," MB </td>\n";
if (defined($ENV{'HTTP_HOST'})) {
      print "<td><IMG SRC=http://",$ENV{'HTTP_HOST'},$ENV{'SCRIPT_NAME'},"?mode=9&xmax=300&ymax=100&scale=",int($max/10),"</IMG></td>\n";
}
    print "</tr>\n";

    print <<__BODY__;
</table>
<DIV>
<HR/>
<ADDRESS>
ths, last changed 09.01.2014
</ADDRESS>
</DIV>
</BODY>
</HTML>
__BODY__

  }
}


[Update 20140122: Links zum 1. und 2. Teil eingefügt]

Leserbrief zu TTIP, dem Transatlantischen Abkommen zur Privatisierung der Staatssouveränität

In der WZ war neulich eine kurze Meldung, dass eine Papierfabrik vor dem Bundesverfassungsgericht klagt. Hier bei Spiegel Online wird etwas genauer erklärt, warum die Firma Klage eingereicht hat.

Diese kurze Meldung war für mich der Anlass, etwas allgemeiner über das TTIP-Abkommen zu schreiben und das als Leserbrief wegzuschicken.

Leserbrief zur Meldung "Firma PKV zieht vor das Bundesverfassungsgericht" (07.01.2014)

Die WZ berichtet, dass eine Papierfabrik vor das Bundesverfassungsgericht zieht, weil das firmeneigene Kraftwerk gesetzlich in die allgemeine Energieversorgung einbezogen wird, d.h. dass das firmeneigene Kraftwerk bei Überproduktion von außen vom Energieversorger Tennet gedrosselt wird, um weniger Strom einzuspeisen und das öffentliche Netz stabil zu halten. Die Firma betreibt dieses Kraftwerk, um Wärme für die Papierproduktion zu erzeugen, der Strom ist "nur" ein Abfallprodukt. Die Firma klagt vor dem höchsten deutschen Gericht gegen Regelungen im Erneuerbare-Energien-Gesetz wegen Eingriff in ihr Eigentum.

Diese kleine, an sich nicht besonders spannende Meldung (Firma klagt gegen Staat) ist ein guter Anlass, über das "Transatlantische Freihandels- und Partnerschaftsabkommen" TTIP nachzudenken. Dieses Abkommen soll weltweit Standards vereinheitlichen und damit den Handel vereinfachen. In dieser Schlichtheit hört sich das richtig gut an. Allerdings steckt wie üblich der Teufel im Detail: eine Vereinheitlichung bedeutet im Allgemeinen, dass immer der niedrigste Standard aus allen Partnerländern angenommen wird. Dieses Abkommen wird weltweit zwischen USA, EU, Kanada, Mexiko, Schweiz und vielen anderen Ländern abgeschlossen. Leider stammen die Entwürfe nicht von den Parlamenten, sondern von Lobbyvertretern der Industrie unter Ausschluss der Öffentlichkeit, ohne Beteiligung der nationalen Parlamente oder des EU-Parlaments, und damit faktisch ohne demokratische Kontrolle. Es steht zu befürchten, dass durch das Abkommen Umwelt- und Gesundheitsstandards untergraben und Arbeitnehmerrechte aufgeweicht werden. Die Vertragstexte dürfen nicht einmal von den Parlamentariern oder anderen Organisationen eingesehen, geschweige denn von ihnen Änderungen vorgeschlagen werden! Auffällig ist z.B. auch, dass Gewerkschaften, die ja die Arbeitnehmer repräsentieren, zur Arbeitsgruppe für Arbeitsplatz und Wachstum keinen Zugang haben.

In Deutschland formiert sich Widerstand gegen dieses Abkommen, u.a. sprechen sich Gewerkschaften, NGOs und Umweltschutzorganisationen wie ver.di, ATTAC, BUND, DNR uvm. dagegen aus. ver.di bezeichnet TTIP als "Angriff auf Löhne, Soziales und Umwelt". Die wirtschaftsfreundliche Bertelsmann-Stiftung, die übrigens an diesem Abkommen mitschreiben darf, schreckt nicht vor methodisch unsauberen Studien zurück, die schon widerlegt wurden (http://goo.gl/zSfU1R).

Nach meiner Meinung schafft dieses Abkommen nationale Regierungen und demokratisch verabschiedete Gesetze so gut wie ab. Wenn einem internationalen Konzern ein Gesetz nicht passt, wird dagegen geklagt. Würden Staaten also gegen die TTIP-Vertragsregelungen verstoßen, könnten hohe Entschädigungen an Unternehmen fällig werden. Darüber würden sogenannte Schiedsgerichte entscheiden, die keiner nationalen Gesetzgebung und Kontrolle unterworfen wären. Diese Entscheidungen von Schiedsgerichten könnten dann nicht mehr gerichtlich angegriffen werden.

Unternehmen könnten so etwa das staatliche Verbot bzw. die Kennzeichnungspflicht gentechnisch veränderter Lebensmittel oder der Gasförderung mittels Fracking verhindern, oder Entschädigungszahlungen für den Ausstieg aus der Kernenergie erzwingen. Ein weiterer Verhandlungsgegenstand von TTIP ist z.B. die Rücknahme von Kontrollen und Regeln für den Finanzsektor. In den USA sind 90% aller Lebensmittel gentechnisch verändert und müssen trotzdem nicht gekennzeichnet werden. Wollen wir die Konzerne weiter entfesseln und unsere Souveränität privatisieren?

Zwei reale Beispiele, was mit diesen Regelungen auch auf uns zukommen könnte:

Die kanadische Provinz Québec hat ein Moratorium für das Fracking von Schiefergas und Öl erlassen. Deshalb klagt das US-Unternehmen Lone Pine, welches zuvor eine Probebohrungslizenz erworben hatte, vor einem internationalen Schiedsgericht gegen den Staat Kanada und fordert 250 Millionen Dollar für den zu erwartenden Gewinnausfall. Wohlgemerkt, es geht nicht um die Rückzahlung von Gebühren oder Entschädigung für schon getätigte Ausgaben, sondern um den entgangenen Gewinn!

Der Tabakkonzern Phillip Morris klagt auf Entschädigung in Milliardenhöhe gegen den Staat Australien aufgrund entgangener Gewinne durch strengere Gesetze zum Tabakkonsum.

10.01.2014

Erbsen zählen - zweiter Teil

Nachdem ich also im 1. Teil das Grundprinzip des Erbsenzählens beschrieben habe, kommt hier ein bißchen mehr Skripting zum Einsatz, damit sich der Tag "Automation" auch lohnt ;)

Das Datensammeln an sich ist eine triviale Sache. Man braucht ein kleines Skript, das mit Hilfe von cron regelmäßig ausgeführt wird.

Ich habe einfach mal willkürlich festgelegt, dass es einmal stündlich laufen soll:

2 * * * * /usr/local/sbin/ip-accounting.sh -I /var/log/lte-acct.in -O /var/log/lte-acct.out >/dev/null 2>&1

Der Inhalt des Skripts sieht ein bißchen aufgebläht aus, weil ein paar Kommandozeilenargumente ausgewertet werden wollen. Die wichtigste Zeile im Skript habe ich nochmal extra rot markiert, das ist die eigentliche Datensammlung.
#!/bin/sh

PATH=/opt/sbin:/opt/bin:/usr/bin:/usr/sbin${PATH:+:$PATH}

bytesin=0
bytesout=0
while getopts "iI:oO:" opt
do
  case "$opt" in
  i) bytesin=1;;
  I) filein="$OPTARG";;
  o) bytesout=1;;
  O) fileout="$OPTARG";;
  esac
done
shift $(expr $OPTIND - 1)
if [ "$bytesin$bytesout$filein$fileout" = "00" ]
then
  bytesin=1
  bytesout=1
fi

dt=$(date +%Y%m%d-%H%M%S)

out=$(iptables -L INET_OUT -v | awk -v D="$dt" '{if(NR>2){printf"%s OUT %-40s %s\n",D,$7,$2}}'|sort)
if [ "$bytesout" -eq 1 ]
then
  echo "$out"
fi
if [ -n "$fileout" ]
then
  echo "$out" >> "$fileout"
fi

out=$(iptables -L INET_IN  -v | awk -v D="$dt" '{if(NR>2){printf"%s IN  %-40s %s\n",D,$8,$2}}'|sort)
if [ "$bytesin" -eq 1 ]
then
  echo "$out"
fi
if [ -n "$filein" ]
then
  echo "$out" >> "$filein"
fi
Und was genau macht diese rot markierte Zeile? Schauen wir mal genauer hin. Es sind mehrere Befehle in einer Zeile, verbunden mit dem Weiterleitungszeichen | (pipe). Diese Pipe verbindet den Output des Programms vorne mit dem Input des Programms dahinter.
iptables -L INET_IN  -v |

Der erste Befehl zeigt die bis dahin aufgelaufene Datenmenge für alle überwachten Systeme an. Wichtig ist der Switch -v, sonst bekomme ich die Datenmenge nicht zu sehen.
awk -v D="$dt" '{if(NR>2){printf"%s IN  %-40s %s\n",D,$8,$2}}' |

Die Ausgabe enthält allerdings ziemlich viel Kram, der mich gar nicht interessiert, deswegen verwende ich awk, um mir gezielt nur das zu extrahieren, was ich für meine Neugier brauche. Natürlich könnte ich normalerweise auch grep verwenden, um etwas zu suchen, aber weil ich gleichzeitig ein wenig programmieren will und einzelne Felder aus jeder Zeile des Outputs ausschneiden will, verwende ich ganz gern awk, statt grep mit cut o.ä. zu kombinieren. Spart ein wenig CPU ;)

Bei diesem Befehl benutze ich zwei eingebaute Fähigkeiten von awk: es zählt automatisch die Zeilennummern in der Variablen NR (number of records) mit, was ich verwende, um die ersten zwei Zeilen mit der Überschrift des iptables-Outputs wegzuwerfen, und es zerlegt automatisch die Zeile in "Felder" gemäß einem vorgegebenen Trennzeichen (üblicherweise Whitespace). awk arbeitet also den printf-Befehl nur ab der dritten Zeile des Inputs ab und zeigt davon nur das achte und das zweite Feld an (Hostname und Datenmenge)

Bei der Chain INET_OUT ist  übrigens  der Hostname im siebten Feld enthalten, und bei der Chain INET_IN im achten Feld, weil ich ja einmal nach "source" und einmal nach "destination" suche.

Randbemerkung: damit awk einen Timestamp mit ausgeben kann, gebe ich dem Aufruf von awk einen solchen vorgefertigten Timestamp auf der Kommandozeile mit dem Switch -v NAME=WERT mit. Das können nicht alle awk unter allen Unix-Varianten, m.W. wurde das mit dem "new awk" (nawk) unter SunOS eingeführt, GNU awk unter Linux kann es auf jeden Fall.
sort
Der letzte Befehl sortiert das ganze schlicht und einfach alphabetisch. Ich könnte auch iptables mit dem Switch -n verwenden, um die IP-Adressen zu sehen anstatt der Hostnamen. Dann wäre es angebracht, bei sort ebenfalls -n zu verwenden, um numerische Sortierung zu erzwingen.

Dieses Skript sammelt also stündlich Daten pro überwachtem Hostname. Die gesammelten Daten sehen dann so aus:
server:/share/www/cgi-bin # tail -n2 /var/log/lte-acct.in /var/log/lte-acct.out
==> /var/log/lte-acct.in <==
20140109-150202 IN  nexus.moeller-seeling.local              23M
20140109-150202 IN  vettie68.moeller-seeling.local           286M


==> /var/log/lte-acct.out <==
20140109-150202 OUT nexus.moeller-seeling.local              6752K
20140109-150202 OUT vettie68.moeller-seeling.local           13M

Wie man sieht, ist der Output menschenfreundlich und es wird immer eine halbwegs lesbare Größenordnung bei der Datenmenge angezeigt: KB, MB oder GB. Ohne Einheit sind "Byte" gemeint. Diese Kleinigkeit ist beim Programmieren einer kleiner Stolperstein gewesen ;)

Das Auswerte-Skript habe ich als CGI-Skript in Perl gelöst. Es kann sowohl eine Tabelle als auch eine Grafik erzeugen (mit dem Perl-Modul GD). Das folgt dann im nächsten Teil.

Am Ersten des Monats sollte man übrigens die Zähler wieder auf Null setzen:
iptables -L INET_IN -Z
iptables -L INET_OUT -Z
Ich habe schon ein Skript, das mir meinen Verbrauchszähler auf Null setzt, dort werde ich das noch mit einbauen.

09.01.2014

Erbsen zählen mit Linux - IP Traffic accounting

Manche unter uns wurden geplagt von langsamem DSL.

Ein paar von denen sind mutig auf LTE umgestiegen, das die Telekom und andere Anbieter in unterversorgten Gebieten anbieten müssen (zwar nur im 800 MHz-Band, aber immerhin).

Und ein paar von denen sind neugierig, welcher Mitbewohner denn nun den Traffic verursacht und schon am 20. die Quote für den ganzen Monat aufgebraucht hat ;)

Das alles und noch viel mehr kann man mit einem Linux-Server machen, wenn man seine PCs und Smartphones im Haushalt nicht direkt ins Internet lässt, sondern den Linux-Rechner als Gateway dazwischenklemmt.

Dieser Ansatz hat nebenher noch den Vorteil, dass auf dem Linux-Server ein Caching Proxy laufen kann und damit potentiell ein bißchen vom Traffic nur einmal über die externe Internetverbindung abgerufen werden muss.

Hauptsächlich ist aber das interessanteste, dass nun wirklich jede Internetverbindung am Linux sichtbar ist.

Mit dem Modul "iptables", das bei jeder Linux-Distribution dabei sein sollte, kann man eine Statistik führen, welcher Rechner oder welches Smartphone wieviel Traffic verursacht hat.

Normalerweise wird "iptables" dazu verwendet, mit Regeln festzulegen, was erlaubt ist und was nicht. Nebenbei wird aber auch mitgezählt, wieviele Bytes durch die Regeln geflossen sind. Man kann diesen Mechanismus also für den Nebeneffekt des Zählens verwenden, wenn man schlicht "keine Regel" festlegt.

Nebenbei könnte man aber natürlich auch am Proxy datenintensive Sites wie spotify usw. sperren, die besonders viel vom Datenvolumen verbrauchen.

Grundsätzlich muss man dazu einen Linux-Server mit zwei Ethernet-Schnittstellen aufbauen. Manche Mainboards haben schon zwei davon; falls nicht, kann man für sehr wenig Geld eine zweite Karte in den PC einbauen. Es bietet sich an, den Linux-Server dann auch gleich zum NAS auszubauen mit einer zweiten Festplatte (für ein gespiegeltes RAID1) oder mit mehreren (wenn man ein RAID5 haben will).

Das wichtigste kommt nun: der Router (DSL oder LTE) kommt an die zweite Schnittstelle (oBdA ab jetzt eth1 genannt), und niemand sonst darf direkt mit dem Router verbunden sein. Es ist also sinnvoll, das WLAN am Router abzuschalten oder das Passwort so zu ändern, dass sich niemand mehr anmelden kann. Außerdem muss am Linux-Server das IP-Forwarding eingeschaltet sein, damit die anderen Systeme im Haushalt die Chance haben, trotzdem ins Internet zu kommen. Wenn man das WLAN am Router abschaltet, handelt man sich noch ein zu lösendes Problem ein: man benötigt einen separaten WLAN-Access Point für das interne Netz, der für Smartphones die Basisstation wird.

Alle anderen PCs und der Linux-Server mit seiner ersten Schnittstelle (eth0) müssen an einen "internen" Switch angeschlossen werden. Das kann der interne, neue WLAN-Access Point sein, oder eine eigene kleine Kiste, ganz nach Geschmack und örtlichen Gegebenheiten.

Am Linux muss nun prinzipiell das IP-Forwarding eingeschaltet werden. Dies bedeutet, dass aller Traffic, der an eth0 ankommt und nicht für eine andere interne Station gedacht ist, automatisch an eth1 weitergeleitet wird (und von dort natürlich weiter ins Internet).

Das deute ich hier nur an, für Details mal ein bißchen im Internet nach "linux ip forwarding" suchen.
    iptables -A FORWARD -j ACCEPT -m state --state ESTABLISHED
    echo 1 > /proc/sys/net/ipv4/ip_forward
    net=192.168.1.0/24
    iptables        -A FORWARD     -j ACCEPT     -i eth0 -s "$net"
    iptables -t nat -A POSTROUTING -j MASQUERADE -o eth1 -s "$net"

    iptables -A FORWARD -j REJECT
Statt des gesamten Netzes (in der Variablen $net) kann man auch gezielt einzelne Hostnamen oder IPs angeben, aber das Setup des IP-Forwardings soll hier nicht das Thema sein.


Stattdessen will ich ein bißchen genauer auf das eigentliche Thema eingehen: das Zählen des Traffics.

Dazu richtet man mit iptables zwei zusätzliche "Chains" ein, und klinkt beide in die immer vorhandene FORWARD-Chain mit ein. Das bedeutet so etwas wie ein Verdoppeln der Daten, wobei die Duplikate in den neuen Chains (eine für Input, eine für Output) nur für das Zählen verwendet und dann weggeworfen werden.
    iptables -N INET_OUT
    iptables -N INET_IN 
    iptables -I FORWARD -j INET_IN
    iptables -I FORWARD -j INET_OUT
Diese beiden neuen Chains stattet man als nächstes mit der Information aus, für welche IP-Adresse überhaupt gezählt werden soll. Dabei muss zum Zählen pro IP-Adresse natürlich dieselbige angegeben werden als "Selektor". Einmal muss der Selektor als "source" (ausgehender Traffic) und einmal als "destination" (eingehender Traffic) verwendet werden (die Variable $ip geeignet setzen).
    iptables -I INET_OUT -s "$ip"
    iptables -I INET_IN  -d "$ip"
Danach kann man schon mal ein bißchen mit dem Gerät spielen (am Smartphone den App Store/Play Store aufrufen oder eine andere App mit Internetabrufen) und beobachten, ob sich bei den Zählern etwas bewegt.
iptables -L INET_IN -v
Um das regelmäßig anzuschauen, kann man den Befehl "watch" verwenden, der ein anderes Kommando regelmäßig aufruft. Meinem Smartphone habe ich im DNS den Namen "nexus" gegeben, und nach diesem Namen suche ich nun:

# watch -n 60 "iptables -L INET_IN -v|grep nexus"

Every 60.0s: iptables -L INET_IN -v|grep nexus
1567 1198K all -- any any anywhere nexus.moeller-seeling.local
Das Interessanteste dürfte der "inbound" sein, der hereinkommt. Typischerweise ist das Downloadvolumen eher groß, und der "outbound" besteht hauptsächlich darin, die Anfragen zu verschicken. Außer natürlich, man lädt gerade große Bilder zu Flickr oder Videos zu Youtube hoch ;)

Als nächstes richtet man dann einen cron-job ein, der diese Daten in regelmäßigen Abständen protokolliert und daraus lustige Reports erstellt. Davon erzähle ich im nächsten Beitrag, wenn ich mit Programmieren fertig bin ;)


[Update: Idee von Robert Dahlem von der LUG Frankfurt, außerdem diese FAQ]