Advanced search


ezPublish liefert in der aktuellen empfohlenen Siteversion die Erweiterung ezTags mit aus. Leider existiert für diese Erweiterung nur die Datenbankdefinition für MySQL. Ich habe aber meine Installation auf PostgreSQL laufen.

Bearbeitet man also nichtsahnend im Reiter ezTags erstmals die Einträge, so gibt es eine Fehlermeldung. Im "var/log/error_log" findet sich dann diese Meldung:

eZPostgreSQLDB: Error: error executing query:
 INSERT INTO eztags (parent_id, main_tag_id, ...:
 ERROR:  relation "eztags" does not exist

Hier sind also die fehlenden Definitionen, die nach "extension/eztags/sql/postgres/schema.sql" geschrieben werden müssen oder einfach von Hand nachzutragen sind.

CREATE SEQUENCE eztags_s;
CREATE TABLE eztags (
   id integer DEFAULT nextval('eztags_s'::text) NOT NULL,
   parent_id integer not null default 0,
   main_tag_id integer not null default 0,
   keyword varchar(255) NOT NULL default '',
   depth integer NOT NULL default 1,
   path_string varchar(255) NOT NULL default '',
   modified integer NOT NULL default 0,
   remote_id varchar(100) NOT NULL default '',
   PRIMARY KEY (id),
   CONSTRAINT remote_id UNIQUE  (remote_id)
);
CREATE INDEX eztags_keyword ON eztags (
   keyword
);
CREATE INDEX eztags_keyword_id ON eztags (
   keyword,
   id
);
CREATE SEQUENCE eztags_attribute_link_s;
CREATE TABLE eztags_attribute_link (
   id integer DEFAULT nextval('eztags_attribute_link_s'::text) NOT NULL,
   keyword_id integer not null default 0,
   objectattribute_id integer not null default 0,
   objectattribute_version integer not null default 0,
   object_id integer not null default 0,
   PRIMARY KEY (id)
);
CREATE INDEX eztags_attr_link_keyword_id ON eztags_attribute_link (
   keyword_id
);
CREATE INDEX eztags_attr_link_kid_oaid_oav ON eztags_attribute_link (
   keyword_id,
   objectattribute_id,
   objectattribute_version
);
CREATE INDEX eztags_attr_link_kid_oid ON eztags_attribute_link (
   keyword_id,
   object_id
);
CREATE INDEX eztags_attr_link_oaid_oav ON eztags_attribute_link (
   objectattribute_id,
   objectattribute_version
); 

 Und geht ...

ezPublish hat standardmäßig eine Contentklasse für Blogbeiträge. Die Blogbeiträge bestehen nur aus der Überschrift und dem eigentlichen Freitext (neben einigen Metadaten). Eine Kurzfassung des Beitrag, z.B. zum Anteasern in Übersichten, ist nicht vorgesehen. Diese ist aber mit etwas CSS leicht erstellt.

Als mögliche Lösungen stehen mehrere Wege zur Auswahl. Zum einen kann man das Template für den Ansicht "line" überschreiben:

{* Blog post - Line view *}
<div class="content-view-line">
  <div class="class-blog-post float-break">

    <div class="attribute-header">
        <h1><a href={$node.url_alias|ezurl}>{$node.data_map.title.content|wash}</a></h1>
    </div>

    <div class="attribute-byline">
        <p class="date">{$node.data_map.publication_date.content.timestamp|l10n(
shortdatetime)}</p>
        <p class="author">{$node.object.owner.name|wash}</p>
    </div>

    <div class="attribute-body float-break">
      {foreach $node.data_map.body.data_text
          |explode('&amp;nbsp;')|implode(' ')
          |explode('<paragraph') as $p
         offset 1}
       {let $text = ""}
        {foreach $p|explode('<') as $p1}
         {set $text = $text|concat($p1|explode('>').1)}
        {/foreach}
        {if $text|count_words|gt(1)}
         {$text|trim(' [.]')|simplify()|shorten(160)
               |explode(' ')|reverse|remove(0,1)|reverse|implode(' ')
               |concat('&nbsp;<a href=',$node.url_alias|ezurl,'>&hellip;</a>')}
         {break}
        {/if}
       {/let}
      {/foreach}
    </div>

  </div>
</div>

Der Trick besteht darin, das XML direkt zu parsen. Zuerst zerlegt man den Text anhand der Absatzeinleitungen "<paragraph", und schneidet sich den Text durch geschicktes Aneinanderreihen jeweils bis zu den der Tagzeichen '<' und '>' zusammen.

Nach jedem Absatz kürzt man auf 160 Zeichen und geht bis zur nächsten Wortstelle (rückwärts nach einem Leerzeichen suchen) zurück. Dann noch der "weiter" Link dran und fertig.

Diese Variante hat den Vorteil, daß die Webseite gleich richtig erstellt und damit browserunabhängig korrekt dargestellt wird. Da der Textanschnitt eine feste Länge hat, ist er praktisch überall konsistent einsetzbar. Nachteilig ist, daß der Abschnitt ohne Rücksicht auf die inhaltlichen Aussagen stattfindet.

Anderseits kann man das ganze auch in CSS allein lösen:

.content-view-line .class-blog-post .attribute-body > * {
  display: none;
}
.content-view-line .class-blog-post .attribute-body > *:first-child {
  display: block;
}

Man blendet also alle Kindelemente des Textkörpers aus und nur das erste Element wieder ein. Typischerweise ist das der erste Absatz. Für Suchmaschinen und nicht CSS verarbeitende Browser erscheint dagegen der volle Text.

Ein dritter Ansatz besteht darin, eine anderen XML Renderer zu schreiben, oder die Templates, die der vorhandene Renderer verwendet, zu überschreiben. So könnte man in "datatype/view/ezxmltags/paragraph.tpl" einen mitlaufenden Ansatzzähler auswerten. Aber dies scheint mir aktuell zu kompliziert.

File size: 2.39 kB (Modified: 20/07/2012 3:08 pm) Download file

Die Cisco SFE/SGE Serie ist so ziemlich die erste Eigenentwicklung auf der Linksys Plattform. Diese zeigt einige Absonderlichkeiten bei der Benutzung.

Der Aufbau der Dokumentation legt nahe, daß es sich nur um verschiedene Bauformen der Switche (mal mit Gigabit und mal mit FastEthernet) in verschiedenen Portanzahlen handelt. Der 8-Port Switch ist aber fundamental anders.

Das Default VLAN ist bei allen SFE/SGE Switchen erwartungskonform die 1. Beim 8-Port Switch SFE1000P ist es aber überraschenderweise 100. Und man kann es nicht ändern.

Natürlich fällt einem das erst auf, wenn man die Switche zusammentrunkt. Es tut einfach nicht.

Erst ein detailierter Blick in die Anleitung offenbart, daß man bei der Konfiguration des Managaments-Interfaces das VLAN auswählen kann. Natürlich nur, wenn man vorher andere VLANs angelegt hat.

Also: Erst VLAN 1 anlegen, dann die Management-IP auf VLAN 1 konfigurieren und dann …

Die DNSSEC Validierung von bsi-für-bürger.de scheitert. Die Kontaktaufnahme zum BSI scheitert beinahe auch.

Wir betreiben validierende DNS Resolver für uns und unsere Kunden. Selbstverständlich prüfen wir die Fehlermeldungen bei der Validierung und dabei fiel heute ein durchaus prominenter Name ins Auge: www.bsi-für-bürger.de.

Eine nähere Untersuchung zeigt, daß bei der DENIC auf den Schlüssel 10876 verwiesen wird:

xn--bsi-fr-brger-hlbd.de. DS  10876 5 2 ...
;; Received 438 bytes from 2001:67c:1011:1::53#53(n.de.net)

Die verantwortlichen Nameserver dagegen kennen keinen solchen Schlüssel, sondern komplett andere:

xn--bsi-fr-brger-hlbd.de. DNSKEY 256 3 5 ...; key id = 30142
xn--bsi-fr-brger-hlbd.de. DNSKEY 257 3 5 ...; key id = 5212
;; Received 789 bytes from 81.92.4.138#53(ns3.klute-thiemann.de)
dnssec-bsi

Korrekterweise lehnt unser Resolver die Validierung ab und liefert statt eines Ergebnisses eine Fehlermeldung. Die Gegenprüfung bei dnsviz bestätigt die Analyse. Vermutlich hat ein Schlüsselwechsel stattgefunden, der nicht korrekt durchgeführt wurde. Diese Kommunikationsfehler beim KSK-Rollover sind leider häufiger.

Die erste Reaktion auf einen solchen Vorfall ist der direkte Kontakt zu einem Verantwortlichen vor Ort, um auf den Fehler hinzuweisen. Unglücklicherweise war das schwieriger als erwartet: Die telefonische Kontaktaufnahme scheiterte mehrfach an Weiterverbindungen ins Nirvana oder zu Personen die sich als unzuständig betrachten. Schließlich empfahl die Zentrale doch kostenpflichtig die 01805 Nummer anzurufen: Nein danke. Als dann endlich das "Service-Center" erreicht wurde, bat man um E-Mail mit der Problembeschreibung, damit man es intern an den hoffentlich richtig Verantwortlichen weiterreichen könne.

Auch diese Kommunikationsschwierigkeiten sind bei der Einführung von DNSSEC häufig zu beobachten: Die Technik betrachet DNSSEC als ihr ureigenes interes Projekt, über das sie nicht weiter reden müssen. Dabei wirkt jeder Fehler, den man im Zusammenhang mit DNSSEC macht, extern wie ein Angriff auf die Infrastruktur. Deswegen gehört zur Einführung von DNSSEC immer eine interne Schulung der Mitarbeiter, insbesondere die Kontakt zum "Endkunden" haben, dazu. Ebenso sind Eskalationspläne zu erstellen, um im Falle eines Falles schnell und professionell reagieren zu können.

Bleibt die Frage, warum man diesen Fehler nicht selbst bemerkt hatte. Ein glaubwürdige Vermutung besteht darin, daß das Browser-Plugin keinen Fehler anzeigt. Und das liegt schlicht daran, daß keine eigne Webseite unter der URL http://www.bsi-für-bürger.de hinterlegt ist, sondern direkt auf https://www.bsi-fuer-buerger.de umgeleitet wird. Dort ist aber DNSSEC in Ordnung: Es läuft erfolgreich ein KSK-Rollover.

Geht doch!

Update: Am folgenden Montag war das Problem behoben. Die Rückmeldung per E-Mail war kurz und knackig. Also nochmal ein "Geht doch!"

Es gibt öfters die Frage, warum ich für meine Domains den freien Zonentransfer von überall zulasse. Der wesentliche Grund ist, meine Serverressoucen zu schonen.

An die Einträge einer Zone, die öffentlich per DNS abfragbar ist, kommt man praktisch immer heran:

  • Man kann die Einträge schlicht raten, denn DNS Namen sollen ja für Menschen verständlich sein. Mit einigen hundertausend Anfragen ist man gut im Rennen und hat in überschaubarer Zeit eine stattliche Masse an Treffern gelandet.
  • Man kann das reverse DNS durchnummerieren. Für alle IP(v4) Adressen eine typischen Kunden braucht man nur ein paar tausend Anfragen. Die Ergebnisse sind praktisch instantan verfügbar und geben beste Anhaltspunkte zum weiteren Raten.
  • Man kann mit DNSSEC die Zone ablaufen. Damit ist die Zone mit einigen hundert Anfragen komplett ausgelesen.

Aus Effizienzgründen habe ich mich dafür entschieden, die öffentliche Natur der Daten im DNS dadurch zu dokumentieren, daß ich AXFR von überall zulassen. Dies hat zwei Vorteile:

  • Wenn jemand an die Zoneninhalte herankommen will, belastet es meine Serverressoucen nicht wirklich. Es ist nur noch eine Query pro Zone.
  • Ich kann beliebig hidden secondaries aufstellen. Diese DNS-Server können sich die Zone einfach ziehen und lokal bedienen. Dies erhöht drastisch die Stabilität von Netzkomponenten, die auf funktionierendes DNS angewiesen sind. Gerade Mailserver und Monitoringsysteme profitieren in Ausfallszenarien deutlich von einer funktionierenden lokalen Auflösung.

Es gibt nun noch die Frage, wieviel Daten ins DNS gehören. Und da vertrete ich die Ansicht, daß eine Unterscheidung zwischen "innen" und "außen" nicht möglich ist. Mobile Kommunikationsformen machen keinen Unterschied, ob das Gerät im lokalen WLAN oder per GSM arbeitet. Beides muß funktionieren. Mit DNSSEC kommt hinzu, daß man nicht trivial intern und extern verschiedene Zonen betreiben kann.

Konsequenterweise bevorzuge ich es, die internen DNS Server (die ja alle IPv6 haben) von extern erreichbar zu machen und dieses als Delegation im DNS zu dokumentieren. Gerade Microsoft Betriebssysteme mit den Active Directory Einträgen danken es einem. Auch wenn IPv6 im Mobilfunknetz nicht funktioniert, verhindert diese Technik wenigstens die NXDOMAIN-Umlenkung auf eine providerseitige Suchmaschine.

Andere Meinungen

Natürlich gibt es andere Ansichten. So schreibt djb von 10^29 Queries beim Raten. Das bezieht sich aber auf das blinde maschinelle Raten beliebiger – vor allem langer und zufälliger – Namen. Diese treten im DNS jedoch praktisch nicht auf. Ich halte die Zahlen von djb schlicht für bewußte Polemik.

Auch die Gegenmaßnahmen gegen DNSSEC Zone Walking überzeugen mich alle nicht besonders. Man kann die NSEC Records dynamisch erzeugen und so minimale Bereiche ausweisen. Der offenkundige Nachteil ist, daß der DNS Server so anfällig für eine CPU-lastige DOS-Attacke wird.

Normalerweise liefert das Zone Walking direkt Klarnamen, wie hier bei den Beweis, daß zwischen "www.iks-jena.de" und "www-cache.iks-jena.de" keine weiteren Einträge liegen und daß für "www.iks-jena.de" nur die Recordtypen A, MX, AAAA sinnvoll existieren:

;; QUESTION SECTION:
;www.iks-jena.de.       PTR
;; AUTHORITY SECTION:
iks-jena.de.            SOA     avalon.iks-jena.de. ...
iks-jena.de.            RRSIG   SOA ...
www.iks-jena.de.        NSEC    www-cache.iks-jena.de. A MX AAAA RRSIG NSEC
www.iks-jena.de.        RRSIG   NSEC ...

Man kann aber auch die Namen kryptographisch verschleiern. Ein entsprechender Auszug enthält die Beweise, daß es den Eintrag selbst nicht gibt, sowie auch keine Wildcardeinträge existieren. Die Nichtexistenz des Wildcard ist der Beweis, daß zwischen dem Hash der Zone "de" und dem nächsten Eintrag nichts steht. Nur die Zone selbst hat einen SOA, also muß der Hash "h319dm..." von "de" stammen.

;; QUESTION SECTION:
;iks-jena*.de.          IN PTR
;; AUTHORITY SECTION:
de.                     SOA     f.nic.de. ...
de.                     RRSIG   SOA ...
3tq06gmsoae3cupqcegc23rt137ane2c.de. NSEC3 1 1 15 BA5EBA11 (
  3TQ5J08P3FUF2070LK5EFJA0MHCQ0SG1 )
3tq06gmsoae3cupqcegc23rt137ane2c.de. RRSIG NSEC3 ...
h319dm5gc3edek691vqbhehot7vggj2b.de. NSEC3 1 1 15 BA5EBA11 (
  H31A76ES4OKI8LOPOTTQVTAMJ734376J NS SOA NAPTR RRSIG DNSKEY NSEC3PARAM )
h319dm5gc3edek691vqbhehot7vggj2b.de. RRSIG NSEC3 ...
svt1592dp1s8dffp1ks6fp0g313vf1i5.de. NSEC3 1 1 15 BA5EBA11 (
  SVT86EFS1182P3IPTNLF4E2IH0O34ULQ A RRSIG )
svt1592dp1s8dffp1ks6fp0g313vf1i5.de. RRSIG NSEC3 ...

Was man beim NSEC3 Walking herausbekommt, ist also eine Liste von Hashes der existierenden Einträge. Gegen diese Liste kann man dann die Ratetechnik anwenden. Der große Vorteil ist, daß das Vergleichen von Hashes um Größenordnungen schneller geht, als jeder Versuch eine DNS Anfrage zu stellen. Konsequenterweise gibt es einen nsec3walker.

Fazit

Öffentliches DNS enthält öffentliche Daten. Verstecken ist kein Ersatz für den Schutz der am Netz hängenden Systeme. Auch wenn es weh tut.

IPv6 bietet mit der dynamischen Adresszuteilung per SLAAC einen eleganten Weg, feste und temporäre IP Adressen an Hostsysteme zuzuweisen. Das hat Vor- und Nachteile. Gerade bei Umbauten im Netzwerk müssen spontan völlig unbeteiligte Systeme angefaßt werden. Aber wie minimiert man diese Aufwände?

Zunächst einmal verringert SLAAC, also StateLess Address AutoConfig, den Verwaltungsaufwand für den Netzwerker erheblich: Der Router verteilt ein Prefix, aus dem sich die Hosts im versorgten Netzwerk automatisch bedienen, solange das Prefix gültig ist. Soweit so einfach. Das ganze skaliert auch bestens, weil man mehrere Prefixe pro Router oder auch mehrere Router reinstellen kann und alles gleichzeitig funktioniert. Die Hosts lernen dann halt mehrere IP Adressen mit den dazugehörigen Routern. SLAAC ist damit der Mechanismus, mit dem man leicht und im laufenden Betrieb von einem Provider zum anderen wechseln kann, indem man in einer Übergangszeit beide gleichzeitig anschließt: Renumbering is easy.

Hosts leiten mit SLAAC mehrere IP Adressen aus dem Prefix ab: Zum einen eine statische IP, die i.d.R. aus der MAC Adresse oder einer anderen eindeutigen und dauerhaften Nummer gebildet wird, sowie einen Satz zufällig generierter IPs, die nur zeitweise genutzt werden (Privacy extensions).

Interessant für den Dauerbetrieb sind die festen IPs, die auf anderen Systemen hinterlegt werden: Im DNS und in Konfigurationdateien, Zugriffsbeschränkungen, Firewalls, Datenbanken etc. pp. Ändern sich diese Adressen ungeplant, ist der manuelle und zeitliche Aufwand bis zum reibungslosen Betrieb erheblich.

Linux und Window 2003 leiten die feste IPs von der MAC der jeweiligen Netzwerkkarte nach dem EUI-64 Schema ab. Ist zum Tausch von Technik gezwungen (z.B. durch Hardwareausfall), so bekommen diese Server neue IPs. Zur parallelen Inbetriebnahme eines neues Servers ist das ungemein nützlich: Die Technik wird einfach angesteckt und angeschaltet, schon ist das Gerät unter einer vorhersehbaren IP in einem beliebigen Netzsegment direkt erreichbar.

Erfolgt aber der Funktionsübergang von der alten zur neuen Hardware, ergibt sich ein Problem: Überall wo die alte Maschine mit ihrer IP bekannt war, muß die neue IP verbreitet werden. Eine Änderung im DNS scheint einfach, jedoch gibt es immer noch zuviel Software, die das Ergebnis einer DNS Auflösung nicht regelmäßig (TTL!) überprüfen. Firewalls wie iptables, Netzwerksoftware wie Sendmail und NTP, Datenbanken wie PostgreSQL müssen neu gestartet werden, um die Änderungen mitzubekommen – von den manuell eingetragenen IPs in verschiedenen Geräten und Scripten ganz zu schweigen. Wohl dem, der seine Konfigurationen zentral in Textform abgelegt hat: zgrep -Ri "alteip" /archiv/configs/ liefert zumindest Anhaltspunkte für die manuell anzufassenden Systeme. Aber auch zentrales Deployment und Konfigurationsmanagement generiert nach der Änderung der IP einen heftigen Schluckauf über die vielen betroffenen Systeme.

Um gegen Hardwaretausch besser gefeit zu sein, generiert Window 2008 die feste IP beim Einstellen in ein neues Netzwerk zufällig und speichert diese ab. Nun kann man am Server die Netzkarten auswechseln, ohne daß auf anderen Systemen etwas angefaßt werden muß: Der Hardwaretausch bleibt ein lokales Phänomen. Window 2008 macht die Netzwerkumgebung an der Adresse des versorgenden Routers fest: Ändert sich die default Route nicht, so behält Window auch für andere Prefixe den gleichen Suffix bei: Renumbering ist easy. Ändert sich aber die default Route, ändert Windows die festen IPs. Es genügt also, den Router zu tauschen, um spontanes Chaos für alle versorgten Windows Netze zu generieren. Zwar korrigieren sich die DNS Einträge von allein (wenn man das nicht verbietet), aber die anderen betroffenen Systeme sind ebenfalls überrascht.

Die Anforderungen an "feste IPv6 Adressen" sind also vielfältig und widersprüchlich:

  • Hardwaretausch soll nur lokale Aktionen erfordern.
  • Neue Hardware soll ohne großen Verwaltungsaufwand versorgt werden.
  • Neue Netze sollen ohne großen Verwaltungsaufwand einrichtbar sein.

Bei uns hat sich folgendes Schema als praktisch herausgestellt:

  • Router bekommen feste link-lokal Adressen: fe80::<gerät>:<interface>, wobei die Gerätenummer zentral in 0.8.e.f.ip6.arpa. als Wildcard verwaltet wird.
  • Da die Routingtabellen praktisch nur Link-Local Adressen enthalten (z.B. bei SLAAC und OSPF), entfällt die Notwendigkeit von offizellen IPv6 Adressen auf Transfernetzen und die Existenz der Reverse-DNS-Zone erweist sich als äußerst nützlich.
  • Bei den meisten Routern und Firewalls wird der Link-Local Teil auch für die offiziellen Adressen verwendet, womit ein konsistentes Erscheinungsbild beibehalten wird.
  • Für Server ist die Verwaltung in einer zentralen Geräteliste umständlich und unnötig. Da praktisch alle Systeme dual-stack laufen, existiert bereits eine IPv4 Adresse (öffentlich oder privat). Diese übernehmen wir in den lokalen Teil der IPv6 Adresse. Aus 192.168.240.12 wird 2001:db8:x:y:192:168:240:12. Die EUI-64 Beschränkungen für die höchstwertigen Bits existieren nicht, wenn der kennzeichnende "ff:fe" Teil fehlt.
  • Ein Windows System kann nur voll dynamisch oder voll statisch konfiguriert werden. Die IPv6 Adresse ergibt sich also aus der IPv4 und dem gültigen Netzprefix, während das default Gateway die schon bekannte Form fe80::<gerät>:<interface> hat.
  • Linux Systeme gestatten eine Mischkonfiguration. In /proc/sys/net/ipv6/conf/ kann man mit "echo 0 > autoconfig" die Adressgenerierung abschalten, während die Routen gelernt werden. Mit "echo 0 > accept_ra_defrtr" kann man das Lernen der default Route untersagen, aber die Adressbildung zulassen.

Beispielkonfig eines Linux Systems:

( cd /proc/sys/net/ipv6/conf
  # all future interfaces and all current interfaces (incl. the predefined ones)
  for i in default all eth*; do
    echo 0 > $i/accept_ra
    echo 0 > $i/forwarding
  done
)
... loading modules, creating bonding, vlans, etc. ...
( cd /proc/sys/net/ipv6/conf
  # automatic address in the managment network, but no routing
  echo 0 > mgmt/accept_ra_defrtr
  echo 1 > mgmt/accept_ra
  # fixed address in the public network, learn routers
  echo 0 > public/autoconf
  echo 1 > public/accept_ra
)
# ip -6 addr automatic dev mgmt
ip -6 addr add 2001:db8:42:23:203:0:113:131/64 dev public
# ip -6 route default via automatic dev public
ip -4 addr add 203.0.113.131/24 dev public

ip link set mgmt up
ip link set public up 
ip -4 route add default via 203.0.113.1

Zuerst natürlich die Defaults umstellen und dann die Interfaces gleichartig konfigurieren, anderenfalls handelt man sich eine schwer zu debuggende Racecondition ein. Unglücklicherweise überschreibt "all" nicht zwingend die Einstellung existierender Interfaces, deswegen werden diese extra benannt. Die Einrichtung neuer Interface im laufenden Betrieb generiert dank dieser Defaults keine Routingstörungen mehr.

Nachdem dann alle Interfaces existieren, werden nur die Ausgewählten mit IPv6 versorgt werden. Externe vorgegebene Angaben sind dabei nur auskommentiert angegeben. In dieser Konfiguration kann der Host in ein beliebiges Managment-Netz gestellt werden und hat dort immer eine durch die Hardware vorhersagbare Adresse aus dem jeweiligen Management-Netz. Die MAC Adresse der Hardware kann man im Notfall am Switchport ablesen, um sich einen Management-Zugriff zu verschaffen.

Die Routingtabelle schaut dann so aus:

2001:db8:42:23::/64 dev public  proto kernel  metric 256
2001:db8:42:f000::/64 dev mgmt  proto kernel  metric 256  expires 2592292sec
fe80::/64 dev public  proto kernel  metric 256
fe80::/64 dev mgmt  proto kernel  metric 256
ff00::/8 dev public  metric 256
ff00::/8 dev mgmt  metric 256
default via fe80::52:3167 dev public  proto kernel  metric 1024  expires 1647sec
unreachable default dev lo  proto kernel  metric -1  error -101 metric 10 255

Beispielkonfig eines Cisco Routers:

interface Loopback0
 ip address 192.0.2.56 255.255.255.255
 ipv6 address 2001:db8:42::56:0/128
!
interface FastEthernet1/0
 description Management IPv6 only
 ipv6 address FE80::56:1000 link-local
 ipv6 address 2001:db8:42:f000::56:1000/64
!
interface GigabitEthernet2/0.75
 description Core
 encapsulation dot1Q 75
 ip address 198.51.100.28 255.255.255.248
 ipv6 address FE80::56:2075 link-local
 ipv6 unnumbered Loopback0
 ipv6 ospf 1 area 0
!
router ospf 1
  network 198.51.100.24 255.255.255.248 area 0
!
ipv6 router ospf 1
!

Interessant ist am Cisco Beispiel der Verzicht auf Transfernetze im Core und die deutliche Vereinfachung der OSPF Konfiguration gegenüber der legacy Version.

Mit dem Wechsel eines älteren Systems auf einen neuen Kernel sollte sich ja nichts tun: Alle alten Programme sollten einfach weiter laufen. In einer berüchtigten Diskussion fordert die Phalanx der Kernelentwickler unmißverständlich, alles menschenmögliche zu tun, um die ABI des Kerneln kompatibel zu halten.

Nach dem Reboot schien alles zu funktionieren. Alles, nur IPv6 nicht. Genaugenommen ging SLAAC auf dem Manangementinterface nicht. Das Netz tauchte in der Routingtabelle auf, aber das Interface bekam keine Adresse.

Erst ein Blick auf die Konsole (kernlog) zeigte eine Unmenge von "Duplicate IP" Meldungen. Hat da ein anderes Gerät die gleiche IP? Evtl sogar die gleiche MAC? Mir selbst sind 1992 mal ein Satz Ethernet-Karten mit identischen MACs ins die Hände gefallen, ein Fabrikationsfehler. Ist das etwa wieder vorgekommen?

Am Switch zeigten zwei Port tatsächlich abwechselnd diese MAC. Beide Ports gehören zu einem Active-Backup-Bundle eines Bonding-Interfaces dieses Servers. Ist etwa das Bonding kaputt? Kurze Suche ergab den Bug 503082, einen Nicht-Bug: Won't fix, work's as designed. WTF?

Der Bug war hier ja gar nicht zutreffend, schließlich steht mein Bonding-Interface auf active-backup, nicht auf balance-xor. Oder vielleicht doch? ifconfig auf beiden Interfaces zeigt, daß beide Slaves des Bondings gleichgroße TX-Counter haben. Eine weitere Nachprüfung zeigt, daß /sys/class/net/bond0/bonding/mode tatsächlich auf balance-xor steht. WTF?

Im Initscript wird der Mode korrekt gesetzt. Das System ist frisch gebootet. Wie kann das sein? Der Versuch den Modus umzustellen scheitetet

# echo 1 > /sys/class/net/bond0/bonding/mode
Error: unable to update mode of bond0 because interface is up. 

Dann eben ausgeschaltet:

# (
 ip link set bond0 down;
 echo 1 > /sys/class/net/bond0/bonding/mode;
 ip link set bond0 up )
