Kinder sind neugierig und an ihrer Umwelt interessiert. Als kürzlich auf logo ein Beitrag über Fracking kam, versagte irgendwie die Vorstellung. Als ich dann über ein Experiment stolperte, wollte ich es mit den Kindern zusammen nachstellen.

Man nehme

SNC01004
  • einige Tüten Gelatine-Pulver und Wasser (für die Erde)
  • eine Packung Mentos (für die Rohstoffe)
  • eine Cola (für die Fracking-Chemikalien)
  • einen dicken Strohalm (als Bohrgestänge)
  • einen Tesafilm (für die Abdichtmaßnahmen)
  • einen Bierdeckel (als Bohrplattform)
  • eine Schaschlik-Nadel (als Bohrer)
  • eine leere saubere Flasche mit breitem Hals (Milchflasche oder Apfelmus-Glas)

Vorbereitung

Zuerst rührt man die Gelatine mit Wasser an, so daß es richtig fest wird. Also ruhig mehr Gelatine nehmen als normal.

Wenn man die Gelatine schüttelt oder rührt, bildet sich ein fester Schaum, der sich nicht mehr von selbst auflöst. In dem Fall sollte die Flasche in heiße Wasserbad, bis sich die Gelatine löst. (Das Bild zeigt den Zustand vor der Erwärmung).

SNC01011

Bevor aber die Gelatine fest wird, muß der Strohalm eingesetzt werden. Da das "Bohrgestänge" frei von "Erde" sein soll, darf die Gelatine nicht in den Strohalm gelangen.

Dazu klebt man das untere Ende des Strohhalms mit einem Tesa zu. Zur Dichtheitsprüfung versucht man nun in den Strohalm zu pusten, wenn das nicht klappt, ist der Verschluß in Ordnung.

Und damit das "Bohrgestänge" fest in seiner Position gehalten wird, wird es auf die "Bohrplattform" montiert. Dazu sticht man ein kleines Loch in den Bierdeckel und steckt den Strohalm durch. Mit etwas Geduld kann man dann das "Bohrgestänge" schön mittig plazieren.

Ist die Gelatine fest, kann gebohrt werden.

Dazu nimmt man eine Schaschlik-Nadel und schaut, wie tief man kommen könnte. Wenn diese nicht lang genug ist, einfach zwei mit Tesa zusammenkleben (wie im Bild).

SNC01018

Anschließend schiebt man den "Bohrer" ins "Bohrgestänge" und zerstört vorsichtig (den Halm festhalten), den Tesafilm an unteren Ende. Dann kann man gleich noch viel tiefer ins "Gestein" bohren.

Einige schmale Streifen oder Krümel eines Mentos werden durch den Strohalm gesteckt und mit dem "Bohrer" in der "Erde" vergraben.

Nun kann es losgehen.

Durchführung

Man füllt etwas "Fracking-Chemikalie" also Cola in das "Bohrgestänge". Wer hat kann dazu eine kleine Spritze nehmen oder einen Löffel. Versehentliche Kleckereien fängt der Bierdeckel ab.

Mit dem "Bohrer" muß man nachhelfen, weil die Cola von allein durch den Halm läuft. Wer das ganz professionell handhaben will, läßt die Cola durch einen kleineren Strohalm im größeren laufen, dann gibt es keine Probleme mit Luftblasen.

SNC01021

Anschließend wird die "Fracking-Chemikale" mit einem vorsichtigem Puster "unter hohem Druck in die Erde gepreßt".

Man kann dann sehr schön beobachten, wie die Cola sich in der Gelatine verteilt.

Richtig interessant wird es aber, wenn die Cola einen Mentos-Splitter erreicht. Es bilden sich dann schnell richtige "Risse" und "Gaskavernen".

SNC01027

Gelingt es nicht, dieses Gas ins Bohrgestänge zu dirigieren (z.B. durch anbohren oder schräg halten), wandert das Gas und die Cola unkontrolliert nach oben.

Es gibt dann eine "Gaseruption" und eine "Kontaminierung mit Fracking-Chemikalien" weit vom Bohrgestänge entfernt.

Weitere Ideen

Wer Spaß daran hat, kann versuchen die Gelatine in mehreren Schichten einzubringen und aushärten zu lassen. So kann man z.B. Sand-Barrieren einziehen, die das freigesetzte "Gas" auffangen. Es ist auch möglich, ein zweites "Bohrgestänge" in diese Sandschicht einzulassen und so zuverlässig das "Gas" abzuführen.

Ach ja, ganz so untypisch ist der Versuch nicht. Man kann das entweichende Fracking-Gas auch aus dem Weltraum deutlich sehen.

Der Artikel hier hat ein geplantes Veröffentlichungsdatum vom März diesen Jahres, die ersten Entwürfe sind schon zwei Jahre als. Zur Sache: DNSSEC mache ich ja schon länger. Und über Zertifikate im DNS habe ich auch schon vor Jahren nachgedacht. Nun wird es endlich real.

Für das ICANN Meeting 2009 in Mexiko hatte ich zur Nutzung von DNSSEC als Ersatz für Zertifikatsinstanzen referiert. Allerdings bin ich mit vernichtender Kritik untergegangen. Also lag es auf Halde.

Im Laufe der Jahre hat die CA-Industrie sich einen Fehler nach dem anderen geleistet und so viel Vertrauen verspielt, daß es inzwischen sinnvoller erschien, lieber den Registrar-Strukturen des DNS zu vertrauen. Bis dato war man davon ausgegangen, daß die Beteiligten am DNS nicht in der Lage seien, die hohen Anforderungen zu erfüllen. Schließlich stellen Zertifikate die Grundlage für Verträge dar, indem die CA dafür haftet, eine ladungsfähige Adresse des Gegenübers bereitzustellen.

Indem man Zertifikate und DNS Delegation in die gleichen Hände gibt, wirkt sich jeder Fehler im DNS fatal auf die Zertifikatsstrukturen aus. Deswegen hatte der ursprüngliche Vorschlag zu TLSA nur die Abwehr von CA-Fehlern im Sinne: Man sollte erkennen können, wenn die vorgelegte Zertifikatskette von unzulässigen CAs oder für die falschen Schlüssel ausgestellt wurde.

Bei mir liegt der Fall aber anders. Wir haben hier eine CA, die nicht in den gängigen Browsern vorinstalliert ist. Man müßte also in den TLSA Angaben das komplette CA-Zertifikat hinterlegen und das als Trust-Anchor deklarieren. Damit könnte dann jemand extern die Zertifikate prüfen.

Ich möchte nur die CA bekannt geben, habe aber nicht die Möglichkeit, das Zertifikat komplett anzugeben. Durch das jährliche CA-Key-Rollover gibt es aber mehrere verwendete CA-Schlüssel: Die aktuelle Root-CA als Wurzel und die durch die des Vorjahres crosszertifizierte gleiche CA. Was aber konstant bleibt, ist das Paar aus Subject und Key.

Nachdem ich das ganze Spiel durchgeführt habe, sieht die DNS Struktur so aus:

_443._tcp.lutz.donnerhacke.de. CNAME ca.iks-jena.de.
ca.iks-jena.de.         CNAME   2014.ca.iks-jena.de.
2014.ca.iks-jena.de.    TLSA  2 1 0 30820...

Im Test https://www.had-pilot.com/dane/danelaw.html klappte es allerdings nicht.