Error: unable to update mode of bond0 because it has slaves.

Das ist neu. Das ging vorher. Im Initscript steht eindeutig:

ip link set eth2 name eth down
ip link set eth3 name eth_bak down
echo +eth > /sys/class/net/bond0/bonding/slaves
echo +eth_bak > /sys/class/net/bond0/bonding/slaves
echo 1 > /sys/class/net/bond0/bonding/mode
# ... mehr Einstellungen
ip link set bond0 up

Und beim testweisen Reboot erscheint die Fehlermeldung tatsächlich direkt beim Booten.

Ich habe also die Festlegung des Modus vor die Einbindung der Slaves gelegt, neu gebootet und alles tat prima.

Ursachenforschung

Das Problem ist damit aus der Welt, aber nicht gelöst. Die Frage lautet: Warum konnte das überhaupt geschehen?

Die erste Frage muß also sein, wer, wann und warum diesen Änderung vorgenommen hat. Und das geschah im November 2011. Dort wird auch diskutiert, daß damit Änderungen an verbreiteter Software erforderlich werden, sonst fällt die auf die Nase. Wie ich.

Dort wird auch diskutiert, warum diese Änderung sinnvoll ist. Beim Hinzufügen eines Slaves zum Bundle werden viele modusabhängige Aktivitäten vorgenommen. Diese müßten rückabgewickelt und im neuen Modus neu vorgenommen werden. Ist diese Komplexität es an dieser Stelle wert?