Der Grund war allerdings simpel: Das Testtool vergleicht den kompletten Subject-String "/C=DE/L=Jena...." aus dem Zertifikat mit dem DNS Namen ... und scheitert.

Nach dem Einfügen eines SubjectAltName in das Zertifikat ist nun auch das Tool glücklich:

Getting URL: https://lutz.donnerhacke.de
Here is the Full Server Cert List 1 gnucall: certs in chain: 1
End Cert Common Name: CN=CA der IKS Service GmbH (SIGN) 2014/emailAddr...
End Cert SubjectAltName: DNS:lutz.donnerhacke.de
Target Domain Name: lutz.donnerhacke.de
Exact Match: Chain Length = 1
DNS Result = NOERROR, DNSSEC = DNSSEC Validated Response (Flags = qr r...
Server Name Indication: SubjectAltName matches target domain: lutz.don
TLSA Values: cu=2, sel=1, mt=0
depth=1 verify=1 err=0 subject=/C=DE/ST=Thuringia/L=Jena/O=IKS Service...
depth=0 verify=1 err=0 subject=/C=DE/ST=Thuringa/L=Jena/O=Lutz Donnerh...

Certificate Verification Status: (0) OK
tlsa=
30820122300D06092A864886F70D01010105000382010F003082010A0282010100CBBC...
Public Key of Verifying Root Cert from TLS handshake is:
30820122300D06092A864886F70D01010105000382010F003082010A0282010100CBBC...
DANE Matched.
lutz.donnerhacke.de authenticated.

Wunderbar.

Bleibt noch der Test mit dem Browser.

Unglücklicherweise gibt es das Plugin für den Firefox nicht für mein aktuellen Betriebssystem: FreeBSD. Deswegen habe ich es mir mal aus den Quellen gebaut:

Was allerdings noch nicht klappt, ist meine Seite damit zu validieren.

Einen Tag später

Nach dem Ausdruck meines Leidens auf der Mailingliste des Validator-Plugins bekam ich den Hinweis, daß der Client ja nur ein Bruchstück des CA-Keys habe.

Dies reiche zwar theoretisch zur Validierung aus, was ein Test zeigt. Ein anderer Test scheitert dagegen grandios. Ebenso auch das Plugin.

Der Trick besteht darin, den CA Key auch mitzuliefern.

SSLCertificateChainFile /etc/httpd/ssl/iksca.2014

Und nun sind beide Tests "grün". Wie schön.

Das Plugin selbst ist aber noch nicht in der Lage zu validieren. Das geht erst in einer Developer-Version.

Mal schauen, was sich da noch drehen läßt.

Whenever you offer IPTV streaming to customers, you need to monitor it. Unfortunately it becomes complicated, if you can not  control all components. We were urged to measure the quality of the IPTV signal, but only have access to a single switch in the distribution network.

Short Outages

The main problem of IPTV is, that customers will notice every outage, even if it lasts a second. And every such outage will result in a serious complaint to the service desk.

Therefore the most important issue was to inform the service desk as soon as possible about missing multicast packets. A few seconds after the outage an alarm has to be raised. This way the service desk is informed before the customer complains. Due to the customer reaction time, the alarm needs to be delivered within five seconds.

What we did was simple: Take an old, low performing system, join all available streams (radio as well as TV) because it does not saturate the Gigabit, and collect per stream when the last packet was seen. If a stream does not received a packet for more than five seconds, report the stream and the corresponding parameters. Those reports are injected into the alarm system, which in turn sends SMS, email or open a popup window. Finally the data can be plotted: Using logarithmic axes to enhance the short outages.

iptv-missing-channels

As secondary result the reporting also tells which streamer is affected and how many streams are still available from this device. If the number of streams drops to zero, urgent replacement of broken hardware can be scheduled. The alarm system can inform a different set of people about this type of events.

Details

Customers do not only complain if the movie freezes or the TV goes black, they even call if the picture or audio is garbled.

That's why our software was extended to examine MPEG-TS packets itself. There is a lot of interesting information inside:

Datenstrom liegt von a.b.c.d an.

Statistik über 3 Sekunden:
 Empfangene UDP Pakete      3446   Paket zu kurz                 0
 Verarbeitete MPEG Pakete  24122   Fehlende Sync-Markierung      0

Statistiken pro Datenstrom (MPEG-Programm)
 Programmkennung (PID)      6630 6610 6622 6620 6621 6600    0   32
                            19e6 19d2 19de 19dc 19dd 19c8 0000 0020
 Empfangene Pakete           56721738  865  494  377   29   28   24
 verschluesselte Daten         021366  829  448  331    0    0    0
 Ansetzpunkte, FullFrames      0    5   18   23   23    0    0    0
 priorisierte Daten            0    5    0    0    0    0    0    0
 Signalstoerung am Streamer    0    0    0    0    0    0    0    0
 Taktstoerung am Streamer      0    0    0    0    0    0    0    0
 Sequenzfehler(CC)             0    0    0    0    0    0    0    0

     Abweichungen vom Synchronisationstakt 19d2 (absoluter Jitter)
26%  ||                                                                   
20%  ||                                                                   
14% ||||                                                                  
 8% ||||||                                                                
 2% ||||||                                                                
   +----------------------------------------------------------------------
 ms 0123456789012345678901234567890123456789012345678901234567890123456789
    0         1         2         3         4         5         6         

     Abweichungen der Paketankunftszeiten (relativer Jitter)
66% |                                                                     
52% |                                                                     
37% |                                                                     
22% |                                                                     
 7% ||  ||||                                                              
   +----------------------------------------------------------------------
 ms 0123456789012345678901234567890123456789012345678901234567890123456789
    0         1         2         3         4         5         6         

Kanal ist durch Kunden im Moment abonniert.

Such a HDTV channel has high data rate and a low jitter. A radio stream is quite different:

Datenstrom liegt von a.b.c.e an.

Statistik über 3 Sekunden:
 Empfangene UDP Pakete        43   Paket zu kurz                 0
 Verarbeitete MPEG Pakete    301   Fehlende Sync-Markierung      0

Statistiken pro Datenstrom (MPEG-Programm)
 Programmkennung (PID)       851    0  850
                            0353 0000 0352
 Empfangene Pakete           282   13    6
 verschluesselte Daten         0    0    0
 Ansetzpunkte, FullFrames      0    0    0
 priorisierte Daten            0    0    0
 Signalstoerung am Streamer    0    0    0
 Taktstoerung am Streamer      0    0    0
 Sequenzfehler(CC)             4    0    4

     Abweichungen vom Synchronisationstakt 0353 (absoluter Jitter)
59%                                                                      |
46%                                                                      |
33%                                                                      |
19%                                                                      |
 6% |    |     | |     |     ||   ||             ||   |      || |        |
   +----------------------------------------------------------------------
 ms 0123456789012345678901234567890123456789012345678901234567890123456789
    0         1         2         3         4         5         6         

     Abweichungen der Paketankunftszeiten (relativer Jitter)
15%         |                                                             
11%         |                                                             
 8%       | | |        |                                                  
 5%     | | | |  |  | || |                                                
 1%  | || ||| ||||||||||||  ||  | |                |                      
   +----------------------------------------------------------------------
 ms 0123456789012345678901234567890123456789012345678901234567890123456789
    0         1         2         3         4         5         6         

Kanal ist durch Kunden im Moment nicht abonniert.

MPEG-Transport-Streams (representing a single TV channel) consist of several programs (sub streams), which are reported separately. The program 0000 contains the index, the information which program is used for which purpose. But even without decoding the index, it's easy to derive that the highest bandwidth program will represent the picture stream. Other programs are teletext, subtitles, EPG, on or more audio channels. ZDF-HD is very rich in respect of those programs.

Statistics

Plotting those data is an obvious wish, simply to investigate customer complains post mortem. In order to find out, what was going wrong last evening, historical data is needed.

Aggregating data over a minute is use to limit the amount of recorded data: The database has to store a single group of measurements per program and minute. And those data can be viewed.

For the first example that the bandwidth as (packets * size / time). Please notice how the bandwidth of an HD-channel is dominated by the movie/picture stream.

iptv-history-data-rate

Between 17:30 and 20:00, the bandwidth dropped, and recurred with half the nominal bandwidth. A reasonable explanation is, that the broadcasting organization switched to SDTV material, i.e. coming from an different source.

Some broadcasting organizations, especially the third programs in the German TV market, do copy the whole stream from the ARD for common broadcasting (i.e. Tagesschau). The switch does show even the different program id.

iptv-history-kanaluebernahme

But let's go back to the bandwidth drop. The number of full frames (restart points for the decoder) may reveal, if there is a problem in the local distribution or at the broadcasting organization. The network itself is usually unable transcode the stream and inject full frames by itself.

iptv-history-fullframes

During this period, the broadcasting organization did repeatedly retry to sync their own signal. The level of resync-points is obviously lower than the full HD stream, but the other programs contains much more resync information than normal.

It's possible, that the streamer lost the (analog) signal. In this case a special bit is set in the MPEG-TS to indicate such a loss of signal, which allows the decoder to restart from scratch instead of waiting for additional information.

iptv-history-signal-streamer

There was such as loss of (analog) signal at the very beginning of the disruption. But the following effects are solely caused by the received signal itself.

There is an other source of trouble which can occur at the broadcasting organization: In order to synchronize the independent programs of the MPEG-TS, a common 33 MHz clock is used. Using this clock, audio and video streams do not diverge. Such a high clock may (according to the standard) be also used to regenerate or calibrate the clock in cheap decoder hardware. But even this clock might be changed. In this case a special bit is set by the broadcasting organization to inform the decoders.

iptv-history-takt-streamer

Even this bit clearly shows, that the disruption was caused by the broadcasting organization. An error in the local distribution infrastructure can be denied.

A still open question is, how to detect packet loss and packet reorder, the most common problems with IP networks. Those are detected by monitoring the sequence numbers (CC) of the MPEG-TS. Unfortunately the sequence number space runs cyclic from 0 to 15, while up to seven MPEG-TS packets are packed into a single UDP packet. This renders this number nearly useless.

iptv-history-cc-error

The most interesting point on this graph is the small amount of CC errors during the disruption. Several data packets are lost or reordered. A possible reason might be, that the programs are resynced over and over again from the broadcasting organization itself. The sequence counter can be reset in this case.

Finally let's take a look to the jitter:

iptv-history-jitter

No more questions.

Summary

It's possible to monitor several hundred of IPTV streams without spending lot's of money. Just do it.

Wie bei allen populären Dienste wird auch nach offenen MySQL-Installationen gescannt. Diese Server sind oft auf dem Internet erreichbar, sollen aber nur für bestimmte Quelladressen und Nutzer offen stehen. Das geht mit Bordmitteln. In der Standardeinstellung kann ein MySQL Server leicht blockiert werden.

Wir werden angegriffen!

Ein Kunde meldet auf seinem Server eine ungewöhnliche Fehlermeldung.

mysql_connect(): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (11)

#0 [internal function]: __error(2, 'mysql_connect()...', '.../www/...', 58, Array)
#1 .../library/Contao/Database/Mysql.php(58): mysql_connect('localhost:3306', 'LOGIN', 'PASSWORD')
#2 system/modules/core/library/Contao/Database.php(77): Contao\Database\Mysql->connect()
#3 system/modules/core/library/Contao/Database.php(161): Contao\Database->__construct(Array)
#4 [internal function]: Contao\Database::getInstance()

Ja, tatsächlich ein Stacktrace mit vollem Namen und Paßwort. Und das ist nicht so selten, wie eine Suche zeigt.

Aber wie kommt es dazu? Kann ein MySQL Server wirklich eine lokale Verbindung ablehnen?

Ursachenforschung

MySQL lehnt neue Verbindungen ab, wenn sein max-connection Limit erreicht ist.

Ein Blick in die Prozeßtabelle offenbart einen sehr ungewöhnlichen Anblick:

mysql> show processlist;
+--------+----------------------+---------------------------+----------+---------+------+------
| Id     | User                 | Host                      | db       | Command | Time | State
+--------+----------------------+---------------------------+----------+---------+------+------
| 374649 | root                 | localhost                 | mysql    | Query   |    0 | NULL
| 374693 | unauthenticated user | *******************:41209 | NULL     | Connect | NULL | Reading
| 374694 | unauthenticated user | *******************:41212 | NULL     | Connect | NULL | Reading
| 374695 | unauthenticated user | *******************:41213 | NULL     | Connect | NULL | Reading
| 374696 | unauthenticated user | *******************:41216 | NULL     | Connect | NULL | Reading
| 374697 | unauthenticated user | *******************:41217 | NULL     | Connect | NULL | Reading
| 374698 | unauthenticated user | *******************:41219 | NULL     | Connect | NULL | Reading
| 374699 | unauthenticated user | *******************:41221 | NULL     | Connect | NULL | Reading
| 374700 | unauthenticated user | *******************:41224 | NULL     | Connect | NULL | Reading
| 374701 | unauthenticated user | *******************:41225 | NULL     | Connect | NULL | Reading
| 374702 | unauthenticated user | *******************:41228 | NULL     | Connect | NULL | Reading
[...]

Der komplette Server ist mit dem Verarbeiten von Anmeldungen aus dem Internet belegt. Es würde also nichts helfen, die Limits für max-connections anzuheben.

Wenn man das selbst sehen will, kann man von Hand einfach mal einen Verbindungsaufbau probieren:

$ telnet mysql.ser.ver 3306
8
<binärdaten>

Wie bekommt man aber diese vielen Verbindungen weg?

Lösung

Die häufigste Antwort, die man bekommt (auch von MySQL), ist, eine Firewall vor den Server zu stellen und den Port zu blocken. Das ist aber offenkundig unbefriedigend.

Die zweithäufigste Antwort ist, die Netzwerkfunktionalität von MySQL abzuschalten. Das macht aber erhebliche Probleme, weil nicht alle Programmierumgebungen über Unix-Domain-Sockets arbeiten können (Java) oder gar externer Zugriff für ein Webfrontend (phpmyadmin) oder vom Kundensystem aus nötig ist.

Wie kommt also MySQL dazu, eine Verbindung überhaupt anzunehmen, wo es doch keine Möglichkeit gibt, einen Nutzer von dieser unbekannten Quelle zu authentisieren?

mysql> use mysql;
mysql> select user, host from user;
+------------+-----------------------+
| user       | host                  |
+------------+-----------------------+
| abcd       | %                     | 
| root       | 127.0.0.1             | 
[...]

Ja, was zur Hölle sucht denn hier der Wildcard?

Das ist das Ergebnis der typischen Anleitungen im Internet. Man möge doch bitte 'user'@'localhost' und 'user'@'%' freigeben.

Sobald man die Zeile mit dem Wildcard durch eine qualifizierte Hostangabe ersetzt, hört der Spuk auf.

mysql> update user set host = 'a.b.c.d' where host = '%';
Query OK, 1 row affected (0,00 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0,00 sec)

Und der Test scheitert grandios:

$ telnet mysql.ser.ver 3306
NHost '*****************' is not allowed to connect to this MySQL server
Connection closed by foreign host.

Und die Prozeßtabelle ist wieder aufgeräumt.

Wenn man diese Schritte hinter sich hat, findet man auch die passende Dokumentation.

Ist es nicht schön?

Betreibt man viele Server, die viele Uplinks ansprechen müssen, sind Ungleichgewichte im Routing unvermeidlich. Aber so kraß wie hier muß es nicht sein. Ein Blick auf die Auslastung der Außenleitungen in abgehender Richtung zeigt ungewöhnliches. Nur eine Leitung ist aktiv in Benutzung. Alle anderen idlen vor sich hin. Das ist ungesund.

Widersprüche

Der Quagga meint, er hätte vier Routen nach außen:

O>* 0.0.0.0/0 [110/11] via 192.0.0161, lagg115, 2d03h41m
                       via 192.0.0162, lagg115, 2d03h41m
                       via 192.0.0167, lagg115, 2d03h41m
                       via 192.0.0163, lagg115, 2d03h41m

Andererseits meint der Kernel, er habe nur eine Route.

# netstat -rn | head
Destination        Gateway            Flags    Refs      Use  Netif Expire
default            192.0.0.161        UG1         0 692675252 lagg11 =>

Eine kurze Recherche ergab, daß der FreeBSD-Kernel mehrere Routen nur dann beherrscht, wenn er mit der Option RADIX_MPATH compiliert wurde. Und natürlich ist das hier nicht der Fall.

Recompile, Reboot

Der neue Kernel ist vergleichsweise schnell ausgerollt.

Und das Ergebnis kann sich sehen lassen:

O>* 0.0.0.0/0 [110/11] via 192.0.0.161, lagg115, 2d03h41m
  *                    via 192.0.0.162, lagg115, 2d03h41m
  *                    via 192.0.0.167, lagg115, 2d03h41m
  *                    via 192.0.0.163, lagg115, 2d03h41m

sowie der Kernel

Destination        Gateway            Flags    Refs      Use  Netif Expire
default            192.0.0.161        UG1         0 692675252 lagg11 =>
default            192.0.0.162        UG1         0 715615509 lagg11 =>
default            192.0.0.167        UG1         0 754143735 lagg11 =>
default            192.0.0.163        UG1         0 749227950 lagg11

Aber auch graphisch sieht es so aus, wie man sich das wünscht ...

traffic-mpath

So ganz "spontan" gleichen sich die Datenströme über alle vier Router an.

Einige Kunden beschweren sich über erhöhte Latenz in den Hochlaststunden. Es betrifft ausschließlich Kunden, die per DHCP eine CGN-Adresse (100.64.0.0/10) bekommen haben. Irgendwie sind also die NAT Kisten am Ende.

Der Krux mit dem Interrupt

das Problem reduziert sich bei näherer Betrachtung auf eine Interrupt-Routine des FreeBSD-Kernels, die die Pakete entgegen nimmt und komplett verarbeitet. Dort wird alles gemacht, was ohne Wartezeit möglich ist, also auch die NAT Funktion.

Zuerst einmal ein Blick auf die Auslastung der CPU Kerne durch einzelne Threads: top -nCHSIzs1 Der Plot über die Zeit demonstriert das Problem deutlich.

lns-stats-threat-single

Die grüne Kurve ist der Interrupt-Handler eines einzelnen Interfaces (und dort von einer einzeln Empfangsqueue, der bis zu 100% seiner CPU auslastet. Ist der Punkt erreicht, wird es langsam oder es kommt gar zu Paketverlust.

Die Tatsache, daß der MPD selbst 100% CPU macht, liegt in der verwendeten Thread-Blibiothek vergraben und soll jetzt nicht Thema sein. Im Endeffekt handelt es sich um eine Art aktiver "idle Thread", was der vornehme Ausdruck für aktives Polling sein soll.

Die Lösung des NAT Problem muß also darin bestehen, mehr Interfaces und mehr Empfangsqueues zu aktivieren. Dann verteilt sich die Last auf mehr CPU Kerne.

Normalerweise ist das ganz einfach: Man bündelt die Interfaces mit LACP zu einem virtuellen Interface zusammen. Dann kommen auf verschiedenen Interfaces die Pakete an und können auf mehrere CPUs verteilt werden.

Ausfallszenarien

Unglücklicherweise soll die Plattform weiter laufen, auch wenn ein Port, ein ganze Baugruppe oder gar ein ganzer Switch ausfällt. Die vier Interfaces jedes Servers sind also so verkabelt, daß beim Ausfall einer (größeren) Komponente immer noch möglichst viel der Topologie übrig bleibt.

nat-server-an-zwei-switchen

Die em und die igb Karten liegen auf verschiedenen Steckplätzen im Server. Deswegen werden sie über Kreuz als Failover-Paare (lagg) definiert:

  • em0 und igb1 bilden ein lagg Failover-Paar für den Traffic vom und zum Kunden (VLAN100).
  • em1 und igb0 bilden ein lagg Failover-Paar für den Traffic vom und zum Internet (VLAN200).

Auf diese Weise wird jedes VLAN von jedem Switch zugeführt, ohne daß dabei die Switche von den anderen Querverbindungen wissen müßten. Im Normalfall wären also nur beispielsweise nur die em Interfaces aktiv. Jeder Switch hat also nur eine aktive Verbindung zu dem Server. Die MAC Adresse des Servers taucht insgesamt im Netz nur einmal auf, an dem Switch, wo das jeweilige Interface aktiv ist.

Es ist nun aber nicht möglich, die Interfaces zu LACP-Tunneln zusammen zu fassen.

  • Die Switche unterstützen kein gemeinsames Multichassis-LACP.
  • Auf dem LACP Tunnel zu einem Switch müssen immer beide VLANs aktiv sein, um den Ausfall des anderen Switches zu kompensieren.
  • FreeBSD unterstützt lagg Interfaces nur auf physikalischen Ethernets. Lagg-Stacking ist nicht möglich.

Genau genommen ist die Situation noch viel komplizierter. Es gibt VLANs, deren Uplink nur auf einem der beiden Switche anliegt, so daß es sinnlos wäre diese auf den anderen Switch zu aktivieren. Dann gibt es VLANs, die nur vorzugsweise auf einem der beiden Switche landen sollen (Managment), aber im Fehlerfall auch von dem anderen Switch übernommen werden müssen.

Was schön wäre, wäre also die Interfaces pro Switch per LACP zu aggregieren (mehr Interrupts). Auf diese Bündel müßten dann die VLANs gebunden werden (was problemlos geht). Und für einige VLANs soll ein Failover zwischen den Switchen aktiv sein.

Man müßte also ein lagg über VLAN-Interfaces erlauben. Glücklicherweise ist das aber recht einfach.

--- -   2014-07-18 17:21:12.580999119 +0200
+++ 8.3/sys/net/if_lagg.c       2014-07-07 11:08:06.000000000 +0200
@@ -513,8 +513,15 @@
                return (EBUSY);
 
        /* XXX Disallow non-ethernet interfaces (this should be any of 802) */
-       if (ifp->if_type != IFT_ETHER)
+       switch (ifp->if_type) {
+       case IFT_ETHER: case IFT_L2VLAN:
+               /* allowed, fall through */
+               break;
+       default:
+               printf("Unsupported lagg parent %s of type %x (sys/net/if_types.h)\n",
+                      ifp->if_xname, ifp->if_type);
                return (EPROTONOSUPPORT);
+       }
 
        /* Allow the first Ethernet member to define the MTU */
        if (SLIST_EMPTY(&sc->sc_ports))

Und plötzlich tut es!

Startprobleme

Was allerdings nicht ohne weiteres tut, ist der Reboot.

Hier gilt es jetzt sehr vorsichtig zu sein, um die Interfaces in der richtigen Reihenfolge anzulegen (wird durch cloned_interfaces festgelegt). Bereits beim Anlegen müssen die lagg Interfaces mit den darunter liegenden Interfaces verbunden werden. Andernfalls wird das Binden der VLAN Interfaces die spätere Festlegung der lagg-Parameter unterbinden. Die Verknüpfung der Interfaces untereinander muß also mit create_args_*  erfolgen, und alle anderen Eigenschaften können später perifconfig_* nachgeliefert werden.

Eine passende rc.conf könnte also so aussehen:

cloned_interfaces="lagg3 lagg4"
cloned_interfaces="${cloned_interfaces} vlan3044 vlan4044 lagg44"
cloned_interfaces="${cloned_interfaces} vlan3115 vlan4115 lagg115"
cloned_interfaces="${cloned_interfaces} vlan3140 vlan4140 lagg140"

# LACP per switch
create_args_lagg3="laggproto lacp laggport igb1 laggport em0"
ifconfig_lagg3="up"
create_args_lagg4="laggproto lacp laggport igb0 laggport em1"
ifconfig_lagg4="up"

# VLANs useful only on a single switch
create_args_vlan143="vlan 143 vlandev lagg3"
create_args_vlan144="vlan 144 vlandev lagg4"

# VLANs available on both switches (preference on first switch)
create_args_vlan3140="vlan 140 vlandev lagg3"
create_args_vlan4140="vlan 140 vlandev lagg4"
create_args_lagg140="laggproto failover laggport vlan4140 laggport vlan3140"
ifconfig_lagg140="up"

create_args_vlan3044="vlan 44 vlandev lagg3"
create_args_vlan4044="vlan 44 vlandev lagg4"
create_args_lagg44="laggproto failover laggport vlan3044 laggport vlan4044"
ifconfig_lagg44="up"

# VLANs available on both switches (preference on second switch)
create_args_vlan3115="vlan 115 vlandev lagg3"
create_args_vlan4115="vlan 115 vlandev lagg4"
create_args_lagg115="laggproto failover laggport vlan3115 laggport vlan4115"
ifconfig_lagg115="up"

Auf diese Weise kommen alle Interfaces automatisch korrekt hoch.

Wirkung

Und das Ergebnis? Das schaut super aus!

lns-stats-thread-lacp

Die CPU Last verteilt breit über vier Interrupt-Routinen. Diese nehmen sich kaum etwas, weil sie nicht mehr nach Aufgaben getrennt (Blick zum Kunden oder ins Internet) ihre Datenpakete erhalten. Stattdessen kommt alles irgendwie bunt gemischt rein und lastet die CPU Cores viel gleichmäßiger aus.

Problem gelöst!

Über Kommentarspam hatte ich mich ja schon mal geärgert und eine Browsererkennung implementiert. In den letzten Tagen waren aber wieder viele hundert Spamkommentare im Blog.

Haben meine Methoden versagt? Ein Blick in die Logfiles zeigen, daß von der betreffenden IP tatsächlich komplette Webseiten geholt werden. Offenbar ist da ein voll qualifizierter Browser am Werk.

Natürlich – das ist ja der Sinn dieses Tests – soll in einem Browser die Kommentarfunktion anstandslos funktionieren. Unglücklicherweise tut es das auch für den Spammer.

Die dümmsten Spammer haben die größten Erfolge

Was unterscheidet nun also den Spammer von einem normalen Nutzer? Auffällig ist die Schnelligkeit, mit der Kommentare versendet werden. Es liegen kaum zwei Sekunden zwischen zwei abgesendeten Kommentaren. Direkt nach dem Laden der Seite wird der Kommentar abgeschickt.

Ganz offensichtlich benutzt der Spammer einen gescripteten Browser. Allerdings waren die ersten Versuche noch sehr langsam. Vielleicht ist das ja die neue Generation von Spammern, die nicht einmal mehr programmieren kann, sondern mit einem Makrorekorder die Bedienung aufzeichnet, um sie dann tausendfach abzurollen.

Eine Möglichkeit besteht darin, den per Javascript nachgeladenen Code nicht direkt auszuführen, sondern per erste einige Sekunden verzögert zu aktivieren. Aber wo legt man da die Grenze? Und was ist mit Nutzern, die reloaden, bevor sie den Kommentar eintippen. Ich mache das öfter, um zu sehen, welche Kommentare noch zuletzt hinzu gekommen sind.

Eine andere Möglichkeit besteht darin, die Anzahl der Kommentare pro Zeiteinheit, also die Rate, auszuwerten. Dies geht nicht mehr im Formular, sondern benötigt einen Blick auf die Historie.

Diese Historie findet sich in der Datenbank. Also kann die Datenbank auch selbst das Rate-Limiting machen.

CREATE OR REPLACE RULE lutz_prevent_spam AS
  ON INSERT TO ezcomment
    WHERE 5 < (SELECT Count(ip) FROM ezcomment
                WHERE ip = NEW.ip
                  AND created > NEW.created - 60)
  DO INSTEAD
    DELETE FROM ezcomment
     WHERE ip = NEW.ip
       AND created > NEW.created - 60*10;

Es wird also geschaut, ob von dieser IP in den letzten 60 Sekunden mehr als fünf Kommentare abgesendet wurden. Ist das der Fall, werden alle Kommentare von dieser IP gelöscht, die in den letzten 10 Minuten geschrieben wurden.

Das scheint aktuell zu halten. Drückt mir die Daumen.

Einen Tag später

Wie in den Kommentaren zu sehen, hat es nicht gehalten. Jedenfalls nicht ganz richtig.

Die neue Lösung besteht darin eine neue Tabelle zu haben, die die blockierten IPs aufzählt:

       Table "public.ezcomment_block"
 Column |          Type          | Modifiers 
--------+------------------------+-----------
 ip     | character varying(100) | not null
 seen   | integer                | not null
Indexes:
    "ezcomment_block_pkey" PRIMARY KEY, btree (ip)
Rules:
    update_if_necessary AS
 ON INSERT TO ezcomment_block
   WHERE new.ip IN (SELECT b.ip FROM ezcomment_block b)
 DO INSTEAD  UPDATE ezcomment_block c SET seen = new.seen
  WHERE c.ip = new.ip

Mit der Regel, die die Inserts bei Bedarf in Updates umschreibt, kann man dauerhaft Inserts benutzen. Das vereinfacht die neue Regel für die Kommentare.

CREATE OR REPLACE RULE lutz_prevent_spam AS
 ON INSERT TO ezcomment
   WHERE 5 < (SELECT count(1) FROM ezcomment
          WHERE ip = new.ip AND created > new.created - 60)
      OR new.ip IN (SELECT ip FROM ezcomment_block
          WHERE seen > new.created - 24 * 60 * 60)
 DO INSTEAD (
  DELETE FROM ezcomment WHERE ip = new.ip AND created > new.created - 10 * 60;
  INSERT INTO ezcomment_block (ip, seen) VALUES (new.ip, new.created);
)

Mal sehen, wie lange das nun hält.

Der OpenSSL Fehler gestattet es, den Hauptspeicher der betroffenen Maschine ohne jedes Sicherheitsabfrage von Ferne aus auszulesen, wenn diese eine TLS Verbindung mit der Heartbeat-Erweiterung anbietet. Die dabei gefährdeten Daten sind als kompromittiert anzusehen, weil der Fehler grundsätzlich seit 2012 besteht:

  • Private Schlüssel von Zertifikaten. Diese dienen zum Aufbau der TLS Verbindung und sind somit im gleichen Speichersegment wie die anfällige Software.
  • Authenisierungsinformationen wie Cookies, Logins und Paßworte. Diese sollten mit der TLS Verbindung geschützt werden und sind deswegen mit hoher Wahrscheinlichkeit betroffen.
  • Weitere geheime Informationen, wie Dokumente und Zugangsdaten, die von anderen Diensten benutzt werden. Der Zugriff auf den Speicherbereich anderer Software ist nicht ganz so leicht, aber möglich.

Notwendige Aktionen

  • Behebung der Schwachstelle durch Update der betroffenen Bibliotheken.
  • Prüfung, ob wirklich keine Altbestände an verwundbaren Bibliotheken existieren. (statisch einkompiliert oder lokal beim Programm beigelegt)
  • Neustart aller betroffenen Dienste
  • Austausch aller privaten Schlüssel, Neuausstellung von Zertifikaten, Sperrung der alten Zertifikate.
  • Wechsel aller Paßworte, die mit dem System in Berührung kamen.

Welche Systeme sind betroffen?

Für die Admins steht nun die Frage, ob wirklich alle eignen oder gehosteten Systeme aktuell verwundbar sind.

Dabei genügt es nicht, auf Port 443 (https) zu testen, denn viele Dienste können ihre Kommunikation extra oder auf Kommando verschlüsseln. Dies betrifft unter anderem:

  • E-Mail Transport per SMTP(25) mit STARTTLS.
  • E-Mail Abruf per POP3 entweder per POP3S(995) oder mit STARTTLS(110).
  • E-Mail Abruf per IMAP entweder per IMAPS(993) oder STARTTLS(143).
  • LDAP Abfragen per LDAPS(636) oder per StartTLS(389)
  • MySQL Datenbank (3306): MySQL ist oft mit bundled SSL Code kompiliert, nicht gegen die Systemlib.
  • mod_spdy ist standardmäßig statisch gegen eine alte Version gelinkt.

In der Liste fehlt bestimmt noch eine ganze Menge weitere Dienste, die ich versuche nach und nach zu ergänzen.

Aber hier erstmal der Link zum Scanner: Teste jetzt!

Wer selbst testen will, nimmt das Perl-Script.

Ich hatte mich über udev aufgeregt und angekündigt, ich wolle einen eigenen Weg gehen. Und das hat nun schon geklappt. MesaLib compiliert und linkt gegen meine Library.

libudev-light

Der Quellcode ist unter git://git.donnerhacke.de/libudev.git abrufbar. Er benötigt den üblichen Rattenschwanz der aktuellen GNU Tool. (automake, autoconf, libtool, ...)

Unter ftp://ftp.iks-jena.de/pub/mitarb/lutz/libudev-light/ finden sich fertige Pakete, die direkt auf dem üblichen Weg (compile, make, make check, make install) eingebunden werden können.

Historie der bisherigen Arbeit:

  • 2014-03-20 FreeBSD, Debian
    • Modified tests to match the different requirements of different systems
  • 2014-03-17 MesaLib 10.0.3
    • MesaLib compiles successfully.
    • Compatibility issues to systems without SYSFS fixed.
    • Version 0.0 completed.
  • 2014-03-12 MesaLib 10.0.3
    • src/gbm/backends/dri/driver_name.c requires:
    • struct udev_device *udev_device_get_parent(struct udev_device *);
    • const char *udev_device_get_property_value(struct udev_device *, const char *);
  • 2014-03-06 MesaLib 10.0.3
    • src/gbm/main/common.c requries:
    • struct udev;
    • struct udev_device;
    • struct udev * udev_new(void);
    • struct udev_device * udev_device_new_from_devnum(struct udev *, char, dev_t);
    • const char * udev_device_get_devnode(struct udev_device *);
    • void udev_device_unref(struct udev *);
    • struct udev * udev_unref(struct udev *);

Interna

Libudev-light setzt auf das Linux spezifische sysfs und falls dieses nicht vorhanden ist oder die betreffenden Angaben nicht enthält auf ein Durchsuchen des/dev Verzeichnisses.

Die Bibliothek beherrscht bereits die Verlagerung der Mountpoints, es gibt aber noch keine configure Optionen dafür.

Die Tests prüfen verschiedenste Aufrufvarianten, versuchen aber auch Fehler zu provozieren. So wird beispielsweise geprüft, ob eine unbeabsichtigte doppelte Ressourcenfreigabe auch erkannt wird.

Naturgemäß sind die Tests alle noch etwas wackelig und bedürfen einer breiten Ausweitung auf interessante Plattformen.

Hier meine aktuelle Testliste:

  • Linux 3.13.6 in einem Eigenbau Userland: Tut.
  • Aktuelles Debian: Tut.
  • FreeBSD 8.3: Tut, kann aber nicht auf sysfs zugreifen.
  • FreeBSD 10.0: Scheitert mit clang am double free Test.
  • Digital UNIX V4.0F: Wenn man manuell die sys/queue.h aus einem BSD hinzugibt, scheitert der Build an der fehlenden snprintf Implementation. Gnulib ist mir dafür aber zu fett.

Wie schaut es bei Euch aus?

Immer wieder benötigt man im Privatkunden-Umfeld die Möglichkeit die Zuweisung der IP Adressen zu dynamisieren. Hier ist der Hauptgrund, daß die Kunden darauf konditioniert wurden und sich ohne regelmäßige Adreßwechsel unsicher fühlen. Erfolgreiches Marketing eben. Muß man halt haben.

Standardsoftware

Wir setzen auf den ISC-DHCP. Und der ist bezüglich der IP Vergabe sehr konservativ. Solange auch nur eine Anhaltspunkt gefunden werden kann, daß der Client die spezielle IP bekommen müßte, wird er die bekommen. Gründe sind obskure Historien, schon mal gesehenen MAC Adressen oder ganz einfach die Anfrage des Clients, diese IP zu erhalten.

Die Dynamisierung besteht im Prinzip aus zwei Schritten.

  • Zum einen darf ein Client seine vorher mal benutzte IP nicht erneut angeboten (DISCOVER) bekommen.
  • Zum anderen darf ein Client eine Lease mit einer zugewiesenen IP ab einem bestimmten Zeitpunkt nicht mehr verlängert (REQUEST) bekommen.

Konsequenterweise sind das zwei getrennte, poolspezifische Optionen:

  • avoid-reuse bool sorgt dafür, daß ein Client jedesmal eine andere IP angeboten bekommt, wenn er keine gültige IP hat. Das gestattet einen Adreßwechsel jedesmal, wenn der Client rebootet oder neue IPs anfordert.
  • force-nack hour minute dagegen sorgt dafür, daß ein Client zu einem bestimmten Zeitpunkt seine aktuell zugewiesene IP verliert. Überstreicht eine Lease den definierten Zeitpunkt, wird die niemals verlängert. Der Client muß dann nach einer neuen IP fragen.

Die Optionen können unabhängig voneinander agieren.

Es ist also möglich, nur den Adreßwechsel zu erzwingen, nicht aber die Zwangstrennung du8rchzuführen. Diese quasi-statische Zuordnung ist im Zusammenhang mit Triple-Play wichtig, wenn der ISP erhebliche Risiken eingehen, falls er ein wichtiges Telefonat (z.B. einen Notruf) oder eine Sportübertragung (Fußball, SuperBowl) unterbricht.

Es ist ebenso möglich, nur die Zwangstrennung zu aktivieren, falls das durch Beschränkungen der Abrechnungssoftware oder andere interne Mechanismen erforderlich ist.

Umsetzung

Zuerst einmal brauchtes ein paar Token, die beide Funktionen kennzeichnen sollen.

diff -pbBru ../ORIGINAL/includes/dhcpd.h includes/dhcpd.h
--- ../ORIGINAL/includes/dhcpd.h        2011-07-09 00:56:26.000000000 +0200
+++ includes/dhcpd.h    2012-03-06 15:54:40.000000000 +0100
@@ -713,6 +713,9 @@ struct lease_state {
 # define SV_LDAP_TLS_RANDFILE           77
 #endif
 #endif
+/* private options */
+#define SV_AVOID_REUSE                 200
+#define SV_FORCE_NACK                  201
 
 #if !defined (DEFAULT_PING_TIMEOUT)
 # define DEFAULT_PING_TIMEOUT 1
diff -pbBru ../ORIGINAL/includes/dhctoken.h includes/dhctoken.h
--- ../ORIGINAL/includes/dhctoken.h     2011-05-12 14:02:47.000000000 +0200
+++ includes/dhctoken.h 2012-03-06 15:54:06.000000000 +0100
@@ -362,6 +362,9 @@ enum dhcp_token {
        REWIND = 663,
        INITIAL_DELAY = 664,
        GETHOSTBYNAME = 665
+       /* Private usage. Prepend COMMA to keep patches context independant */
+     ,  AVOID_REUSE = 1000
+     ,  FORCE_NACK = 1001
 };
 
 #define is_identifier(x)       ((x) >= FIRST_TOKEN &&  \

Als nächstes sind diese Werte pro Pool aus der Konfiguration zu parsen:

diff -pbBru ../ORIGINAL/server/stables.c server/stables.c
--- ../ORIGINAL/server/stables.c        2011-05-20 16:21:11.000000000 +0200
+++ server/stables.c    2012-03-06 15:27:42.000000000 +0100
@@ -266,6 +266,8 @@ static struct option server_options[] = 
        { "ldap-tls-randfile", "t",             &server_universe,  77, 1 },
 #endif /* LDAP_USE_SSL */
 #endif /* LDAP_CONFIGURATION */
+       { "avoid-reuse", "f",                   &server_universe,  SV_AVOID_REUSE, 1 },
+       { "force-nack", "BB",                   &server_universe,  SV_FORCE_NACK, 1 },
        { NULL, NULL, NULL, 0, 0 }
 };
diff -pbBru ../ORIGINAL/common/conflex.c common/conflex.c
--- ../ORIGINAL/common/conflex.c        2011-05-11 16:20:59.000000000 +0200
+++ ./common/conflex.c  2012-03-06 15:56:22.000000000 +0100
@@ -782,6 +782,8 @@ intern(char *atom, enum dhcp_token dfv) 
                                return AUTO_PARTNER_DOWN;
                        break;
                }
+               if (!strcasecmp(atom + 1, "void-reuse"))
+                       return AVOID_REUSE;
                break;
              case 'b':
                if (!strcasecmp (atom + 1, "ackup"))
@@ -986,6 +988,8 @@ intern(char *atom, enum dhcp_token dfv) 
                        return FIXED_PREFIX6;
                if (!strcasecmp (atom + 1, "ddi"))
                        return TOKEN_FDDI;
+               if (!strcasecmp(atom + 1, "orce-nack"))
+                       return FORCE_NACK;
                if (!strcasecmp (atom + 1, "ormerr"))
                        return NS_FORMERR;
                if (!strcasecmp (atom + 1, "unction"))

Und dann braucht es noch Funktionalität. Dazu werden Hooks in die betreffenden Funktionen eingepaßt. Diese Hooks ermitteln, ob die betreffende Funktionalität jetzt im Moment für diese Anfrage aktiviert werden soll oder nicht.

diff -pbBru ../ORIGINAL/server/dhcp.c server/dhcp.c
--- ../ORIGINAL/server/dhcp.c   2011-07-20 00:22:49.000000000 +0200
+++ server/dhcp.c       2012-03-16 11:33:11.000000000 +0100
@@ -40,6 +40,8 @@
 static void commit_leases_ackout(void *foo);
 static void maybe_return_agent_options(struct packet *packet,
                                       struct option_state *options);
+static int avoid_reuse(struct packet *packet, struct lease * lease);
+static int force_nack(struct packet *packet, struct lease * lease);
 
 int outstanding_pings; 

Die Funktion von avoid-reuse besteht darin, jede zuvor gefundenen Lease während der DISCOVER Verarbeitung zu verwerfen. Deswegen steht sie am Ende aller Lease-Ermittlungen. Diese Platzierung gestattet es, alle anderen Funktionen unverändert zu belassen.

diff -pbBru ../ORIGINAL/server/dhcp.c server/dhcp.c
--- ../ORIGINAL/server/dhcp.c 2011-07-20 00:22:49.000000000 +0200
+++ server/dhcp.c 2012-03-16 11:33:11.000000000 +0100
@@ -341,6 +343,18 @@ void dhcpdiscover (packet, ms_nulltp)
                }
        }
 #endif
+       /*
+        * Special handling to insist on new IPs whenever possible
+        */
+       if (avoid_reuse(packet, lease)) {
+          log_info ("Avoid reuse of old lease %s", piaddr (lease -> ip_addr));
+          if(lease -> ends > cur_time)
+            dissociate_lease (lease);   /* Free lease to enable reuse. */
+          lease_dereference (&lease, MDL);
+          if(lease)
+            log_error ("Lease %s can't be avoided, it's still referenced.",
+                       piaddr (lease -> ip_addr));
+       }
 
        /* If we didn't find a lease, try to allocate one... */
        if (!lease) {

Die Funktion von force-nack dagegen besteht darin, die Verarbeitung des REQUEST zu unterbinden.

diff -pbBru ../ORIGINAL/server/dhcp.c server/dhcp.c
--- ../ORIGINAL/server/dhcp.c   2011-07-20 00:22:49.000000000 +0200
+++ server/dhcp.c       2012-03-16 11:33:11.000000000 +0100
@@ -671,6 +685,12 @@ void dhcprequest (packet, ms_nulltp, ip_
                goto out;
        }
 
+       if (force_nack (packet, lease)) {
+               log_info ("%s: force disconnect.", msgbuf, piaddr (cip));
+               nak_lease (packet, &cip);
+               goto out;
+       }
+       
        /* Otherwise, send the lease to the client if we found one. */
        if (lease) {
                ack_lease (packet, lease, DHCPACK, 0, msgbuf, ms_nulltp,

Bleibt also die Hooks auch zu aktivieren. Unglücklicherweise ist das Optionshandling nicht trivial. Es gilt nicht nur die Konfiguration zum jetzigen Zeitpunkt, sondern auch die Konfiguration, die bei der letzten Verlängerung der Lease galt. Dieses ist Suche habe ich in eine separate Funktion get_lease_state ausgelagert.

Mit dieser Hilfsfunktion ist es wesentlich leichter, die Funktionalität aufzubauen.

diff -pbBru ../ORIGINAL/server/dhcp.c server/dhcp.c
--- ../ORIGINAL/server/dhcp.c   2011-07-20 00:22:49.000000000 +0200
+++ server/dhcp.c       2012-03-16 11:33:11.000000000 +0100
@@ -4472,3 +4492,129 @@ maybe_return_agent_options(struct packet
                        options->universe_count = agent_universe.index + 1;
        }
 }
+
+/*
+ * 
+ * 
+ * 
+ */
+static int get_lease_state(struct lease_state * state,
+                          struct packet * packet, struct lease * lease) {
+   int i;
+
+   state -> got_requested_address = packet -> got_requested_address;
+   shared_network_reference (&state -> shared_network,
+                            packet -> interface -> shared_network, MDL);
+   
+   /* See if we got a server identifier option. */
+   if (lookup_option (&dhcp_universe,
+                     packet -> options, DHO_DHCP_SERVER_IDENTIFIER))
+     state -> got_server_identifier = 1;
+   
+   maybe_return_agent_options(packet, state->options);
+   
+   /* Execute statements in scope starting with the subnet scope. */
+   execute_statements_in_scope ((struct binding_value **)0,
+                               packet, lease, (struct client_state *)0,
+                               packet -> options,
+                               state -> options, &lease -> scope,
+                               lease -> subnet -> group,
+                               (struct group *)0);
+   
+   /* If the lease is from a pool, run the pool scope. */
+   if (lease -> pool)
+     (execute_statements_in_scope
+      ((struct binding_value **)0, packet, lease,
+       (struct client_state *)0, packet -> options,
+       state -> options, &lease -> scope, lease -> pool -> group,
+       lease -> pool -> shared_network -> group));
+   
+   /* Execute statements from class scopes. */
+   for (i = packet -> class_count; i > 0; i--) {
+      execute_statements_in_scope
+       ((struct binding_value **)0,
+        packet, lease, (struct client_state *)0,
+        packet -> options, state -> options,
+        &lease -> scope, packet -> classes [i - 1] -> group,
+        (lease -> pool
+         ? lease -> pool -> group
+         : lease -> subnet -> group));
+   }
+}
+
+static int avoid_reuse(struct packet *packet, struct lease * lease) {
+   int avoid_this_lease = 0;
+   struct option_cache * oc;
+   struct lease_state * state;
+   int ignorep;
+
+   /* Shortcut: Nothing to do. */
+   if (!packet || !lease || (lease -> flags & STATIC_LEASE))
+     return avoid_this_lease;
+   
+   state = new_lease_state (MDL);
+   if (!state)
+     return avoid_this_lease;         /* silently ignore the error */
+   else
+     get_lease_state(state, packet, lease);
+   
+   oc = lookup_option(&server_universe, state -> options, SV_AVOID_REUSE);
+   if (oc &&
+       evaluate_boolean_option_cache(&ignorep, packet, lease,
+                                    (struct client_state *)0,
+                                    packet -> options, state -> options,
+                                    &lease -> scope, oc, MDL)) {
+      /* 120 seconds is the typical hold time for temporary allocations */
+      if(cur_time - 120 > lease -> starts)
+       avoid_this_lease = 1;          /* do not OFFER an "old" lease */
+   }
+
+   free_lease_state (state, MDL);
+   
+   return avoid_this_lease;
+}
+   
+
+static int force_nack(struct packet *packet, struct lease * lease) {
+   int force_nack = 0;
+   struct option_cache * oc;
+   struct lease_state * state;
+   struct data_string data;
+
+   /* Shortcut: Nothing to do. */
+   if (!packet || !lease || (lease -> flags & STATIC_LEASE))
+     return force_nack;
+   
+   state = new_lease_state (MDL);
+   if (!state)
+     return force_nack;               /* silently ignore the error */
+   else
+     get_lease_state(state, packet, lease);
+   
+   memset(&data, 0, sizeof(data));
+   oc = lookup_option(&server_universe, state -> options, SV_FORCE_NACK);
+   if (oc &&
+       evaluate_option_cache(&data, packet, lease,
+                            (struct client_state *)0,
+                            packet -> options, state -> options,
+                            &lease -> scope, oc, MDL)) {
+      struct tm disconnect_tm;
+      TIME disconnect;
+      
+      if(localtime_r(&cur_time, &disconnect_tm)) {
+        disconnect_tm.tm_sec  = 0;
+        disconnect_tm.tm_min  = data.data[1];
+        disconnect_tm.tm_hour = data.data[0];   
+        disconnect = mktime(&disconnect_tm);
+        if(disconnect <= cur_time &&
+           disconnect >  lease -> starts) {
+           force_nack = 1;            /* NAK the lease if it spans the disconnect time */
+        }
+      }
+      data_string_forget (&data, MDL);
+   }
+
+   free_lease_state (state, MDL);
+   
+   return force_nack;
+}

Konfiguration

Wie sieht das nun aus, wenn man diese Optionen auch benutzen will?

# short lease times to allow quick changes
shared-network Internet_Vlan {
        default-lease-time 3000;
        max-lease-time 3600;

        # static and other subnets as usual

        subnet x.y.z.0 netmask 255.255.255.0 {
                option subnet-mask 255.255.255.0;
                option routers x.y.z.1;
                option domain-name-servers a.b.c.d;
                pool {
                        allow members of "dynamic";
                        range x.y.z.10 x.y.z.254;
                        avoid-reuse on;
                        force-nack 3 0;
                }
        }

        subnet 100.64.0.0 netmask 255.255.128.0 {
                option subnet-mask 255.255.128.0;
                option routers 100.64.0.1;
                option domain-name-servers 100.64.0.4, 100.64.0.5;
                pool {
                        allow members of "CGN";
                        range 100.64.0.10 100.64.127.254;
                        avoid-reuse on;
                }
        }
}

Es gibt mehrere statische Bereiche, die wie gewohnt konfiguriert werden.

Es gibt darüberhinaus mehrere dynamische Bereiche (einer ist hier dargestellt), bei denen nachts um 3:00 eine vertraglich vereinbarte Zwangstrennung erfolgt und die IP Adressen gewechselt werden.

Darüberhinaus gibt es mehrere dynamische Carrier Grade NAT Bereiche (einer ist hier dargestellt), bei denen keine Zwangstrennung, wohl aber ein Adreßwechsel erfolgt.

Der ganze Kram ist Failover fähig und läuft hier problemlos in einem DHCP-Cluster.