Was wäre eigentlich zu tun? Der Code steht praktisch oben drüber:

/* linux/drivers/net/bonding/bond_sysfs.c */
switch (command[0]) {
 case '+':
   pr_info("%s: Adding slave %s.\n", bond->dev->name, dev->name);
   res = bond_enslave(bond->dev, dev);
   break;
 
 case '-':
   pr_info("%s: Removing slave %s.\n", bond->dev->name, dev->name)$
   res = bond_release(bond->dev, dev);
   break;

Es wäre also ein Leichtes, die bestehenden Interfaces rauszuwerfen, den Modus umzustellen und die Interfaces wieder reinzunehmen. Gut, man muß die Fehlerfälle durchgehen: Was ist, wenn sich ein Interface nicht im neuen Modus einbringen läßt? Was ist wenn alle Interfaces im neuen Modus nicht laufen? Eine Variante wäre eine Warnung und ein Rollback. Eine andere wäre, die Fehlermeldungen auszugeben und das Interface im Zweifel unbenutzbar (weil ohne Slaves) zurückzulassen.

Mit Linus Anspruch, man müsse die Kompatibilität halten, müßte man diese Anstrengungen eben unternehmen. Das das nicht gemacht wird, scheint sysfs keine ABI zu sein.

Ursachenforschung Teil 2

Die eigentliche Ursache liegt aber tiefer. Per Voreinstellung wird der Treiber mit balance-xor initialisiert. Dieser Modus erfordert aber spezielle Vorbereitungen des Switches auf der anderen Seite des Bundels. Mit einfach "anstecken und geht schon" hat das nichts zu tun. Im Gegenteil: Da der Switch nichts von der besonderen Konfiguration weiß, schickt er die Frames eines Interfaces auch zum anderen. Fertig ist der IPv6 DAD.

Das es bei IPv4 klappt, ist eher Zufall. Multicast und Broadcast kommen gleichermaßen durcheinander. Möglicherweise ist dieser Default auch der Grund für die Probleme mit den Streamern einer IPTV Plattform: Diese ertrinken in ihren eigenen Streams.

In einem anderen Ticket wird zuerst versucht, auch unter diesen Umständen DAD korrekt auszuführen. Dies muß aber konzeptionell scheitern, weil man nicht zwischen "das bin ich doch selbst" und "da benutzt jemand anderes meine MAC und meine IP" unterscheiden kann. Im Zweifel ist vom Schlimmsten auszugehen. Dies tut DAD konsequent. Konsequenterweise ist der DAD Fehler im balance-Mode ein "Won't fix", weil die Konfiguration fehlerhaft ist.

Es wäre also ein Doppelpatch angebracht: Zum einen sind die fehlerhaften Defaults auf einen harmloseren Fall umzustellen und zum anderen ist der Aufwand zum Moduswechsel notwendig.

Somebody is misusing my servers for DDoS as described in DNSSEC Amplification Attack. Neither the protocol nor the implemetation offers any kind of protection. Both try hard to provide the highest possible (attack) performance. I'll try to defend my systems anyway.

DNS amplification attacks are a well known problem. The attacker sends UDP queries with spoofed adresses to the DNS server. And the server responds with much larger responses to the vicitim. Many hundered Mbps may be generated with ease.

Known solutions

Server operators are helpless. They have only a few options:

  • They can stop offering DNS services. That means, that they do not longer provide their own zones.
  • They can limit the bandwith of the servers. The vast majority of the traffic will still be attack traffic. All packets are dropped with a similar probabiltiy. So this approach will mainly drop the productive packets, which are not resent as often as the attack ones.
  • They can rate limit the queries per client. Unfortunly this generates only a constant data stream of attack packets. DDoS works well with limited data rates per server, if you misuse enough servers. On the other hand the implementation required a lot of ressources.
  • They can respond to the most commom attack queries (ANY over UDP) with REFUSED or TRUNCATED. Those responses are smaller than the queries. So the bandwith benefit for the attacker is negative. Sadly the attacker is not interested in an efficient amplification. The remaining bandwith is still large enough to establish a problem.

All those solutions did not convince me.

Practical experience

In order to understand the problem better, please have a look at real world data. As first approach the interface of the server is switched to 100Mbps and becomes quickly saturated by eight Mbps of attack bandwith. The amplification effect is impressive. Oviously this is not a usable solution.

dnssec-attack-bandwith

Next try reducing the size of the responses using REFUSED. Bandwith drops instantaneously after switching the software. However the vicitims keep complaining. In this mode this server is able to saturate a typical 2Mbps business line. On the other hand queries other than ANY can be observed, which exploit similar amplification effects. This is the path to the next arms race.

dnssec-attack-refused

I'd like to have a self-learning system, which silently drops "bad" queries. It should provide auto-recovery, in order to be run without permanent supervision or manual adjustment. And it should not harm ordinary users.

Dampening—the theory

I remembered BGP Route Dampening, which seems to match my requirements. Wrong defaults discredited this proposal. I was warned and promised to check all my assumptions with real wolrd data.

The basic idea is to collect penalty points per IP (or network) depending on the type of the query and the size of the response. Those penalty points should decay over time. Applying a hysteresis to the penalty will start dampening at a high level and stop at a muich lower value. During dampening no processing (besides calculating penalty points) should occur. In this state all packets should be dropped silently, even if packets are malformed.

Ciscos implementation tastes badly, because they require a regular scheduled full table scan to time out the values. I'd like to have an algorithm with constant or at least logarithmic timeing. Decaying table entries should be handled by the way.

The average case should be fast when accessing the data. And the typical case is dominated by attacks. Therefore the table entries should be searched in the order of probabiltiy.

My prefered data stucture is a heap. Using linear storage, the heap does not require explicit meta information. All the structure information is derivated from the numeric properties of the array indicies. The high penalty values are stored in the front of the heap, so linear searching the heap arrary will work fine in the avarage case.

Instead of aging each of the heap entries regularly, I try to recalculate only the current and the first element. In order to delay the calculation, each entry requires a last access time field. The current penalty is calculated using the old penalty value and the time difference. Recalculation is only needed after serveral seconds, not every time.

Stable software is designed around deterministic ressouce usage. That's why the heap should have a fixed size. To keep it small, all entries which drop below a certain limit should be removed as soon as possible. In the case of a full heap new entries can simply overwrite the last old one.

In order to age all entries, it should be sufficient to decay the first entry with the largest penalty. Over the time the decay will press it downtree. In theory this will cause the whole tree to be processed.

Software safety urges me to consider integer overflows: Penalty points bumps againt an upper bound.

Dampening—the reality

Compile time only configuration let me start quickly with conservative defaults:

  • 1 point per query
  • Activate dampening at 40000 points
  • Deactivate dampening at 1000 points
  • Dropping from table at 100 points

The very first try was a desaster: Software crashed in the first few seconds without any useful output. The reason was as simple as embarrassing: I forgot to allocate the heap. While fixit it, I wrapped most functions with asserts.

And the next try was really promising.

dnssec-attack-dampening1

The old software (reponding with REFUSED) runs up to 02:00. While chaning the software, the output data rate drops naturally. The drastic increase is caused by the learning time, where all packets are processed. 19 seconds later, the first attacker was catched. Those are 40000 queries!

02:10:30.212 general: notice: running
02:10:49.136 client: warning: 99.71.68.170 enters dampening.
02:11:07.477 client: warning: 108.248.25.44 enters dampening.
02:11:27.321 client: warning: 173.218.224.224 enters dampening.
02:11:58.131 client: warning: 217.23.3.214 enters dampening.

But a few minutes later the server crashed again:

02:15:51.732 critical: dampening.c:282: fatal error:
02:15:51.732 critical: RUNTIME_CHECK(b > 0 && b <= dampening_last) failed
02:15:51.732 critical: exiting (due to fatal error in library)

This bug is located in the heap manipulation functions. I rewrote "heap_down" (thanks to Knuth, TAoCP) and removed 80% of the code.

Next try was delayed up to 13:30 and became a complete flop: Traffic increases without limit. The reason is, that the attackers IPs fight about the last entry in the heap. They override each other, so none of them gained enough points to be punished.

13:22:31.985 client: info: 61.247.0.2 added to dampening with 1 (replacing 212.49.98.14 with 31)

The simple solution is to not override the last entry, if it has a higher penalty. I log an error message, if the last entry has a large penalty values. In this case the table is fully booked with attacker IPs.

Those changes bring the system up. After a learning phase the output rate drops to two Mbps since 14:30. But about an hour later, most attacks come through again.

15:29:59.321 client: error: Dampening table to small! Entry 213.186.33.20 with 12175 at the end.

WTF? I do not see that many attackers in the parallel sniffing. I observe some occasional spikes with some new addresses. This bug hides somewhere else. My assumption, the table will be aged step by step, seems to be wrong. How? The most prominent entries do not stop attacking when I stop sending responses. They collect penalty points faster that the decay can remove.

Only logging the situation is a really bad idea. This GB of logs was unnecessary. If the table is full and the server stops with a fatal error, all I can to is to restart the server. That's why it's enough to log it once and clear the tables to allow a new learning phase.

In order to enable real aging, I recalculate three entries: The first, the last, and a random one. There is no need for perfect randomness, it is sufficient to hit the table at different points. Shortly after 17:00 the change is ready for deployment. But about an hour later the system crashes again!

17:55:51.045 general: critical: dampening.c:283: fatal error:
17:55:51.045 general: critical: RUNTIME_CHECK(b > 0 && b <= dampening_last) failed
17:55:51.045 general: critical: exiting (due to fatal error in library

There is no bug at this point. The system can't crash here. What happened?

The only possible explanation is, that the heap was modified while being processed. But bind runs as a single process, does it? No, it does not. Bind is multithreaded. Introducing a simple static counter at the API calls reveals the awful truth:

general: critical: RUNTIME_CHECK(duplicatecheck == 0) failed

After rewriting the whole API code to insert a central LOCK about the heap code, the system is working for the first time!

dnssec-attack-dampening2

I dramatically shortened the learning period by applying a new penalty table:

  • 10 points for an unknown client
  • 100 point for an ANY query, otherwise 1 point per query
  • If a query is repeated with the same ID, 100 additional points per repeat count are applied. So the first retry gains 100 points, the second gains 200, and further on. This escalates quickly.
  • Depending on the response size further penalties add to the entry. Responses up to 500 bytes gain one to five, up to 2500 byte 10 to 50, and larger packets 100 or 200 points.

Using this configuration attacks are detected within the first 40 packets. After two hours of running the situation consolidates to:

  5 minute  input rate  381000 bits/sec,   23 packets/sec
  5 minute output rate 7275000 bits/sec, 9479 packets/sec 

Out of 10000 queries only 30 are permitted and processed.

The Patch

This patch exists only for historical purpose. Please refer to a more usable version.

Update
diff -rbTuN bind-9.9.1-P3/bin/named/client.c bind-9.9.1-P3-dampening/bin/named/client.c
--- bind-9.9.1-P3/bin/named/client.c    2012-08-24 06:43:09.000000000 +0200
+++ bind-9.9.1-P3-dampening/bin/named/client.c  2012-09-23 22:05:38.000000000 +0200
@@ -55,6 +55,8 @@
        #include <named/server.h>
        #include <named/update.h>

+       #include "dampening.h"
+
        /***
         *** Client
         ***/
@@ -877,6 +879,8 @@
                        goto done;
                }

+               dampening_by_size(client, mr->length);
+
                result = client_allocsendbuf(client, &buffer, NULL, mr->length,
                                             sendbuf, &data);
                if (result != ISC_R_SUCCESS)
diff -rbTuN bind-9.9.1-P3/bin/named/dampening.c bind-9.9.1-P3-dampening/bin/named/dampening.c
--- bind-9.9.1-P3/bin/named/dampening.c 1970-01-01 01:00:00.000000000 +0100
+++ bind-9.9.1-P3-dampening/bin/named/dampening.c       2012-09-23 22:34:37.000000000 +0200
@@ -0,0 +1,415 @@
+       /*
+        * Dampening
+        * =========
+        *
+        * Purpose of dampening is to sort out misbehaving clients in order to
+        * serve the legitemate clients and protect the innocent vitims of a
+        * DNS(SEC) amplification attack caused by spoofed UDP queries.
+        *
+        * Overview
+        * ~~~~~~~~
+        *
+        * Each querier IP gets a additive penalty depening on the type of the
+        * query, the size of the answer, and other parameters.
+        *
+        * If the penalty reaches a limit, the state of the IP switches to a
+        * "dampening". In this state the server drops every query from this IP
+        * without further processing (besides adding penalty points).
+        *
+        * The penalty is decreased expotentially over time. So if no further points
+        * are added fast enough, the total penalty will drop below a secondary limit.
+        * Then the state of the IP is changed back to "normal".
+        *
+        * Implementation
+        * ~~~~~~~~~~~~~~
+        *
+        * DNS servers to not track their clients. So adding state to querier IPs
+        * requires a new storage model. Because access to this storage is needed
+        * by processing each query, the storage must be in memory only. In order
+        * to prevent ressouce exhaustion, the used memory is fixed.
+        *
+        * The assumed access pattern is that high penalty clients will be cause
+        * most of the queries. The used data structure is therefore a heap.
+        *
+        * The following operations are implemented:
+        *
+        *  a) Searching an IP by travering the heap array from front (high values)
+        *     to the end (beware of a partially filled heap).
+        *
+        *  b) Adding a new IP (if not found) by overwriting the very last entry
+        *     of the heap, if the heap is full. This will cause one of the low
+        *     penality entries to be lost.
+        *
+        *  c) If a searched IP was found, the last updated timestamp is used to
+        *     recalculate the penality value (expotential decrease plus new
+        *     points), update the state, and rebalanced the heap for this modified
+        *     entry only. If the penality value falls below a certain limit, the
+        *     entry is removed from the heap.
+        *
+        *  d) The top, last, and a random node are recalculated and rebalanced
+        *     after each update or insertion, if the nodes were not updated
+        *     recently.
+        *
+        * Using this approach, the operations on the heap will be O(log n) on
+        * each access. There is no need for a regular maintainence activity.
+        *
+        * TODO
+        * ~~~~
+        *
+        *  - All hardcoded parameters need to be (re)configurable.
+        *  - Allow ACL dependant penalty parameters.
+        *  - Check long time stability.
+        *  - Check, if the assumptions are really true.
+        *
+        */
+
+       #include "dampening.h"
+       #include <isc/util.h>
+       #include <stdlib.h>
+       #include <math.h>
+       #include <named/globals.h>
+       #include <named/log.h>
+
+       #include <isc/netaddr.h>
+       #include <isc/stdtime.h>
+
+       typedef unsigned int dampening_penalty_t;
+       typedef size_t dampening_position_t;
+       #define DAMPENING_NOTHING 0
+
+       struct {
+          struct {
+             dampening_penalty_t top, damp, norm, drop;
+          } limit;
+          size_t heapsize;
+          int updatedelay;
+          int halflife;
+       } dampening_configuration = {
+            { 60000, 40000, 1000, 100 },      /* limits */
+            5000,                             /* entries in the heap */
+            5,                                /* delay expire by seconds */
+            600                               /* half-live of penalty in seconds */
+       };
+
+       typedef struct dampening_entry {
+          isc_netaddr_t netaddr;
+          isc_stdtime_t last_updated;
+          dns_messageid_t last_id;
+          size_t last_id_count;
+          unsigned int penalty;
+          dampening_state_t state;
+       } dampening_entry_t;
+
+       static dampening_entry_t * dampening_heap = NULL;
+       static dampening_position_t dampening_last = 0;
+       static isc_mutex_t dampening_lock;
+
+       /*
+        * Helper functions
+        */
+       void dampening_init(void);
+       dampening_position_t dampening_search(const isc_netaddr_t * netaddr);
+       void dampening_add(const isc_netaddr_t * netaddr, dampening_penalty_t points, isc_stdtime_t now);
+       void dampening_update(dampening_position_t entry, dampening_penalty_t points, isc_stdtime_t now);
+       void dampening_update1(dampening_position_t entry, dampening_penalty_t points, isc_stdtime_t now);
+       dampening_state_t dampening_get_state(dampening_position_t entry);
+       void dampening_expire(isc_stdtime_t now);
+       void dampening_heap_up(dampening_position_t entry);
+       void dampening_heap_down(dampening_position_t entry);
+       void dampening_heap_swap(dampening_position_t a, dampening_position_t b);
+       int dampening_below_top(dampening_position_t entry);
+
+       /*
+        * Main API function
+        */
+       dampening_state_t dampening_query(ns_client_t * client) {
+          isc_stdtime_t now;
+          isc_netaddr_t addr;
+          dampening_state_t state;
+          dampening_position_t entry;
+
+          RUNTIME_CHECK(client != NULL);
+          LOCK(&dampening_lock);
+
+          if(dampening_heap == NULL)
+            dampening_init();                 /* Autoinit */
+
+          isc_stdtime_get(&now);
+          isc_netaddr_fromsockaddr(&addr, &client->peeraddr);
+
+          entry = dampening_search(&addr);
+          if(entry == DAMPENING_NOTHING) {
+             dampening_add(&addr, 10, now);
+             state = DAMPENING_NORMAL;
+          } else {
+             state = dampening_get_state(entry);
+          }
+
+          UNLOCK(&dampening_lock);
+          return state;
+       }
+
+       void dampening_by_qtype(ns_client_t * client, dns_rdatatype_t qtype) {
+          isc_stdtime_t now;
+          isc_netaddr_t addr;
+          dampening_position_t entry;
+          dampening_penalty_t points;
+
+          RUNTIME_CHECK(client != NULL);
+          INSIST(dampening_heap != NULL);
+
+          LOCK(&dampening_lock);
+          isc_stdtime_get(&now);
+          isc_netaddr_fromsockaddr(&addr, &client->peeraddr);
+
+          entry = dampening_search(&addr);
+          if(entry != DAMPENING_NOTHING) {
+             switch(qtype) {
+              case dns_rdatatype_any: points = 100; break;
+              default               : points =   1; break;
+             }
+
+             if(dampening_heap[entry].last_id == client->message->id) {
+                points += (dampening_heap[entry].last_id_count++)*100;
+             } else {
+                dampening_heap[entry].last_id = client->message->id;
+                dampening_heap[entry].last_id_count = 1;
+             }
+
+             dampening_update(entry, points, now);
+          }
+
+          UNLOCK(&dampening_lock);
+       }
+
+       void dampening_by_size(ns_client_t * client, size_t length) {
+          isc_stdtime_t now;
+          isc_netaddr_t addr;
+          dampening_position_t entry;
+          dampening_penalty_t points;
+
+          RUNTIME_CHECK(client != NULL);
+          INSIST(dampening_heap != NULL);
+
+          LOCK(&dampening_lock);
+          isc_stdtime_get(&now);
+          isc_netaddr_fromsockaddr(&addr, &client->peeraddr);
+
+          entry = dampening_search(&addr);
+          if(entry != DAMPENING_NOTHING) {
+             if(length <= 100)  points =   1; else
+             if(length <= 200)  points =   2; else
+             if(length <= 300)  points =   3; else
+             if(length <= 400)  points =   4; else
+             if(length <= 500)  points =   5; else
+             if(length <= 700)  points =  10; else
+             if(length <= 1000) points =  20; else
+             if(length <= 1500) points =  30; else
+             if(length <= 2000) points =  40; else
+             if(length <= 2500) points =  50; else
+             if(length <= 5000) points = 100; else
+                                points = 200;
+             dampening_update(entry, points, now);
+          }
+
+          UNLOCK(&dampening_lock);
+       }
+
+       /*
+        * Set up the heap: allocate the memory range. Change of size is possibe,
+        * so let dampening_last untouched.
+        */
+       void dampening_init(void) {
+          dampening_heap = realloc(dampening_heap, (1+dampening_configuration.heapsize) * sizeof(dampening_entry_t));
+          if(dampening_heap == NULL)
+            UNEXPECTED_ERROR(__FILE__, __LINE__, "realloc() failed");
+       }
+
+       /*
+        * Return the current state.
+        */
+       dampening_state_t dampening_get_state(dampening_position_t entry) {
+          REQUIRE(dampening_heap != NULL);
+          RUNTIME_CHECK(entry > 0 && entry <= dampening_last);
+
+          return dampening_heap[entry].state;
+       }
+
+       /*
+        * Simple search though the heap, exploiting the feature, that the heap
+        * is mapped to a continiously filled, linear array. High penalty values
+        * occur first, so they are searched first.
+        * Returns DAMPENING_NOTHING if no match.
+        */
+       dampening_position_t dampening_search(const isc_netaddr_t * netaddr) {
+          dampening_position_t i;
+
+          REQUIRE(dampening_heap != NULL);
+          for(i = 1; i <= dampening_last; i++) {
+             if(ISC_TRUE == isc_netaddr_equal(&dampening_heap[i].netaddr, netaddr))
+               return i;
+          }
+          return DAMPENING_NOTHING;
+       }
+
+
+       /*
+        * Add an element to the end (eventually overwrite the last one)
+        */
+       void dampening_add(const isc_netaddr_t * netaddr, dampening_penalty_t points, isc_stdtime_t now) {
+          REQUIRE(dampening_heap != NULL);
+
+          if(dampening_last < dampening_configuration.heapsize) {
+             dampening_last++;
+          } else {
+             if(dampening_heap[dampening_last].penalty > points) {
+                if(dampening_heap[dampening_last].penalty > dampening_configuration.limit.drop)
+                  isc_log_write(ns_g_lctx, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, ISC_LOG_ERROR,
+                                "Dampening table overflow! Restart dampening.");
+                dampening_last = 1;
+                return;
+             }
+          }
+
+          memcpy(&dampening_heap[dampening_last].netaddr, netaddr, sizeof(isc_netaddr_t));
+          dampening_heap[dampening_last].last_updated = now;
+          dampening_heap[dampening_last].penalty = 0;
+          dampening_heap[dampening_last].last_id = 0;
+          dampening_heap[dampening_last].last_id_count = 0;
+
+          dampening_update(dampening_last, points, now);
+       }
+
+       /*
+        * Update an existing entry with new points
+        */
+       void dampening_update(dampening_position_t entry, dampening_penalty_t points, isc_stdtime_t now) {
+          dampening_update1(entry, points, now);
+          dampening_expire(now);
+       }
+
+       void dampening_update1(dampening_position_t entry, dampening_penalty_t points, isc_stdtime_t now) {
+          int timediff;
+          char onbuf[ISC_NETADDR_FORMATSIZE];
+
+          REQUIRE(dampening_heap != NULL);
+          RUNTIME_CHECK(entry > 0 && entry <= dampening_last);
+
+          timediff = now - dampening_heap[entry].last_updated;
+          if(timediff > dampening_configuration.updatedelay) {
+             float penalty = dampening_heap[entry].penalty;
+             penalty *= exp(-(0.693*timediff)/dampening_configuration.halflife);
+             dampening_heap[entry].penalty = penalty;
+             dampening_heap[entry].last_updated = now;
+          }
+          if(dampening_heap[entry].penalty >= dampening_configuration.limit.top - points)
+            dampening_heap[entry].penalty = dampening_configuration.limit.top;
+          else
+            dampening_heap[entry].penalty += points;
+
+          if(dampening_heap[entry].state == DAMPENING_SUPPRESS &&
+             dampening_heap[entry].penalty < dampening_configuration.limit.norm) {
+
+             isc_netaddr_format(&dampening_heap[entry].netaddr, onbuf, sizeof(onbuf));
+             isc_log_write(ns_g_lctx, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, ISC_LOG_WARNING,
+                           "%s dampening removed.", onbuf);
+             dampening_heap[entry].state = DAMPENING_NORMAL;
+          }
+
+          if(dampening_heap[entry].state == DAMPENING_NORMAL &&
+             dampening_heap[entry].penalty > dampening_configuration.limit.damp) {
+
+             isc_netaddr_format(&dampening_heap[entry].netaddr, onbuf, sizeof(onbuf));
+             isc_log_write(ns_g_lctx, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY, ISC_LOG_WARNING,
+                           "%s dampening activated.", onbuf);
+             dampening_heap[entry].state = DAMPENING_SUPPRESS;
+          }
+
+          if(dampening_heap[entry].penalty < dampening_configuration.limit.drop &&
+             timediff > dampening_configuration.updatedelay) {
+             if(entry < dampening_last) {
+                dampening_heap_swap(entry, dampening_last);
+             } else {
+                entry = 0;
+             }
+             dampening_last--;
+          }
+
+          if(entry > 0) {
+             if(dampening_below_top(entry))
+               dampening_heap_down(entry);
+             else
+               dampening_heap_up(entry);
+          }
+       }
+
+       /*
+        * Expire penalties
+        */
+       void dampening_expire(isc_stdtime_t now) {
+          dampening_penalty_t poi[3] = { 1, dampening_last, 1+(random()%dampening_last) };
+          unsigned int i;
+
+          for(i=0; i<sizeof(poi)/sizeof(*poi); i++) {
+
+             if(0 < poi[i] && poi[i] <= dampening_last &&
+                now > dampening_heap[poi[i]].last_updated + dampening_configuration.updatedelay)
+               dampening_update1(poi[i], 0, now);
+          }
+       }
+
+       /*
+        * Heap operations
+        */
+       void dampening_heap_up(dampening_position_t entry) {
+          dampening_position_t e;
+
+          RUNTIME_CHECK(entry > 0 && entry <= dampening_last);
+
+          for(e = entry/2;
+              e > 0;
+              entry = e, e = entry/2) {
+             if(dampening_heap[entry].penalty <= dampening_heap[e].penalty)
+               break;
+             dampening_heap_swap(entry, e);
+          }
+       }
+
+       void dampening_heap_down(dampening_position_t entry) {
+          dampening_position_t e;
+
+          RUNTIME_CHECK(entry > 0 && entry <= dampening_last);
+
+          for(e = 2*entry;
+              e <= dampening_last;
+              entry = e, e = 2*entry) {
+
+             if(e+1 <= dampening_last &&
+                dampening_heap[e].penalty <= dampening_heap[e+1].penalty)
+               e = e+1;
+
+             if(dampening_heap[e].penalty <= dampening_heap[entry].penalty)
+               break;
+
+             dampening_heap_swap(entry, e);
+          }
+       }
+
+       void dampening_heap_swap(dampening_position_t a, dampening_position_t b) {
+          dampening_entry_t temp;
+
+          RUNTIME_CHECK(a > 0 && a <= dampening_last);
+          RUNTIME_CHECK(b > 0 && b <= dampening_last);
+          RUNTIME_CHECK(a != b);
+
+          memcpy(&temp,              &dampening_heap[a], sizeof(dampening_entry_t));
+          memcpy(&dampening_heap[a], &dampening_heap[b], sizeof(dampening_entry_t));
+          memcpy(&dampening_heap[b], &temp,              sizeof(dampening_entry_t));
+       }
+
+       int dampening_below_top(dampening_position_t entry) {
+          RUNTIME_CHECK(entry > 0 && entry <= dampening_last);
+          return entry <= 1 ||
+            (dampening_heap[entry].penalty <= dampening_heap[entry/2].penalty);
+       }
+
diff -rbTuN bind-9.9.1-P3/bin/named/dampening.h bind-9.9.1-P3-dampening/bin/named/dampening.h
--- bind-9.9.1-P3/bin/named/dampening.h 1970-01-01 01:00:00.000000000 +0100
+++ bind-9.9.1-P3-dampening/bin/named/dampening.h       2012-09-23 21:37:44.000000000 +0200
@@ -0,0 +1,14 @@
+       #ifndef _DAMPENING_H
+       #define _DAMPENING_H
+
+       #include <named/client.h>
+
+       typedef enum dampenting_state {
+          DAMPENING_NORMAL, DAMPENING_SUPPRESS
+       } dampening_state_t;
+
+       dampening_state_t dampening_query(ns_client_t * client);
+       void dampening_by_qtype(ns_client_t * client, dns_rdatatype_t qtype);
+       void dampening_by_size(ns_client_t * client, size_t length);
+
+       #endif /* _DAMPENING_H */
diff -rbTuN bind-9.9.1-P3/bin/named/Makefile.in bind-9.9.1-P3-dampening/bin/named/Makefile.in
--- bind-9.9.1-P3/bin/named/Makefile.in 2012-08-24 06:43:09.000000000 +0200
+++ bind-9.9.1-P3-dampening/bin/named/Makefile.in       2012-09-19 15:55:39.000000000 +0200
@@ -87,6 +87,7 @@
                        zoneconf.@O@ \
                        lwaddr.@O@ lwresd.@O@ lwdclient.@O@ lwderror.@O@ lwdgabn.@O@ \
                        lwdgnba.@O@ lwdgrbn.@O@ lwdnoop.@O@ lwsearch.@O@ \
+                       dampening.@O@ \
                        ${DLZDRIVER_OBJS} ${DBDRIVER_OBJS}

        UOBJS =         unix/os.@O@ unix/dlz_dlopen_driver.@O@
@@ -101,6 +102,7 @@
                        zoneconf.c \
                        lwaddr.c lwresd.c lwdclient.c lwderror.c lwdgabn.c \
                        lwdgnba.c lwdgrbn.c lwdnoop.c lwsearch.c \
+                       dampening.c \
                        ${DLZDRIVER_SRCS} ${DBDRIVER_SRCS}

        MANPAGES =      named.8 lwresd.8 named.conf.5
diff -rbTuN bind-9.9.1-P3/bin/named/query.c bind-9.9.1-P3-dampening/bin/named/query.c
--- bind-9.9.1-P3/bin/named/query.c     2012-08-24 06:43:09.000000000 +0200
+++ bind-9.9.1-P3-dampening/bin/named/query.c   2012-09-23 21:50:22.000000000 +0200
@@ -61,6 +61,8 @@
        #include <named/sortlist.h>
        #include <named/xfrout.h>

+       #include "dampening.h"
+
        #if 0
        /*
         * It has been recommended that DNS64 be changed to return excluded
@@ -7282,6 +7284,14 @@
                }

                /*
+                * Update the penalty and report the current state
+                */
+               if (dampening_query(client) == DAMPENING_SUPPRESS) {
+                       query_next(client, DNS_R_DROP);
+                       return;
+               }
+
+               /*
                 * Get the question name.
                 */
                result = dns_message_firstname(message, DNS_SECTION_QUESTION);
@@ -7323,6 +7333,7 @@
                INSIST(rdataset != NULL);
                qtype = rdataset->type;
                dns_rdatatypestats_increment(ns_g_server->rcvquerystats, qtype);
+               dampening_by_qtype(client, qtype);
                if (dns_rdatatype_ismeta(qtype)) {
                        switch (qtype) {
                        case dns_rdatatype_any:

If the patch allows runtime configuration, there will be a download button.

Horrible, horrible

Publishing source code is the only way to let somebody find the most horrible bug. Did nobody else notice this copy and wast desaster? Data is destroyed, the heap is corrupted!

+          memcpy(&temp,              &dampening_heap[a], sizeof(dampening_entry_t));
+          memcpy(&dampening_heap[a], &dampening_heap[a], sizeof(dampening_entry_t));
+          memcpy(&dampening_heap[b], &temp,              sizeof(dampening_entry_t));  

Let's take this straight: t=a; a=a; b=t; How should this ever work?

+          memcpy(&temp,              &dampening_heap[a], sizeof(dampening_entry_t));
+          memcpy(&dampening_heap[a], &dampening_heap[b], sizeof(dampening_entry_t));
+          memcpy(&dampening_heap[b], &temp,              sizeof(dampening_entry_t));

 That's correct! And the bug is already fixed in the code above. Many thanks to the employee of Suse, who dropped me a note.

And finally an other braindead bug: Calculation of decay misses a minus in the exponent. The penalty is increased expotentially over time. The patch above is already fixed, but the history of this bug will be presented soon.

penalty3-1