Ger ↴
  • Ger
  • Eng
  • Tag cloud
  • Site map
  • Login Login
    Forgot your password?
Lutz Donnerhacke
Navigation
  • Projekte
  • Veröffentlichungen
  • Blog
  • Impressum
  • Datenschutz
  • Suchen

Suche

Die Suche nach "0" ergab 92 Treffer

Flexible Netflow mit Voraggregation im Router

13.09.2018 17:57 Lutz Donnerhacke Tags: Internet , Cisco , WTF , Dokumentation 0

Was bei einem ISP so rein und raus geht, ist für die Planung wichtig und für Traffic-Engineering essentiell. Um diese Informationen abzugreifen, hat der Netzwerk-Gott Netflow erfunden. Normalerweise ist Netflow zu detailliert, um es auf höheren Bandbreiten einsetzen zu können, also muß man schon am Router aggregieren.

Beschränkung aufs Relevante

Von Interesse ist, mit welchen Autonomen Systemen der ISP Daten austauscht. Genau dafür gibt es ein vordefiniertes Profil:

#show flow record netflow ipv4 as      
flow record netflow ipv4 as:
  Description:        AS aggregation schemes
  No. of users:       0
  Total field space:  29 bytes
  Fields:
    match routing source as
    match routing destination as
    match interface input
    match interface output
    match flow direction
    collect counter bytes
    collect counter packets
    collect timestamp sys-uptime first
    collect timestamp sys-uptime last

Der entsprechende Eintrag ist gar nicht von der IP Version abhängig und paßt genauso auch auf IPv6. Er definiert einen Flow primär anhand des Quell- und Ziel-ASN. Für alle Pakete, die in allen match-Feldern übereinstimmen, werden die collect-Felder gesammelt. Ob summiert, das Minimum oder das Maximum genommen wird, ist vom jeweiligen Feld abhängig.

Den aufgesammelten Datenwust kann man sich lokal anschauen oder exportieren:

flow exporter server8
 destination 192.0.2.8
 source Loopback0
 transport udp 9995
flow monitor asn
 exporter server8
 record netflow ipv4 as
interface TenGigabitEthernet0/1/0
 ip flow monitor asn input
 ip flow monitor asn output
 ipv6 flow monitor as input
 ipv6 flow monitor asn output

Soweit so einfach.

Auf dem betreffenden Server läuft dann ein Netflow-Collector, der die Daten einsammelt und alle fünf Minuten in eine extra Datei auf der Platte ablegt. Auf dieser Datei führe ich nun meine Aggregation aus.

~# nfdump -r /var/spool/netflow/2018/09/27/12/nfcapd.201809271200 -A srcas -O bps | head
Date first seen          Duration  Src AS   Packets    Bytes      bps    Bpp Flows
2018-09-27 11:58:23.111   400.103   15169    38.9 M   47.7 G  953.1 M   1225 25304
2018-09-27 11:58:22.973   401.026       0   157.9 M   45.9 G  915.4 M    290 714869
2018-09-27 11:58:24.009   399.987    2906    23.3 M   34.4 G  688.4 M   1474  1223
2018-09-27 11:58:23.215   400.643   16509    26.0 M   33.7 G  673.7 M   1297 34785
2018-09-27 11:58:23.167   399.702   16097    18.3 M   27.0 G  541.3 M   1477  2560
2018-09-27 11:58:23.143   399.853    3356    16.9 M   25.2 G  503.6 M   1486  3869
2018-09-27 11:58:23.411   396.579    6185    15.3 M   22.7 G  458.5 M   1488  3624
2018-09-27 11:58:29.698   393.297   22822    10.8 M   16.0 G  324.6 M   1482   734
2018-09-27 11:58:23.095   400.807   32934    11.7 M   14.6 G  292.4 M   1251 11138

Sehr schön. Das AS0 ist Traffic, der von lokal kommt (ausgehend also). Dann plotte ich das mal auf:

2018-09-13-151512_447x228_scrot

WTF? Das ist definitiv nicht korrekt.

Fehlersuche

Bei der Auswertung sind ja viele einzelne Flows nochmal nachaggregiert worden. Wenn man die sich einzeln anschaut, dann gibt es keinen einzigen Flow, der mehr als 4,3 GByte an Daten enthält. Und von diesen gibt es eine ganze Menge. Alle mit der identischen Datenmenge.

Was ist passiert?

#show flow exporter templates
  Client: Flow Monitor asn
  Exporter Format: NetFlow Version 9
  Template ID    : 0
  Source ID      : 0
  Record Size    : 29
  Template layout
  _____________________________________________________________________
  |                 Field                   |  Type | Offset |  Size  |
  ---------------------------------------------------------------------
  | timestamp sys-uptime first              |    22 |     0  |     4  |
  | timestamp sys-uptime last               |    21 |     4  |     4  |
  | counter bytes                           |     1 |     8  |     4  |
  | counter packets                         |     2 |    12  |     4  |
  | interface input snmp                    |    10 |    16  |     4  |
  | interface output snmp                   |    14 |    20  |     4  |
  | flow direction                          |    61 |    24  |     1  |
  | routing destination as                  |    17 |    25  |     2  |
  | routing source as                       |    16 |    27  |     2  |
  ---------------------------------------------------------------------

Der Byte-Zähler hat eine Größe von 4 Byte, also 32bit, was genau bis 4294967295 enthalten kann. Anders gesagt: Mehr als 4,3 GByte kann man nicht pro Flow exportieren. Und diese Grenze wird immer wieder gerissen.

Normalerweise ist es kein Problem, einen Flow auf 4GB zu beschränken, da die typische Flow-Definition auch IPs und Portnummern enthält. Mit ausgelasteten 100Mbps braucht man für diese Datenmenge fast sechs Minuten, also mehr als das Meßintervall.

Hier jedoch wird schon auf Routerseite stark voraggregiert, die IP Adressen oder gar die Portnummern spielen keinerlei Rolle mehr. Deswegen werden deutlich weniger Flows erzeugt und exportiert. Andererseits werden die Zähler nun aber deutlich größer.

Ebenfalls auffällig ist, daß auch für die AS-Nummern nur 2 Byte zur Verfügung stehen. Und tatsächlich sind im Export alle 4-Byte ASNs durch 23456 ersetzt, so wie es RFC 4893 vorschreibt. Für die Auswertung ist das natürlich witzlos.

Also definiere ich mir einen maßgeschneiderten Record:

flow record asn
 match routing source as 4-octet
 match routing destination as 4-octet
 collect timestamp sys-uptime first
 collect timestamp sys-uptime last
 collect counter bytes long
 collect counter packets long

Und siehe an, das schaut viel besser aus. Wie im obigen Bild zu sehen ist, habe ich die Änderung am ersten Router gegen 17 Uhr vorgenommen. Die Spitzen sind danach deutlich angestiegen.

Aber warum überhaupt die seltsamen Spitzen? Alle halbe Stunde steigt der Traffic stark an, um dann wieder abzufallen? Sehr unwahrscheinlich.

Es sieht so aus, als ob die Flow-Records schlicht nicht gleichmäßig ankommen, sondern schubweise.

#show flow monitor asn
Flow Monitor asn:
  Description:       User defined
  Flow Record:       asn
  Flow Exporter:     server8
  Cache:
    Type:                 normal (Platform cache)
    Status:               allocated
    Size:                 200000 entries
    Inactive Timeout:     15 secs
    Active Timeout:       1800 secs
    Trans end aging:   off

Der Timeout für aktive Flows, also die, auf denen weiter Daten rein kommen, ist 1800 Sekunden, 30 Minuten, eine halbe Stunde!

Selbstverständlich sind bei der aggressiven Aggregation alle Flows dauerhaft aktiv. Sie werden also still aufsummiert und die Zwischensummen alle halbe Stunde exportiert. Im Ergebnis sieht man die Spitzen, da fast alle Flows zu der Zeit beginnen, als Netflow auf dem Interface aktiviert wurde.

Da alle fünf Minuten ein Snapshot beim Collector weggeschrieben wird, sollte man etwas häufiger Zwischensummen exportieren.

flow monitor asn
 exporter server8
 cache timeout active 100
 cache timeout update 150
 record asn

Und schon flutscht es.

2018-09-24-164028_497x248_scrot

Bereit zum Wechsel des Schlüssels der Root Zone?

27.03.2018 22:31 Lutz Donnerhacke Tags: DNSSEC , ICANN 0

Während der ICANN Diskussionen@AtLarge kam die Frage auf, wie man als einfacher Nutzer testen kann, ob man von dem DNSSEC Key Rollover der Root Zone betroffen ist. Das ist erstaunlich schwierig oder vollkommen trivial. Je nachdem was man will.

Das Rollover Problem

DNSSEC funktioniert im Grundsatz so, dass die Elternzone einen Verweis auf den aktiven Schlüssel der Kindzone setzt. Auf diese Weise kann man in der Kindzone der Schlüssel leicht gewechselt werden, indem man parallel den Verweis in der Elternzone aktualisiert.

Für die Root-Zone ist es nicht so einfach, weil es schlicht keine Elterzone für die Root gibt. Das Schlüsselmaterial der Root kommt stattdessen aus den (Festplatten-)Speichern der Resolver-Server. Aber wie kommt es da rein?

Zum einen kann man das Schlüsselmaterial manuell eintragen. Das hat man anfangs auch gemacht. Die Daten fanden sich in Zeitungen, auf Webseiten etc. pp. Inzwischen wird das Schlüsselmaterial von den Software- / Betriebssystemherstellern vorinstalliert mit ausgeliefert. Eine noch andere Möglichkeit besteht darin, während der Erstinstallation den aktuellen Schlüssel ungeprüft abzurufen und zu speichern.

Das Problem besteht nun darin, den Schlüssel zu wechseln. Die Abermillionen von Installationen müssen also neue Schlüsseldaten bekommen. Normalerweise eine unmögliche Aufgabe. Seit einigen Jahren gibt es aber ein automatisiertes Verfahren: RFC5011. Im Kern besagt der RFC, der Resolver soll alle Schlüssel, die er mit Hilfe der bekannten Schlüssel validieren kann, abzuspeichern hat. Damit kennt und vertraut er den neuen Schlüsseln, sobald er sie sieht.

Ob das in der Praxis funktioniert, wurde nicht wirklich getestet. Dieser Root KSK Rollover ist der erste Versuch, das zu tun. Und natürlich muss man damit rechnen, dass es schief geht. Es kann aus ganz unterschiedlichen Gründen schief gehen, z.B. könnte der Festplattenbereich durch den Resolver nicht beschreibbar sein.

Wenn es schief geht, kann der Resolver keinerlei DNS Anfragen mehr beantworten. Gar keine. Und nun stelle man sich vor, dieser betroffene Resolver steht bei einem großen Internetprovider: Es gibt einen Blackout für alle Kunden dieses Providers.

Konsequenzen

Wie viele solche Resolver es gibt, ist nicht bekannt. ICANN hat also beschlossen, den für letzten Oktober geplanten Rollover auszusetzen.

Neuere Methoden, ob ein Resolver den neuen Schlüssel schon gelernt hat, wurde in den letzten Monaten entwickelt und ausgerollt. Allerdings ist die Verbreitung dieser neuen Methoden auf die gut gepflegten Systeme beschränkt, die sowieso kein Problem mit dem Rollover haben werden. Eine Aufklärung des problematischen Dunkelfelds ist nicht zu erwarten.

Nun steht die Frage im Raum, ob und wann ein neuer Anlauf genommen wird. Neue Daten sind jedenfalls nicht zu erwarten.

Andererseits besteht das Problem, dass immer mehr Geräte auf den Markt kommen, die gar nicht in der Lage sind einen Root Schlüssel zu wechseln. Hier stehen die vielen unsupporteten IoT-Geräte im Vordergrund. Je länger man also mit dem Rollover wartet, desto mehr Anbieter können einen Rollover ignorieren. Nur ein regelmäßiger Wechsel zwingt die Industrie und die Admins zu eine korrekten Vorgehensweise.

Ängste

Das Thema ist emotionsgeladen:

  • Will ich riskieren, dass mein Internet ausfällt?
  • Wer wird mich (als Admin) verantwortlich machen, wenn es zu Ausfällen kommt?
  • Wer wird mich (als ICANN) verantwortlich machen, wenn es zu Ausfällen kommt?
  • Wie viele Leute werden einfach DNSSEC ausschalten, anstatt das Problem richtig zu beheben?

Dazu muss vorrangig die Frage beantwortet werden, ob man überhaupt betroffen ist.

Dazu gibt es eigentlich nur zwei Fälle:

  • Mein ISP (Resolver) kann DNSSEC und validiert. Dann kann es passieren, dass der KSK Rollover schief geht.
  • Mein ISP (Resolver) validiert DNSSEC nicht. Dann betrifft der Rollover mich (und ihn) gar nicht.

Um diese beiden Fälle einfach zu überprüfen, habe ich auf Anregung von Alan Greenberg eine Webseite erstellt, die den aktuellen Stand beim Nutzer ermittelt und den betreffenden Fall anzeigt.

Wie man den Stand von DNSSEC ermittelt

Natürlich kann man nicht aktiv DNS im Browser sprechen, um diese Fälle zu unterscheiden. Eine aktive Programmierung jeder Art fällt also schon mal flach.

Was man aber machen kann, ist Teile der Webseite so abzulegen, dass ein DNSSEC validierender Resolver diese nicht abrufen kann. Ich habe mich dazu entschieden eine Webseite mit unterschiedlichem CSS zu machen.

<link rel="stylesheet" href="dnssec.css">
<link rel="stylesheet" href="http://css.fail.donnerhacke.de/dnssec-fail.css">

Der zweite Teil des CSS überschreibt die vorherige Einstellung:

$ cat dnssec.css
.failed { display: none; }
.dnssec { display: block; }
$ cat dnssec-fail.css
.failed { display: block; }
.dnssec { display: none; }

Er kann aber nur abgerufen werden, wenn der Resolver keine DNSSEC Validerung macht.

Um das hin zu bekommen, muss absichtlich ein stabiler Fehler im DNSSEC-Setup erzeugt werden. Der Fehler muss so stabil sein, dass er den normalen Betrieb und automatische Korrekturmaßnahmen überlebt. Ich habe mich zu einer fehlerhaften Delegation entschieden:

$ORIGIN donnerhacke.de.
fail            NS      avalon.iks-jena.de.
                NS      broceliande.iks-jena.de.
                DS      12345 8 2 1234...

Die delegierte Zone ist dann nicht signiert, obwohl die Elternzone behauptet, sie müsse es sein.

Das schaut dann so aus:

fail.donnerhacke.de

Ein validierender Resolver kann das nicht auflösen und antwortet:

;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 10476
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;css.fail.donnerhacke.de. IN AAAA

;; Query time: 49 msec
;; SERVER: 100.100.100.100#53

Ein nicht validierender Resolver kommt dagegen zu folgendem Ergebnis:

donnerhacke.de.         NS      avalon.iks-jena.de.
donnerhacke.de.         NS      broceliande.iks-jena.de.
;; Received 185 bytes from 2001:678:2::53#53(a.nic.de) in 25 ms

css.fail.donnerhacke.de. CNAME  pro.donnerhacke.de.
pro.donnerhacke.de.     AAAA    2001:4bd8:1:1:209:6bff:fe49:79ea
donnerhacke.de.         NS      broceliande.iks-jena.de.
donnerhacke.de.         NS      avalon.iks-jena.de.
;; Received 219 bytes from 2001:4bd8:52:1:20a:e4ff:fe80:bec8#53(broceliande.iks-jena.de) in 1 ms

Die abgerufene Webseite wird also, in Abhängigkeit von den DNSSEC-Fähigkeiten des Resolvers, unterschiedlich aussehen.

Und nun gehe hin und teste Deinen Resolver: *click*

Portchannels mit ASA

26.03.2018 14:06 Lutz Donnerhacke Tags: Cisco , WTF 2

Die ASAs von Cisco können ja Portchannels, d.h. mehrere Leitungen zu Einer bündeln. Außerdem können Sie Failover, ein Gerät kann für ein anderes einspringen. Beides zusammen ist allerdings trickreich, da es im ungünstigen Fall zu Verbindungsabbrüchen führt.

Aufbau

Zwei ASAs im Active/Standby-Failover sind an getrennte Switche angeschlossen. Zur Erhöhung der Bandbreite wird ein Interface als LACP-Bündel zum übergeben. Ist die primäre ASA aktiv, funktioniert es prächtig.

asa# sh port-channel summary 
Group  Port-channel  Protocol  Span-cluster  Ports
------+-------------+---------+------------+------------------------------------
1      Po1(U)            LACP          No     Gi0/0(P)   Gi0/1(P)   
asa# failover exec mate show port-channel summary
Group  Port-channel  Protocol  Span-cluster  Ports
------+-------------+---------+------------+------------------------------------
1      Po1(U)            LACP          No     Gi0/0(P)   Gi0/1(P)   

Die Interfaces haben folgende Eigenschaften:

asa# sh interface | in Interface|MAC
Interface GigabitEthernet0/0 "", is up, line protocol is up
        MAC address 00.01.01, MTU 1500
Interface GigabitEthernet0/1 "", is up, line protocol is up
        MAC address 00.01.02, MTU 1500
Interface Port-channel1 "outside", is up, line protocol is up
        MAC address 00.01.01, MTU 1500

und

asa# fail exec mate sh interface | in Interface|MAC
Interface GigabitEthernet0/0 "", is up, line protocol is up
        MAC address 00.02.01, MTU 1500
Interface GigabitEthernet0/1 "", is up, line protocol is up
        MAC address 00.02.02, MTU 1500
Interface Port-channel1 "outside", is up, line protocol is up
        MAC address 00.02.01, MTU 1500

Wie man sieht übernimmt der Port-Channel die MAC Adresse des ersten Interfaces. Soweit so dokumentiert.

asa-portchannel-active

Wie man sieht, tauchen die beiden MAC Adressen der Interfaces am jeweiligen Switch auf. Die ASA antwortet im ARP mit der MAC Adresse des Port-Channels und alles ist gut.

Failover

Bei Umbauten kam es vor, dass die ASAs umschalteten. Das ist beabsichtigt und stellt kein Problem dar.

Die aktive ASA ist einfach das andere Gerät, der Status incl. der IPs und MAC Adressen wechselt zwischen den beiden ASAs und es funktioniert.

asa-portchannel-failover

Man sieht sehr schön, wie die Haupt-MAC des Portchannel wechselt und nun zur aktiven ASA zeigt.

Ein Blick in die ASA selbst bestätigt den Wechsel:

asa# sh interface | in Interface|MAC
Interface GigabitEthernet0/0 "", is up, line protocol is up
        MAC address 00.02.01, MTU 1500
Interface GigabitEthernet0/1 "", is up, line protocol is up
        MAC address 00.02.02, MTU 1500
Interface Port-channel1 "outside", is up, line protocol is up
        MAC address 00.01.01, MTU 1500

und

asa# fail exec mate sh interface | in Interface|MAC
Interface GigabitEthernet0/0 "", is up, line protocol is up
        MAC address 00.02.01, MTU 1500
Interface GigabitEthernet0/1 "", is up, line protocol is up
        MAC address 00.02.02, MTU 1500
Interface Port-channel1 "outside", is up, line protocol is up
        MAC address 00.01.01, MTU 1500

Achtung! Da die Funktion der ASAs gewechselt hat ist jetzt die aktive ASA auf der Hardware des zweiten Geräts. Deswegen haben sich die Hardware-MAC der Interfaces nicht verschoben, wohl aber die Funktion (mate ist jetzt das, was vorher aktiv war).

Probleme

Denkt man genauer über das nach, was da zu sehen ist, bleiben Fragen:

  • Warum wechseln die MAC Adressen der realen Interfaces nicht? Die MACs aller anderen Interfaces wechseln nämlich.
  • Warum erscheinen die MAC Adressen der Interfaces beim Switch? Eigentlich sollte doch nur die MAC des Port-Channels auftauchen.

Tatsächlich gibt es zeitweise Ausfälle, bei denen keine Außenkommunikation mehr möglich ist. Und da sieht es real so aus:

asa-portchannel-failover-fail

Die MAC Adresse des aktiven Port-Channels zeigt wieder auf die alte ASA, die aktuell passiv ist. Und die verwirft natürlich alle Pakete.

Wenn kurze Zeit später die aktive ASA ein Paket aussendet, lernen die Switche wieder um und es funktioniert wieder alles. Bis zum nächsten Vorfall.

Lösung

Ganz offensichtlich sendet eine ASA neben dem Port-Channel auch Daten über die Interfaces selbst aus. Und dabei benutzt sie die nicht wechselnden MAC-Adressen der Interfaces!

Man kann darüber diskutieren, ob es sich um einen Bug handelt. Man kann auch anfangen zu untersuchen, welche Art von Paketen die ASA auf den individuellen Ports eines LACP-Bundles aussendet. Das ist alles spannend, aber nicht zielführend.

Man muss die MAC des Port-Channels von der MAC der Interfaces trennen, und das geht!

asa(conf)# int Po1
asa(config-if)# mac-address 00.01.00 standby 00.02.00

Und das schaut dann so aus:

asa-portchannel-failover-ok1

Cool nicht?!

Die neue MAC taucht separat von den Interface-MACs auf und damit sollte ein Umschalten auch klappen:

asa-portchannel-failover-ok2

Tatsächlich springt nur die virtuelle, manuell gesetzte MAC des Port-Channels um.

Und die Probleme sind weg.

Vom Umgang mit DDoS

13.03.2018 10:51 Lutz Donnerhacke Tags: IPv6 , Internet , SSL , Verfügbarkeit 0

Seit einiger Zeit wird eine Webseite mit offenbar unliebsamen Inhalt, die wir hosten, angegriffen. Nach einem lockeren Anfang kam die inzwischen obligatorische Traffic-Bombe. Und die lies sich erstaunlich leicht entschärfen.

Entwicklung eines DDoS

Dieser Distributed Denial of Service ist eine Protestaktion. Das Ziel ist bekannt, die mögliche Ursache auch, der Initiator des Angriffs allerdings nicht. Also müssen wir mit den Folgen leben.

Zuerst fing es mit einem dauerhaften Abruf der Webseite (F5-Bombe) einiger weniger Quellen an. Da die betroffene Seite statisch ist, war die Last des Webservers zwar hoch, aber nicht bedrohlich.

Als nächstes schwenkte der Angriff auf HTTPS um, was den Server wegen der TLS-Bearbeitung stärker belastete.

Kurz darauf kamen die Anfragen aus aller Welt. Offenbar hatte der Angreifer ein Botnet von übernommen PCs und Webservern zugeschaltet. Nun war es nervig.

Als nächstes kam es zu einer verbesserten Form von Syn-Floods: Der TLS Verbindungsaufbau wurde komplett durchgeführt, aber keine Webseite abgefragt. Das führte zu einem ersten Ausfall der Verfügbarkeit, da nun die Maximalanzahl der Webserver-Prozesse/Threads in der Warteschleife fest saßen.

Die Gegenmaßname war eine Limitierung von TCP-Syns pro Host und Zeiteinheit mit dem recent-Modul von iptables. Zusätzlich wurden die entsprechenden Hosts, die zu viele 408-Einträge im Webserverlog hinterließen, per Script und iptables geblockt (und bei Inaktivität automatisch freigegeben). Selbstverständlich gibt auch der Abruf der angegriffenen Webseite selbst schon Strafpunkte.

Jeden Werktag gegen 16 Uhr kommt eine Verfügbarkeitsabfrage über einen der vielen Ist-Mein-Server-Erreichbar-Dienste. Kurz danach beginnt eine neue Variante des Angriffs. Meist sucht sich der Angreifer eine andere Webseite auf diesem Shared-Hosting-Webserver aus und bearbeitet diese.

Dabei kann der Angreifer auch mal eine Seite erwischen, die PHP und datenbanklastig ist. Das stört sehr, wird aber von dem inzwischen installierten Block-Skript kurzfristig eingefangen.

Und dann knallte es rein: Unmengen von UDP/GRE und anderen verbindungslosen Protokollen schlugen auf die IP ein. Es wurde ernst.

Umgang mit der Traffic-Welle

Glücklicherweise hält die Traffic-Welle immer nur kurze Zeit an. Es genügt aber, im Netz der Uplink-Carrieren Störungen zu verursachen.

Mit dem Einsatz von Paketfiltern (für z.B. UDP) gegen die betroffene IP möglichst weit außen, konnten die Auswirkungen gedämpft werden. Die kurzen Störungen zu Beginn der täglichen Welle blieben aber im Netz der Uplinks.

Zur leichteren Separierung des Traffics bekam die Webseite eine neue IP. Aber welche?

Der Massentraffic kommt i.d.R. von IoT-Geräten: Schlecht gewarteten und vor allem schlecht entwickelten Kleinstgeräten mit Internet-Anschluss. Könnte das ein Ansatzpunkt der Verteidigung sein?

Die meisten IP-Stacks sind inzwischen (1993) classless, d.h. sie müssen zu jeder IP die Netzmaske kennen, damit sie die Pakete routen können. Früher war das anders: Da ergab sich aus der IP die Netzmaske von allein.

Darüber hinaus werden beim Anschluss von IP-Netzen an einer Broadcast-Netztopologie (z.B. Ethernet) bestimmte IP-Adressen reserviert: Die letzte IP im Netz (Hostteil besteht nur aus 1-Bits) ist die Broadcast-Adresse.

Des weiteren gilt die erste Adresse im Netz (Hostteil  besteht nur ais 0-Bits) als Netzadresse. Diese soll nicht verwendet werden, weil einige IP-Stacks die Adressen intern invertiert ablegten und somit Netz- und Broadcast-Adressen nicht auseinander halten konnten. Um mit diesen Hosts weiter arbeiten zu können, wird bis heute generell nicht an die Netzadresse gesendet.

Es war also nahe liegend, als neue IP Adresse die a.b.c.0 aus einem (ehemaligen) Class-C Netz zu verwenden.

Gesagt, getan. Die Traffic-Spitzen sind weg.

Fazit

Es ist gelungen, Angriffe durch kaputte System an der Quelle zu unterbinden. Eben weil die Systeme auch noch anders kaputt sind.

Inzwischen hat sich der TCP-Angriff auch auf IPv6 ausgebreitet, alles vorher war immer nur IPv4.

Warten wir's ab.

Hinweis für Manager: Es nützt nichts zu fragen, ob der Angriff vorbei ist. Die Frage ist sinnlos.

Wie schnell sind Algorithmen in Haskell? - Vorbereitungen

06.02.2018 16:04 Lutz Donnerhacke Tags: Haskell 0

Haskell ist derartig faul, dass die gängigen Methoden zur Abschätzung der Programmlaufzeit scheitern. Nach der bahnbrechenden Arbeit von Okasaki bestand aber Hoffnung, dieses Thema in den Griff zu bekommen. Einige der Annahmen dieser Arbeit sind nicht anwendbar, wenn Haskell selbst zum Aufräumen von Zwischenergebnissen zu faul ist. Um selbst einige Ideen auszuprobieren, brauche ich reale Messwerte. Und die will ich jetzt erzeugen.

Profiling

Der klassische Weg mit Performance Problemen umzugehen, ist Profiling. Als Beispiel muss wieder die Fibonacci-Folge herhalten. Und das besonders ineffizient:

fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n = a + b
 where
  a = fib (n-1)
  b = fib (n-2)

Ich habe die Zwischen-Terme a und b extra benannt, damit sie im Profiling dann auch auftauchen.

Ich möchte die Zeit messen, die Haskell zur Abarbeitung braucht, dazu lasse ich mir die verstrichene CPU Zeit vor und nach der Abarbeitung geben. Um das Programm mit verschiedenen Werten zu testen, lese ich das Argument der Funktion von der Kommando-Zeile ein.

import System.Environment
import System.CPUTime

main = do
  [arg] <- getArgs
  limit <- readIO arg
  print limit
  start <- getCPUTime
  let result = fib limit
  finish <- getCPUTime
  print (finish - start) -- picoseconds

Nun noch kompilieren und probieren:

$ ghc -O3 -prof -fprof-auto -rtsopts fib.hs
[1 of 1] Compiling Main             ( fib.hs, fib.o )
Linking fib ...
$ fib 20
20
14000000
$ fib 200
200
14000000

Huch? Warum sollte das Programm die gleiche Zeit brauchen, wenn es doch so unterschiedliche Berechnungen zu vollführen hat? Die angegebene Zeit ist in Picosekunden, also stehen da real 14µs. Etwas wenig.

Schauen wir mal in das Profiling:

$ fib 200 +RTS -p
200
5000000
$ cat fib.prof
                                                  individual      inherited
COST CENTRE MODULE             no.     entries  %time %alloc   %time %alloc

MAIN        MAIN                54          0    0.0    1.1     0.0  100.0
 CAF        Main               107          0    0.0    0.1     0.0    2.2
  main      Main               108          1    0.0    0.0     0.0    0.0
 CAF        GHC.IO.Encoding     95          0    0.0    5.5     0.0    5.5
 CAF        GHC.IO.Handle.FD    92          0    0.0   56.5     0.0   56.5
 CAF        GHC.IO.Handle.Text  91          0    0.0    0.1     0.0    0.1
 CAF        Text.Read.Lex       88          0    0.0    1.1     0.0    1.1
 CAF        GHC.IO.Encoding.Ic  83          0    0.0    0.4     0.0    0.4
 CAF        GHC.Conc.Signal     73          0    0.0    1.0     0.0    1.0
 CAF        GHC.Read            68          0    0.0    1.5     0.0    1.5
 main       Main               109          0    0.0   30.5     0.0   30.5

Man sieht sehr schön. dass die übergebene Zahl vom Lexer bearbeitet, Daten ausgegeben wurden. Was aber komplett fehlt, ist jedoch die Abarbeitung der fib-Funktion?

Haskell ist schlicht zu faul, die Berechnung überhaupt auszuführen, wenn sowieso niemand das Ergebnis braucht! Um dem abzuhelfen, muss man das Ergebnis also auch noch ausgeben.

main = do
  [arg] <- getArgs
  limit <- readIO arg
  print limit
  start <- getCPUTime
  let result = fib limit
  finish <- getCPUTime
  print (finish - start)
  print result

Einfach am Ende noch das Ergebnis ausgeben. Sollte tun, oder?

$ fib 35
35
9000000
[ ... Pause ...]
14930352

Das Ergebnis erscheint mit deutlicher Verzögerung nach der Ausgabe. Insofern sind  die angegebenen 9µs definitiv unpassend. Was ist passiert?

Haskell war zu faul, das Ergebnis sofort zu berechnen. Erst als es durch die Ausgabe gezwungen wurde, hat es die Berechnung nachgeholt. Da war die Zeitmessung allerdings schon lange vorbei.

Die Verwendung des Ergebnisses muss also vor dem Ende der Zeitmessung erfolgen!

main = do
  [arg] <- getArgs
  limit <- readIO arg
  print limit
  start <- getCPUTime
  let result = fib limit
  print result
  finish <- getCPUTime
  print (finish - start)

Und das gibt:

$ fib 35 +RTS -p
35
14930352
1354134000000

Jetzt zeigt die Zeitmessung auch passende 1,3 Sekunden. Und was sagt das  Profilinging?

                                                  individual      inherited
COST CENTRE   MODULE                   entries  %time %alloc   %time %alloc

MAIN          MAIN                          0    0.0    1.1   100.0  100.0
 CAF          Main                          0    0.0    0.0     0.0    2.1
  main        Main                          1    0.0    2.0     0.0    2.0
 CAF          GHC.IO.Encoding               0    0.0    5.4     0.0    5.4
 CAF          GHC.IO.Handle.FD              0    0.0   54.6     0.0   54.6
 CAF          GHC.IO.Handle.Text            0    0.0    0.1     0.0    0.1
 CAF          Text.Read.Lex                 0    0.0    1.1     0.0    1.1
 CAF          GHC.IO.Encoding.Iconv         0    0.1    0.4     0.1    0.4
 CAF          GHC.Conc.Signal               0    0.0    1.0     0.0    1.0
 CAF          GHC.Read                      0    0.0    1.5     0.0    1.5
 main         Main                          0    0.0   32.8    99.9   32.8
  main.result Main                          1    0.0    0.0    99.9    0.0
   fib        Main                   29860703   28.3    0.0    99.9    0.0
    fib.a     Main                   14930351   40.8    0.0    40.8    0.0
    fib.b     Main                   14930351   30.8    0.0    30.8    0.0

Die eine Zahl Differenz zwischen a und b macht allein 10% der benötigten Rechenleistung aus. Das ist beachtlich.

Zeitverhalten

Zurück zum urspünglichen Problem: Wie schnell sind Algorithmen in Haskell? Die Frage bezieht sich normalerweise auf die Entwicklung des Zeitverhaltens in Abhängigkeit von der zu verarbeitenden Datenmenge.

Grundsätzlich könnte man das bisherige Programm für jeden Zahlenwert einzeln aufrufen und die Zeiten aufschreiben. Aber das ist aufwändig und kann von Haskell selbst gemacht werden.

Zunächst einmal ist zuverlässig sicher zu stellen, dass der Versuch der Berechnung nicht über alle Zeiten hinauswächst. Die Berechnung muss notfalls abgebrochen werden. Das löst die Funktion System.Timeout.timeout: Sie lässt die Berechnung in parallelen Thread laufen und bricht den notfalls ab. Das ganze Signalisierungsverhalten ist zwar prinzipell einfach, aber die Bibliotheksfunktion löst auch die kleinen Dreckeffekte, über die man selbst noch nicht gestolpert ist.

Anderseits ist der bisherige Rückgabewerte von CPUTime schwer verständlich. Besser wäre ein klarer Wert ins Sekunden. Deswegen wechsle ich hier auf Data.Time.getCurrentTime.

Der dritte Punkt, der zu beachten ist, behandelt die vollständige Evaluierung der Eingabedaten vor der Ausführung der zu testenden Funktion, sowie das Erzwingen der notwendigen Berechnung. Dazu verlange ich zwei Hilfsfunktionen: pre dient dazu, aus dem numerischen Wert der Datenmenge auch brauchbare Ausgangsdaten zu erzeugen, z.B. eine Baumstruktur, über die der Algorithmus dann laufen soll. post dagegen extrahiert aus dem Ergebnis des Algorithmus eine Zahl. Diese Extraktion muss nicht zwingend alle Teile des Ergebnisses auswerten, es genügt z.B. die Länge eines Rückgabestrings zu ermitteln, wenn es sich um einen Algorithmus zur Stringverarbeitung handelt, schließlich sind die konkreten Inhalte des Strings dabei nicht relevant.

Die Eingabedaten werden komplett angezeigt, um alle Details zu evaluieren. Zum Erzwingen der Auswertung an der richtigen Stelle werden die Zahlenwerte auf positive Wertebereiche per Control.Monad.guard geprüft.

import Data.Time
import Control.Monad

profile :: (Show a) => (a -> b) -> (Int -> a) -> (b -> Int) -> Int
        -> IO (Maybe NominalDiffTime)
profile test pre post n = do
  let input = pre n
  guard $ 0 <= length (show input) -- fully evaluate before measuring
  timeout 5000000 (do
      start <- getCurrentTime
      let output = test input
      guard $ 0 <= post output -- evaluate the relevant part
      finish <- getCurrentTime
      return $ finish `diffUTCTime` start
    )

Nochmal in Worten:

  • Die Funktion pre macht aus einem Zahlenwert die Datenstruktur a, auf der der Algorithmus arbeiten soll.
  • Die Funktoin test führt den Algorithmus aus, in dem sie als Argument eine Datenstruktur a übergeben bekommt und eine Datenstruktur b zurück liefert.
  • Die Funktion post presst aus der Ergebnisstruktur b einen positiven Zahlenwert heraus, wobei sie die Ausführung des Algorithmus erzwingt.
  • Klappt alles, erhalten wir die Zeit, die die Funktionen test und post benötigt haben.
  • Die Berechnung ist mit einem Timeout von 5 Sekunden umschlossen: Endet die Berechnung nicht rechtzeitig gibt es Nothing.

Mit einer solche Mess-Funktion kann man nun eine Mess-Reihe automatisch erzeugen:

mkProfile :: (Show a) => (a -> b) -> (Int -> a) -> (b -> Int) -> IO ()
mkProfile test pre post = worker (profile test pre post) testpoints
 where
  testpoints = [ 1 .. 9 ]
            ++ [ x*10^c | c <- [0 .. ], x <- [10 .. 99] ]
  worker f (x:xs) = do
    r <- f x
    case r of
      (Just y) -> do putStrLn . shows x . showChar ',' . init $ show y
                     when (y < 5) $ worker f xs
      _        -> putStrLn "# Timed out"

Für eine Reihe von Testpunkten, die immer größere Abstände annehmen, wird die Laufzeit ermittelt. Da die Darstellung der Laufzeit ein finales "s" für Sekunden am Ende hat, wird es per init abgeschnitten. Der Rest der Ausgabe ist ein CSV.

Die Messung wird fortgeführt, wenn überhaupt ein Ergebnis ermittelt werden konnte (kein Timeout) und die Laufzeit die 5 Sekunden nicht überschreitet. Die Timeout-Funktion garantiert nur, dass der Abbruch nicht vor 5 Sekunden stattfindet. Länger kann es durchaus dauern, da die typische Haskell-Runtime kooperatives Multitasking betreibt.

Und was sagt das zu unserer Fibonacci-Folge?

main = mkProfile fib id id

Die Hauptfunktion ist denkbar einfach: Erstelle ein Profil der fib-Funktion, wobei die Eingabemenge aus der Zahl besteht, die übergeben wird. Das Ergebnis ist ebenfalls eine Zahl, die so übernommen wird wie sie ist.

image005

Das ist klar expotentielles Wachstum.

Damit steht nun ein Handwerkszeug zur Verfügung, um andere - interessantere Algorithmen - zu untersuchen.

Nachbetrachtung

Natürlich würde man die Fibonacci-Folge in Haskell niemals so schreiben, sondern eher so:

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
fib1 x = fibs !! x

main = mkProfile fib1 id abs

Die Fibonacci-Folge besteht aus den Werten 1, 1 und dann der Summe der Fibonacci-Werte beginnend am Anfang und um ein Element versetzt. Die Liste ist unendlich lang, aber dank des faulen Haskells besteht da kein Problem: Denn nur der x-te Wert wird dann heraus genommen.

Da wir mit Int rechnen, gibt es Überläufe während der Rechnung. Anstatt auf den korrekten Typ Integer auszuweichen, nehme ich hier einfach den Absolutwert als Ergebnis. Es geht ja nicht um die Berechnung der Werte an sich, sondern um die Art der Ermittlung (also den Algorithmus).

Und das ergibt dann?

image007

Das ist spannend! Mit einigen Ausreißern geht die Berechnung fast linear durch und zwar in die zig Millionen, statt bis 42.

Beobachtet man allerdings den RAM-Verbrauch während der Abarbeitung, so steigt dieser bis zu 4 GB an (mehr gebe ich dem Prozess hier nicht). Dann bricht die Abarbeitung ab. Die Stellen, bei denen die Laufzeit so stark ansteigt, entspricht den Stellen, wo das System massiv nach RAM verlangt.

Was ist passiert?

Die Definition von fibs ist global, d.h. der Wert steht immer zur Verfügung. Anders gesagt, verwirft Haskell die ermittelten Werte des Feldes nie wieder. Deswegen steigt der RAM-Verbrauch immer weiter an. Am Ende sind über 40 Mio. Werte berechnet worden. Incl. Overhead für die verkettete Liste sind das also ungefähr 80 Byte pro Eintrag.

Andererseits führt das zu den niedrigen Laufzeiten, da ja alle Werte schon berechnet wurden.

Alle Werte? Nein, die Schrittweite steigt ja immer um eine Größenordung. Damit müssen auch immer mehr Zwischenwerte berechnet werden, um auf das nächste Ergebnis zu kommen. Dies erklärt die sprunghaft steigende Abarbeitungszeit, die mit der größeren Schrittweite korreliert.

Was wäre nun, wenn man korrekterweise nicht mit den übrig gebliebenen Berechnungen des vorherigen Versuchen zu betrügen versucht?

fib2 x = fib' !! x
  where fib' = 1 : 1 : zipWith (+) fib' (tail fib')

Hier werden die Zwischenergebnisse in einer lokalen Variablen gehalten, die außerhalb des Aufrufs keine Bedeutung hat. Es besteht sogar die Hoffnung, dass die nicht benötigten Zwischenergebnisse direkt verworfen werden, und so der RAM-Bedarf nicht exorbitant steigt.

image007

Zwei Dinge sind auffällig:

  • Der RAM-Bedarf steigt wieder so an, das Programm hält aber länger durch, wenn die Grenze erreicht ist.
  • Das Laufzeitverhalten ist ähnlich, erreicht aber deutlich höhere Werte.

Was ist passiert?

Haskell erkennt durchaus, dass die Definition von fib' unabhängig vom Aufrufparameter ist und benutzt diese Tatsache zu dem Memorization-Trick, der bei fibs noch explizit aufgeschrieben wurde. Das führt zu dem ähnlichen Verhalten bis zu 40 Mio. Danach muss (und kann) Haskell die Zwischenergebnisse immer wieder verwerfen, um weiter arbeiten zu können.

Kann man Haskell den Algorithmus expliziter vermitteln? Schließlich ist es ja wenig elegant, eine Liste von uninteressanten Werten jedesmal von Anfang an zu durchlaufen (was schonmal minimal lineare Laufzeit generiert)

fib3 x = head $ drop x fib'
 where fib' = 1 : 1 : zipWith (+) fib' (tail fib') 

Der einzige Unterschied ist hier, dass statt dem (!!) Operator, der eine Liste bis zum x-ten Element durchläuft, der Anfang der Liste weggeworfen wird, um dann das erste Element zu nehmen.

image007

Das gleiche Bild, allerdings sind die Laufzeiten kürzer und die Abarbeitung erfolgt glatter. Es ist nicht so erratisch. Das exorbitante RAM-Bedarf ist allerdings noh da.

Also muss auf die Liste als solche verzichtet werden.

fib4 x = fib' x (1,1)
 where
  fib' 0 (a,_) = a
  fib' n (a,b) = fib' (n-1) (b,a+b) 

Dieses Programm nimmt den Kern der zipWith (+) Funktion explizit her und zählt parallel rückwärts. Es sammelt die Werte gar nicht erst auf, sondern verwirft sie sofort.

image007

Der RAM-Bedarf ist diesmal konstant. Das ist allerdings erwartungskonform.

Eine echte Überraschung ist allerdings die Laufzeit dieses Algorithmus:

  • Die Laufzeit ist praktisch linear mit 2 Sekunden je 10 Mio.
  • Allerdings sind die anderen Algorithmen durch die Bank deutlich schneller als die direkte Berechnung der Werte.
Laufzeit im Vergleich
Mio fib1 fib2 fib3 fib4
10 53ms 53ms 52ms 1750ms
20 192ms 189ms 189ms 3714ms
30 349ms 264ms 238ms  
40 321ms 361ms 298ms  
50   839ms 339ms  
60   366ms 365ms  

Fazit

Die von Haskell angewendeten Methoden der Memorization gestatten es, perfomante Algorithmen zu schreiben, die im Gegensatz zu den klassischen Algorithmen geschickter betrügen. Man muss bei Performance Messungen also höllisch aufpassen.

ASA mit mehrfacher Default Route

29.11.2017 16:28 Lutz Donnerhacke Tags: Cisco , WTF 0

Eine Appliance, die in einer fremden Umgebung Daten einsammelt, soll von extern angesprochen werden. Zum einen muss der Hersteller Fernwartung machen und zum anderen gibt es eine Gruppe von Stellen, die auf die gesammelten Daten zugreifen sollen. Völlig unerwartet wird das geschützte Netzwerk dieser Stellen ebenfalls mit öffentlichen IP Adressen betrieben, so dass für beide Zwecke eine Default Route benötigt wird. Das Ganze soll trotzdem nur mit einer ASA funktionieren. Eine Aufsplittung in mehrere Kontexte (ASA-Virtualisierung) scheitert am fehlenden Support für Remote-Access. Es muss also anders gehen.

Aufgabenstellung

Die Appliance arbeitet in einem anderen, größeren Netzwerk und benutzt selbst nur private IP Adressen (hier 192.0.2.x). Um alle Messstellen erreichen zu können, zeigt die Default Route dieses Gerätes in das fremde Netzwerk (hier grau).

Der Remote-Access Teil (hier grün) ist ein einfaches Setup. Um die vorab unbekannten Clients erreichen zu können, muss die Default Route ins Internet zeigen. Dieser Uplink der ASA benutzt öffentliche IPs (PA-space, hier 189.51.100.x). Die Remote-Access Clients bekommen aus einem Adresspool des lokalen Netzes IPs, so dass sie ohne weiteres Routing auf die Appliance zugreifen können. Von Seiten der Appliance aus erscheint der Client im LAN.

Die ursprüngliche Annahme war, dass die abfragenden Stellen, die über ein extra gesichertes Netzwerk zugeführt werden, ebenfalls in koordinierter Weise mit nicht-öffentlichen Adressen arbeiten würden. In diesem Fall hätte eine spezifische Route zu all diesen Netzen genügt. Allerdings haben die Planer dieses Netzes den Koordinationsaufwand gescheut und verlangen stattdessen, öffentliche IP-Adressen aus dem lokalen PA-Space zu benutzen, der auch normal über Internet erreichbar ist.

ASA multiple default routes

Die Gegenstellen hinter dem geschützten Netzwerk (hier rot) sind vorab ebenso wenig bekannt, wie die Fernwartungs-Clients (grün). In dem hier skizzierten Bild, soll eine solche Gegenstelle eine ganz andere öffentliche IP Adresse haben (hier 203.0.113.133). Um solche Adressen zu erreichen, müsste eine Default Route ins rote Netz gesetzt werden.

Angefasst werden darf nur die ASA, alle anderen Geräte unterliegen fremder Verfügungsgewalt.

Remote-Access

Die Konfiguration für das Remote-Access VPN gemäß Lehrbuch:

interface Ethernet0/0
 nameif green
 security-level 0
 ip address 198.51.100.25 255.255.255.192
!
interface Ethernet0/1
 nameif gray
 security-level 100
 ip address 192.0.2.5 255.255.255.0
!
route green 0.0.0.0 0.0.0.0 198.51.100.1 1
!
ip local pool vpnippool 192.0.2.101-192.0.2.102 mask 255.255.255.0
!
crypto map vpn_outside 999 ipsec-isakmp dynamic vpn_dyn
crypto map vpn_outside interface green
...

Erwartungsgemäß tut das seinen Dienst:

  • Der entfernte Supportmitarbeiter kann per Default Route erreicht werden.
  • Der entfernte Supportmitarbeiter bekommt über das VPN eine IP aus dem inside-LAN.
  • Die Appliance redet mit ihm über das lokale Netzwerk. Extra Routen werden nicht benötigt.

Einmal hin ...

Um die Stellen, die mit öffentlichen IP Adressen aus dem geschützten Netz kommen, versorgen zu können, sind einige Überlegungen notwendig.

Zuerst fällt auf, dass der Verbindungsaufbau bei unbekannten Gegenstellen nur von extern kommen kann. Die Clients werden ausschließlich eine öffentliche IP au dem Netz 198.51.100.248/29 zugreifen können. Von dem privaten Netz hinter der ASA wissen sie nichts.

Damit die Appliance nichts von den fremden Adressen merkt, braucht es eine Doppel-NAT:

interface Ethernet0/2
 nameif red
 security-level 0
 ip address 198.51.100.254 255.255.255.248
!
object network o-server
 host 192.0.2.4
!
nat (red,green) source dynamic any interface destination static interface o-server

Eingehende Pakete über das rote Interface von beliebiger Quelle an die Interface-IP werden so genanntet, dass die Quell-IP zur grünen Interface-Adresse und die Zieladresse, die der Appliance wird. Damit sieht die Appliance nur Anfragen aus ihrem LAN.

Um zu testen, dass diese Idee auch tut, wird ein Testaufbau erstellt:

ASA multiple default routes Test

Anstelle des roten Netzwerkes, steht ein Linux-Rechner, den man passend konfiguriert:

# ip addr add 198.51.100.254/29 dev eth_rot
# ip addr show eth_rot
1: eth_rot: <BROADCAST,MULTICAST,UP,10000>
    inet 198.51.100.254/29 scope global eth_rot
       valid_lft forever preferred_lft forever 

Zusätzlich bekommt er die fremde IP Adresse. Damit er diese auch als Quelladresse benutzt, gibt es eine spezielle extra Route:

# ip addr add 203.0.113.133/32 dev lo
# ip route add 198.51.100.249/32 src 203.0.113.133 dev eth_rot

Wenn man testweise versucht eine Verbindung zur Appliance herzustellen, scheint es erst einmal zu tun.

# ssh 198.51.100.249
15:42:02  IP 203.0.113.133.35158 > 198.51.100.249.22: S 3034099253:3034099253(0)
15:42:03  IP 203.0.113.133.35158 > 198.51.100.249.22: S 3034099253:3034099253(0)
15:42:05  IP 203.0.113.133.35158 > 198.51.100.249.22: S 3034099253:3034099253(0)

Wie man sieht, geht die Anfrage mit der richtigen Absende-IP raus.

Allerdings kommt keine Antwort zurück. Aber warum?

... und zurück

Ein Blick auf die ASA zeigt, das die Datenpakete korrekt nattet und sogar Antworten bekommt.

 16: 15:42:02   192.0.2.5.35158 > 192.0.2.4.22: S 3034099253:3034099253(0) win 14600
 17: 15:42:02   192.0.2.4.22 > 192.0.2.5.35158: S 2009198346:2009198346(0) ack 3034099253 win 14480
 18: 15:42:03   192.0.2.5.35158 > 192.0.2.4.22: S 3034099253:3034099253(0) win 14600
 19: 15:42:03   192.0.2.4.22 > 192.0.2.5.35158: S 2009198346:2009198346(0) ack 3034099253 win 14480
 20: 15:42:05   192.0.2.5.35158 > 192.0.2.4.22: S 3034099253:3034099253(0) win 14600
 21: 15:42:05   192.0.2.4.22 > 192.0.2.5.35158: S 2009198346:2009198346(0) ack 3034099253 win 14480

Warum kommen die Datenpakete nicht wieder zurück? Weil die Default Route fehlt!

Glücklicherweise ist die ASA kein Router. Genauer gesagt die Routingentscheidung wird in zwei Schritten vorgenommen:

  • Steht das ausgehende Interface nicht fest, so wird die generische Routingtabelle befragt, um das Interface zu ermitteln.
  • Ist allerdings klar, auf welchem Bein ausgesendet werden muss, so wird nur der Teil der Routingtabelle betrachtet, der diesem Interface zugeordnet ist.

Eine der Konsequenzen aus diesem Ansatz erklärt die seltsame Anforderung, bei jeder statische konfigurierten Route immer das Interface mit angeben zu müssen: Um so eine schräge Logik umsetzen zu können, muss man erst mal die Angaben dafür haben.

In diesem Fall genügt es also eine weitere Default Route einzutragen, die eine höhere Metrik hat. Dies stellt sicher, dass die ASA keine zwei gleichberechtigten Routen erhält, mit denen sie nicht umgehen kann. Die Situation für die ASA bleibt also eindeutig.

(conf)# route red 0.0.0.0 0.0.0.0 198.51.100.254 2

An der Standard-Routingtabelle ändert sich damit ja nichts:

C    198.51.100.248 255.255.255.248 is directly connected, red
C    198.51.100.0 255.255.255.192 is directly connected, green
C    192.0.2.0 255.255.255.0 is directly connected, gray
S*   0.0.0.0 0.0.0.0 [1/0] via 198.51.100.1, green

Ganz anders ist nun aber die Situation für die Pakete dar, die aus dem (reverse)-NAT heraus fallen.

  • Der NAT Eintrag enthält die Quell- und Ziel-Interfaces, sowie die IP-Adressen und Ports.
  • Pakete bestehender Flows werden also nicht normal behandelt, sondern anhand des vorhandenen NAT-Eintrags.
  • Damit umgehen sie sämtliches konfiguriertes Routing, ACLs etc. insbesondere die Standard-Routingtabelle.
  • Da das Zielinterface fest steht, greift nur noch die Routingtabelle dieses Interfaces, also die niedrig priorisierte Default Route.

Und siehe an:

# ssh  198.51.100.249 
16:38:20  IP 203.0.113.133.51310 > 198.51.100.249.22: S 3979383937:3979383937(0)
16:38:20  IP 198.51.100.249.22 > 203.0.113.133.51310: S 3186400021:3186400021(0) ack 3979383938
16:38:20  IP 203.0.113.133.51310 > 198.51.100.249.22: . ack 1 win 115
16:38:20  IP 198.51.100.249.22 > 203.0.113.133.51310: P 1:22(21) ack 1 win 114
16:38:20  IP 203.0.113.133.51310 > 198.51.100.249.22: . ack 22 win 115
16:38:20  IP 203.0.113.133.51310 > 198.51.100.249.22: P 1:21(20) ack 22 win 115
16:38:20  IP 198.51.100.249.22 > 203.0.113.133.51310: . ack 21 win 114
 136: 16:38:20    192.0.2.5.51310 > 192.0.2.4.22: S 1381971035:1381971035(0) win 14600
 137: 16:38:20    arp who-has 192.0.2.5 tell 192.0.2.4 
 138: 16:38:20    arp reply 192.0.2.5 is-at e8:b7:48:fd:89:81 
 139: 16:38:20    192.0.2.4.22 > 192.0.2.5.51310: S 2009198346:2009198346(0) ack 1381971036 win 14480
 140: 16:38:20    192.0.2.5.51310 > 192.0.2.4.22: . ack 2009198347 win 115
 141: 16:38:20    192.0.2.4.22 > 192.0.2.5.51310: P 2009198347:2009198368(21) ack 1381971036 win 114
 142: 16:38:20    192.0.2.5.51310 > 192.0.2.4.22: . ack 2009198368 win 115
 143: 16:38:20    192.0.2.5.51310 > 192.0.2.4.22: P 1381971036:1381971056(20) ack 2009198368 win 115
 144: 16:38:20    192.0.2.4.22 > 192.0.2.5.51310: . ack 1381971056 win 114
 145: 16:38:20    192.0.2.5.51310 > 192.0.2.4.22: P 1381971056:1381971848(792) ack
 146: 16:38:20    192.0.2.4.22 > 192.0.2.5.51310: . ack 1381971848 win 126

Hurra!

Proxy-ARP Daemon

27.10.2017 15:11 Lutz Donnerhacke Tags: Internet , BGP , ARP 6

Kaputtes xDSL ist ein nervendes Problem hier. Wir haben die Schmerzen gemildert, indem wir die Netzmasken verkleinerten. Während der letzten Monate verschärfte sish die Situation wieder, weil rigide Filter auf den DSLAM ausgerollt wurden und Geräte hinzu kamen, die nicht in der Lage waren mit den kleinen Netzmasken umzugehen. Wir hatten das Problem grundsätzlich zu lösen.

Problem

Große Netzwerke haben eine große Anzahl an sichtbaren MAC Adressen. Einige Geräte (z.B. DSLAMs) verhalten sich seltsam, wenn sie zuviele MACs zu sehen bekommen. Deshalb versuchen die Netzwerkbetreiber ihre Netze zu segmentieren, so dass keine Frames mehr von einer entfernten, kleinen Lokation zu eienr anderen gelangen können. Die Blockade von Quertraffic zuwischen Kunden heißt split-horizon. Kunden in verschiedenen Teilen des Netzwerkes können dabei nicht mehr miteinander kommunizieren.

local proxy arp1

Große Netzwerke sind schwer zu betreiben. Deswegen wird möglichst nahe am Endkunden, also am DSLAM gefiltert. Diese "Fist-Hop-Security" Filter sind nur für einfache Anwendungsfälle gedacht und neigen dazu Pakete in nicht-standard-Situationen zu verwerfen. Man kann dann diese Filter nur abschalten oder damit leben.

Meistens lesen diese Geräte die DHCP-Kommunikation mit und passen danach ihre Filter an. Nutzer mit statischen IP Adressen machen aber oft gar kein DHCP, so dass diese Filter nicht zu lernen haben. Andere Nutzer haben mehr als ein Endgerät oder ganze Netzbereiche zugeteilt bekommen. In all dieses Fällen versagen die DLSAM-Filter.

Eine weitere Sicherheitsmaßnahme besteht darin, den Broadcast-Verkehr zu unterbinden, so er nicht dazu dient, die MAC Adresse zur dem Filter bekannten IP zu ermitteln. Die Idee hinter diesem Filter ist, dass jeder, der die MAC Adresse des Endgeräts kennt, dann automatisch berechtigt ist, mit diesem zu kommunizieren. Es genügt also, die Broadcasts von Endkunden zu Endkunden zu blockieren, um effektiv nur noch erlaubte Kommunikation zu haben.

Setzt man DHCP-Sniffing, Broadcast-Filter und Split-Horizon zusammen ein, erhält man ein typisches xDSL-Netzwerk, in dem die Nutzer nur mit dem zentralen Router reden können. Sämtliche darüber hinaus gehende Kommunikation ist nicht möglich.

Einfache Lösung

Gibt es nur einen einzelnen Router im Netz, so genügt es dort local-proxy-arp anzuschalten: Jede ARP-Anfrage (eines Endgeräts) wird vom Router mit der MAC-Adresse des Router-Interfaces beantwortet. Auf diese Weise können die Kunden im Umweg über den Router miteinander reden (hair pinning).

local proxy arp3

Da der Router die MAC-Adresse des Endgeräts schon bei der ARP-Anfrage dieses Geräts nach seinem Default-Router lernt, muss der Router oft gar nicht selbst ARP-Anfragen per Broadcast stellen. Einige Routermodelle erneuern die auslaufenden Cache Einträge per Unicast-Anfrage, die von den DSLAM-Filtern durchgelassen werden. Auf diese Weise kann man ein solches Netz ohne merkbare Störungen betreiben.

Gibt es allerdings mehrere Router oder Server an zentraler Stelle,. so wird es kompliziert. Local-proxy-arp kann nun nicht mehr eingesetzt werden, da die Router sonst sich gegenseitig die ARP-Anfragen beantworteten und anschließend die Pakete im Kreis laufen.

Andererseits bekommen DHCP-Server Probleme, weil sie auf den ARP-Test "Ist die IP noch frei?" Fake-Antworten vom Router bekommen. Local-proxy-arp stört alle Arten dieser Anwendungen erheblich.

Anderer Ansatz

Um unser Netz hier trotzdem am Laufen zu halten, stellt eine neue Software (parpd) die notwendigen ARP-Antworten. In Abhängigkeit von frei konfigurierbaren Regeln antwortet der Daemon auf ARP-Anfragen mit einer passenden Antwort. Dies kann die reale MAC des Endgeräts sein oder auch die MAC-Adresse eines Routers (redirect).

Die Software lernt durch passives Zuhören die realen MAC-IP Paare. Selbstverständlich ignoriert sie dabei Antworten von anderen Instanzen, beschränkt sich also auf die originalen Quellen. parpd erneuert auslaufende ARP Cache-Einträge mit Unicast-Anfragen. Nur um die Redirect-MAC zu ermitteln, darf sie auf Broadcast zurück greifen.

Antworten können aber auch verzögert werden, d.h. die ersten Anfragen eines Gerätes nach einer bestimmten IP werden innerhalb einer gewissen Zeit ignoriert. Auf diese Weise kann ein DHCP-Server seine Überprüfungen vornehmen, bekommt aber dann doch eine Antwort, wenn er auf direkter Kommunikation besteht.

Die frei konfigurierbaren Regeln gestatten die Anpassung an komplexere Aufbauten, z.B. überlappenden Netzen.

Beispiel

Wie sieht das in der Praxis aus? So:

cache
 timeout       302     # seconds
 tablesize     3499    # expecting about 10000 entries
 refresh       3*5     # 3 retries a 5 seconds each
 delay         4*3     # respond at 4th retry in 3 seconds
end

interface em0
 timeout       1.011
 # do not respond for queries to our own infrastructure
 rule          0.0.0.0/0        198.51.100.0/29    ignore
 # delay queries from the DHCP server
 rule          198.51.100.4/32  198.51.100.0/24    delay tell
 # help the routers/servers to reach the clients
 rule          198.51.100.0/29  198.51.100.0/24    tell
 # interclient communication through hairpinning at the default gateway
 rule          198.51.100.0/24  198.51.100.0/24    198.51.100.1
 # help erroneous clients arping for everything
 rule          198.51.100.0/24  0.0.0.0/0          verbose 198.51.100.1
 # multihomed server with weak host model
 rule          192.0.2.0/24     198.51.100.0/24    tell
 # show missing entries
 rule          0.0.0.0/0        0.0.0.0/0          verbose ignore
end

ASA und der Heisenbug mit der Default Route

25.08.2017 14:23 Lutz Donnerhacke Tags: IPv6 , Cisco , WTF 5

Ein Kunde hat nach einem Umbau am Außenanschluss massive Probleme mit der Stabilität der IPv6 Versorgung. Nach nächtlicher Inaktivität ist am folgenden Morgen keine IPv6 Kommunikation möglich. Beginnt man auf der ASA zu debuggen, verschwindet das Problem sofort.

Heisenbergsches Nichts

Zunächst stellt sich die Frage, was denn so spannendes in der Nacht passiert sein kann. Eigentlich nichts.

Genau das ist das Problem. Es ist nichts passiert. Genau deswegen geht es nicht mehr. Zur Überprüfung lösche ich den IPv6 Nachbarschafts-Cache und das Problem tritt sofort wieder auf. Der IPv6 Nachbarschaftseintrag für das default Gateway will und will nicht wieder kommen.

Pingt man von der ASA aus ein externes Ziel an, verschwindet das Problem instantan. Auch der IPv6 Nachbarschaftseintrag ist wieder vorhanden.

Das Problem kann also kontrolliert ein- und ausgeschaltet werden. Damit ist der Heisenbug in einen Bohrbug verwandelt und die Suche kann beginnen.

Gestörte Nachbarschaft

Zuerst aktiviere ich das Debugging für IPv6 Nachbarschaftskram.

asa# debug ipv6 nd
asa# debug ipv6 icmp
asa# clear ipv6 neighbors
ICMPv6-ND: DELETE -> INCMP: 2001:db8:700:1d::1
ICMPv6-ND: Sending NS for 2001:db8:700:1d::1 on outside
ICMPv6-ND: Sending NS for 2001:db8:700:1d::1 on outside
ICMPv6-ND: Sending NS for 2001:db8:700:1d::1 on outside
ICMPv6-ND: INCMP deleted: 2001:db8:700:1d::1
ICMPv6-ND: INCMP -> DELETE: 2001:db8:700:1d::1
ICMPv6-ND: DELETE -> INCMP: 2001:db8:700:1d::1
ICMPv6-ND: Sending NS for 2001:db8:700:1d::1 on outside
ICMPv6-ND: Sending NS for 2001:db8:700:1d::1 on outside
ICMPv6-ND: Sending NS for 2001:db8:700:1d::1 on outside
ICMPv6-ND: INCMP deleted: 2001:db8:700:1d::1
ICMPv6-ND: INCMP -> DELETE: 2001:db8:700:1d::1
ICMPv6-ND: DELETE -> INCMP: 2001:db8:700:1d::1

Das Spiel wiederholt sich endlos.

Die ASA benötigt den Nachbarschaftseintrag des Default Gatways, der gerade fehlt (DELETE), und versucht dann per Neighbour Solication (NS) wieder die MAC zur IP zu ermitteln (INCoMPlete).

Allerdings bekommt sie keine Antwort zurück.

Also jetzt mal von der ASA aus pingen:

asa# ping 2001:db8::1
ICMPv6: Sending echo request to 2001:db8::1
ICMPv6-ND: INCMP deleted: 2001:db8:700:1d::1
ICMPv6-ND: INCMP -> DELETE: 2001:db8:700:1d::1
ICMPv6-ND: DELETE -> INCMP: 2001:db8:700:1d::1
ICMPv6-ND: Sending NS for 2001:db8:700:1d::1 on outside
ICMPv6-ND: Sending NS for 2001:db8:700:1d::1 on outside
?

Es fehlt nach wie vor die Antwort vom gegenüber liegenden Router. Deswegen schreibt der Ping sein erstes "?" hin.

ICMPv6: Sending echo request to 2001:db8::1
ICMPv6-ND: Sending NS for 2001:db8:700:1d::1 on outside
ICMPv6: Received ICMPv6 packet from 2001:db8:700:1d::1, type 136

Und auf einmal ist die Antwort da!

Sofort macht sich die ASA über die Rückmeldung her:

ICMPv6-ND: Received NA for 2001:db8:700:1d::1 on outside from 2001:db8:700:1d::1
ICMPv6-ND: INCMP -> REACH: 2001:db8:700:1d::1
ICMPv6: Received ICMPv6 packet from 2001:db8::1, type 129
ICMPv6: Received echo reply from 2001:4bd8::1
!

Das Ping Echo ist zurück, es schreibt ein "!" auf die Zeile und alles geht wieder.

Wenn man jetzt mal genau hinschaut, erkennt man deutlich den Unterschied zwischen den unbeantworteten Anfragen und der erfolgreichen Anfrage.

Ganz genau hinschauen! Sieht man's?

Der kleine Unterschied

Genau, da ist kein Unterschied.

Aber es muss etwas geben, was den Effekt auslöst. Also muss man noch genauer hinschauen.

asa(conf)# access-list debug-38594 extended permit icmp6 any6 any6 neighbor-advertisement 
asa(conf)# access-list debug-38594 extended permit icmp6 any6 any6 neighbor-redirect 
asa(conf)# access-list debug-38594 extended permit icmp6 any6 any6 neighbor-solicitation 
asa(conf)# access-list debug-38594 extended permit icmp6 any6 any6 router-advertisement 
asa(conf)# access-list debug-38594 extended permit icmp6 any6 any6 router-renumbering 
asa(conf)# access-list debug-38594 extended permit icmp6 any6 any6 router-solicitation
asa# capture d-38594 access-list debug-38594 packet-length 1500 interface outside circular-buffer

Es werden alle Nachbarschafts- und Router-Nachrichten mitgeschnitten. In voller Länge, damit man auch was sehen kann.

Und nun schauen wir mal nach den fehlenden Details:

asa# show capture d-38594 detail
17: 07:36:17.423180 6412.25e3.e7fd 3333.ff00.0001 0x86dd Length: 86
 fe80::6400:0 > ff02::1:ff00:1: icmp6: neighbor sol:
 who has 2001:db8:700:1d::1(src lladdr: 64:12:25:e3:e7:fd) [class 0xe0] (len 32, hlim255)

18: 07:36:17.433342 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 2001:db8:700:1d::1 > fe80::6400:0: icmp6: neighbor adv:
 tgt is 2001:db8:700:1d::1(RSO)(tgt lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len 32,hlim 255)

19: 07:36:18.415887 6412.25e3.e7fd 3333.ff00.0001 0x86dd Length: 86
 fe80::6400:0 > ff02::1:ff00:1: icmp6: neighbor sol:
 who has 2001:db8:700:1d::1(src lladdr: 64:12:25:e3:e7:fd) [class 0xe0] (len 32, hlim255)

20: 07:36:18.423211 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 2001:db8:700:1d::1 > fe80::6400:0: icmp6: neighbor adv:
 tgt is 2001:db8:700:1d::1(RSO)(tgt lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len 32,hlim 255)

21: 07:36:19.415887 6412.25e3.e7fd 3333.ff00.0001 0x86dd Length: 86
 fe80::6400:0 > ff02::1:ff00:1: icmp6: neighbor sol:
 who has 2001:db8:700:1d::1(src lladdr: 64:12:25:e3:e7:fd) [class 0xe0] (len 32, hlim255)

22: 07:36:19.421441 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 2001:db8:700:1d::1 > fe80::6400:0: icmp6: neighbor adv:
 tgt is 2001:db8:700:1d::1(RSO)(tgt lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len 32,hlim 255)

23: 07:36:20.422921 6412.25e3.e7fd 3333.ff00.0001 0x86dd Length: 86
 fe80::6400:0 > ff02::1:ff00:1: icmp6: neighbor sol:
 who has 2001:db8:700:1d::1(src lladdr: 64:12:25:e3:e7:fd) [class 0xe0] (len 32, hlim255)

24: 07:36:20.428124 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 2001:db8:700:1d::1 > fe80::6400:0: icmp6: neighbor adv:
 tgt is 2001:db8:700:1d::1(RSO)(tgt lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len 32,hlim 255)

25: 07:36:21.415856 6412.25e3.e7fd 3333.ff00.0001 0x86dd Length: 86
 fe80::6400:0 > ff02::1:ff00:1: icmp6: neighbor sol:
 who has 2001:db8:700:1d::1(src lladdr: 64:12:25:e3:e7:fd) [class 0xe0] (len 32, hlim255)

26: 07:36:21.419350 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 2001:db8:700:1d::1 > fe80::6400:0: icmp6: neighbor adv:
 tgt is 2001:db8:700:1d::1(RSO)(tgt lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len 32,hlim 255) 

Die ASA (fe80::6400:0) fragt per Link-Local Multicast (ff02::1:ff00:1) nach der MAC zur IP 2001:db8:700:1d::1. Soweit war das im Debug schon zu sehen.

Überraschenderweise antwortet die Zielmaschine (2001:db8:700:1d::1) der ASA (fe80::6400:0) mit den gewünschten Daten.

Das bedeutet, dass trotz Umbaus am Uplink keine Störung dort vorliegt: Die Antworten kommen an!

Und es bedeutet weiter, dass es allein ein Problem der ASA ist, was hier abgeht.

Als nächstes wird ein Ping von der ASA aus probiert:

27: 07:36:22.396021 6412.25e3.e7fd 203a.0762.b442 0x86dd Length: 86
 fe80::6400:0 > fe80::223a:7ff:fe62:b442: icmp6: neighbor sol:
 who has fe80::223a:7ff:fe62:b442(src lladdr: 64:12:25:e3:e7:fd) [class 0xe0] (len 32,hlim 255)

28: 07:36:22.399408 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 78
 fe80::223a:7ff:fe62:b442 > fe80::6400:0: icmp6: neighbor adv:
 tgt is fe80::223a:7ff:fe62:b442(RS) [class 0xe0] (len 24, hlim 255)

29: 07:36:22.416009 6412.25e3.e7fd 3333.ff00.0001 0x86dd Length: 86
 2001:db8:700:1d::2 > ff02::1:ff00:1: icmp6: neighbor sol:
 who has 2001:db8:700:1d::1(src lladdr: 64:12:25:e3:e7:fd) [class 0xe0] (len 32, hlim255)

30: 07:36:22.425988 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 2001:db8:700:1d::1 > 2001:1438:700:1d::2: icmp6: neighbor adv:
 tgt is 2001:db8:700:1d::1(RSO)(tgt lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len 32,hlim 255)

Zuerst kommt ein Abgleich der Nachbarschaftsbeziehung zwischen den Link-Local-Adressen selbst. Dieser funktioniert.

Und spontan ist die folgende Antwort für die ASA akzeptabel.

Das Problem besteht darin, dass die ASA für durchgereichten Datenverkehr die ND nach dem Default-GW mit der Quell-IP fe80::xxx macht, und dann die Antwort ignoriert. Wenn Sie dagegen selbst pingt, ist die Quell-IP des ND allerdings 2001:... und dann akzeptiert sie auch die Antwort.

Und wie sieht man das in dem Ausgabewust (der hier gekürzt ist)? Mit sort | uniq -c.

Auf der Zielgeraden

Ich hab' mal mit der link-lokal Adresse als Default-Route gespielt, weil ja offenbar kein Problem besteht, die MAC einer Link-Local Adresse zu ermitteln.

Es tritt das gleiche Problem auf:

5081: 11:22:27.164588 6412.25e3.e7fd 3333.ff62.b442 0x86dd Length: 86
 fe80::6400:0 > ff02::1:ff62:b442: icmp6: neighbor sol:
 who has fe80::223a:7ff:fe62:b442(src lladdr: 64:12:25:e3:e7:fd) [class 0xe0] (len 32,hlim 255)

5082: 11:22:27.167609 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 fe80::223a:7ff:fe62:b442 > fe80::6400:0: icmp6: neighbor adv:
 tgt is fe80::223a:7ff:fe62:b442(RSO)(tgt lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len32, hlim 255)

5083: 11:22:28.164557 6412.25e3.e7fd 3333.ff62.b442 0x86dd Length: 86
 fe80::6400:0 > ff02::1:ff62:b442: icmp6: neighbor sol:
 who has fe80::223a:7ff:fe62:b442(src lladdr: 64:12:25:e3:e7:fd) [class 0xe0] (len 32,hlim 255)

5084: 11:22:28.165869 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 fe80::223a:7ff:fe62:b442 > fe80::6400:0: icmp6: neighbor adv:
 tgt is fe80::223a:7ff:fe62:b442(RSO)(tgt lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len32, hlim 255)

5085: 11:22:29.173925 6412.25e3.e7fd 3333.ff62.b442 0x86dd Length: 86
 fe80::6400:0 > ff02::1:ff62:b442: icmp6: neighbor sol:
 who has fe80::223a:7ff:fe62:b442(src lladdr: 64:12:25:e3:e7:fd) [class 0xe0] (len 32,hlim 255)

5086: 11:22:29.180914 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 fe80::223a:7ff:fe62:b442 > fe80::6400:0: icmp6: neighbor adv:
 tgt is fe80::223a:7ff:fe62:b442(RSO)(tgt lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len32, hlim 255)

Wie bisher: Anfrage von Link-Local wird beantwortet, aber seitens der ASA nicht verarbeitet.

5087: 11:22:30.164664 6412.25e3.e7fd 3333.ff62.b442 0x86dd Length: 86
 2001:db8:700:1d::2 > ff02::1:ff62:b442: icmp6: neighbor sol:
 who has fe80::223a:7ff:fe62:b442(src lladdr: 64:12:25:e3:e7:fd) [class 0xe0] (len 32,hlim 255)

5088: 11:22:30.172262 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 fe80::223a:7ff:fe62:b442 > 2001:db8:700:1d::2: icmp6: neighbor adv:
 tgt is fe80::223a:7ff:fe62:b442(RSO)(tgt lladdr: 20:3a:07:62:b4:42) [class 0xe0](len 32, hlim 255)

Wenn die ASA aber mit ihrer offiziellen Adresse anfragt, gibt es kein Problem.

Und noch etwas fällt auf:

5089: 11:22:31.194234 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 fe80::223a:7ff:fe62:b442 > fe80::6400:0: icmp6: neighbor sol:
 who has fe80::6400:0(src lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len 32, hlim 255)

5090: 11:22:32.200871 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 fe80::223a:7ff:fe62:b442 > fe80::6400:0: icmp6: neighbor sol:
 who has fe80::6400:0(src lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len 32, hlim 255)

5091: 11:22:33.207508 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 fe80::223a:7ff:fe62:b442 > fe80::6400:0: icmp6: neighbor sol:
 who has fe80::6400:0(src lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len 32, hlim 255)

Umgekehrt mag sie auch nicht auf Anfragen nach ihrer eigenen Link-Local Adresse antworten. Das gilt inbesondere auch dann, wenn das Paket schon an sie selbst adressiert ist.

5092: 11:22:35.178915 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 fe80::223a:7ff:fe62:b442 > 2001:db8:700:1d::2: icmp6: neighbor sol:
 who has 2001:db8:700:1d::2(src lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len 32,hlim 255)

5093: 11:22:35.179464 6412.25e3.e7fd 203a.0762.b442 0x86dd Length: 78
 2001:db8:700:1d::2 > fe80::223a:7ff:fe62:b442: icmp6: neighbor adv:
 tgt is 2001:db8:700:1d::2(RS) [class 0xe0] (len 24, hlim 255)

Wohl aber antwortet sie auf die Anfrage an ihre offizielle IP nach ihrer offiziellen IP.

Es scheint so, als ob die ASA die eigene Link-Local Adresse nicht mag.

Der Fehler scheint darin zu bestehen, dass die Link-Local Adresse auf der ASA nicht geändert werden darf. Warum auch immer.

Normalerweise habe ich händisch vergebene Link-Local Adressen nach dem Schema fe80::Gerät:Interface, was die Routingtabellen schön lesbar macht.

Also habe ich die Link-Local Adresse auf "system-default" gestellt und es scheint zu gehen. Selbst nach einem Löschen der Nachbarschafts-Caches erholt sich das System binnen Sekundenfrist von allein.

5123: 14:20:32.560914 6412.25e3.e7fd 3333.ff00.0001 0x86dd Length: 86
 fe80::6612:25ff:fee3:e7fd > ff02::1:ff00:1: icmp6: neighbor sol:
 who has 2001:db8:700:1d::1(src lladdr: 64:12:25:e3:e7:fd) [class 0xe0] (len 32, hlim255)

5124: 14:20:32.569077 203a.0762.b442 6412.25e3.e7fd 0x86dd Length: 86
 2001:db8:700:1d::1 > fe80::6612:25ff:fee3:e7fd: icmp6: neighbor adv:
 tgt is 2001:db8:700:1d::1(RSO)(tgt lladdr: 20:3a:07:62:b4:42) [class 0xe0] (len 32,hlim 255) 

Ein schöner Bug, Cisco.

Eine Klarstellung zu CAA

23.08.2017 17:53 Lutz Donnerhacke Tags: DNSSEC , Zertifikat , Sicherheit , IETF 0

Die Firma Qualys bietet einen populären SSLTest an. Leider scheinen sie in letzter Zeit einen Fehlgriff getan zu haben, indem sie die CAA Tests einführten. Das Problem ist, dass CAA etwas völlig anderes tut, als die Betreiber annehmen.

Warum CAA?

CAA wurde im RFC 6844 standardisiert und vom CA Forum ab 8. September als verpflichtend vereinbart.

Es geht um folgendes (aus dem RFC)

The Certification Authority Authorization (CAA) DNS Resource Record
allows a DNS domain name holder to specify the Certification
Authorities (CAs) authorized to issue certificates for that domain.
Publication of CAA Resource Records allows a public Certification
Authority to implement additional controls to reduce the risk of
unintended certificate mis-issue.

Der Webseitenbetreiber kann also im DNS seiner Webseitenzone angeben, welche CA für den Server Zertifikate ausstellen darf.

Der RFC stellt nochmal explizit klar:

Like the TLSA record defined in DNS-Based Authentication of Named
Entities (DANE) [RFC6698], CAA records are used as a part of a
mechanism for checking PKIX certificate data.  The distinction
between the two specifications is that CAA records specify an
authorization control to be performed by a certificate issuer before
issue of a certificate and TLSA records specify a verification
control to be performed by a relying party after the certificate is
issued.

Die Zielgruppe des CAA Records ist also die CA, die bei der Zertifikatserstellung prüfen kann, ob der Betreiber sich auch an diese CA wenden wollte. Der Gedanke dabei ist, dass eine CA erkennen kann, dass sie von einem Angreifer für eine fehlerhafte Zertifikatserstellung missbraucht wird.

Die Zielgruppe von TLSA Records dagegen sind die Browser, die bei der Zertifikatsvalidierung prüfen können, ob das beim Verbindungsaufbau präsentierte Zertifikat vom echten Webseitenbetreiber stammt. Der Gedanke dabei ist, dass ein Browser erkennen kann, ob ihm ein falsches Zertifikat unter geschoben werden soll.

Wie deutlich der CAA Record auf den Betrieb einer CA ausgerichtet ist, sieht man daran, dass die Autoren von Comodo (einer CA) stammen und als Beispiel angeben, welche Nutzerkennung im Commodo-Portal für die Zertifikatserstellung benutzt werden darf (aus dem RFC):

example.com.  CAA 0 issue "ca.example.net; account=230123"

Böse formuliert kann man den CAA Record als Abwehrmechanismus von Comodo verstehen, wenn wieder mal fälschlich Zertifikate ausgestellt wurden. Die CA könnte dann darauf verweisen, dass der Webseitenbetreiber ja angeben hätte können, dass diese CA gar nicht für ihn arbeiten darf.

Woher bekommt man nun so einen CAA Eintrag, wenn man einen braucht?

  • Auf der Webseite von SSLmate kann man sich die Records erzeugen.
  • Den notwendigen Issuer-String kann man bei einer inoffiziellen Registry nachschlagen.

Interessant ist dabei, welche Issuer-Strings die CAs so anerkennen. Symantec ist es beispielsweise egal, welcher Reseller zum Zuge kommt. Es ist also nicht möglich, "thawte.com" anzugeben und so GeoTrust auszuschließen.

Ebenso interessant ist, dass der RFC vermeidet, der IANA die Pflege der Issuer-Strings aufzuerlegen. Dabei wäre das echt nützlich gewesen. Die referenzierte Registry hat lustigerweise ein "fork me on Gitlab" Banner in der Ecke.

Was SSLabs tut

Zunächst mal hat Qualsys auf die Ankündigung des CA-Forums reagiert, und prüft auf die Existenz des Eintrags. Ist der Eintrag vorhanden, gibt es Zusatzpunkte für einen A+ Status. Das hebt die Motivation natürlich.

Also schauen wir mal. Eintrag vornehmen und testen:

2017-08-23-184740_578x445_scrot

Ist das nicht cool? Das Zertifikat stammt von Symantec, und der Test befindet den CAA Eintrag für gut.

Aber kann er überhaupt einen Eintrag testen, der gar nicht für die Verwendung in einem Browser vorgesehen ist? Schließlich hat der Test ja keinen Zugriff auf die Validierungslogik der CAs.

Also testen wir auch das.

2017-08-23-185026_573x442_scrot

Wie bitte? Ein ganz offensichtlicher Widerspruch wird mit Zusatzpunkten bewertet?

Wenn man versteht, dass CAA ausschließlich für die  CAs da ist, ist das keine Überraschung. Vielleicht soll ja auch das nächste Zertifikat von einer anderen CA ausgestellt werden? Der Betreiber der Webseite benutzt halt nur noch das noch gültige alte Zertifikat weiter. Gründe kann es vieles geben.

Im Kern fällt es auf die Aussage zurück, dass der Zeitpunkt der Auswertung des CAA Records bei der Zertifkatserstellung wichtig ist. Dieser Zeitpunkt unterscheidet sich erheblich von dem der Zertifikatsvalidierung!

Noch viel witziger sind die Ausschlusskriterien für die Verwendung von CAA:

  • Wenn der CAA Test schon zweimal in der Vergangenheit erfolgreich war und das im Certifcate Transparency Log steht.
    Ich darf also die CA nicht wechseln?
  • Wenn die ausstellende Sub-CA dies in ihren Verträgen ausschließt.
    Diese Sub-CAs waren bisher schon immer eine Quelle von Fehlern.
  • Wenn die CA auch Betreiber des DNS ist.
    Die Großen schauen also weg.
  • Wenn die DNS Abfrage fehlschlägt aus Gründen, die nicht die CA zu verantworten hat, die Anfrage aber wenigsten einmal versucht wurde und die Zone nicht komplett bis zur Wurzel DNSSEC validierbar ist.
    Also immer.

Ehrlich gesagt ist das ein Frechheit.

Und nun?

Wer auch immer einen Draht zu Qualys SSLabs hat, er möge den Leuten dort auf die Füße treten:

  • Entfernt die CAA Prüfung!
  • Prüft TLSA!

Der Hauptschlüssel des Internets wird gewechselt

17.08.2017 15:28 Lutz Donnerhacke Tags: DNSSEC , Internet , ICANN 0

So, Leute: Es wird ernst. Die Root-Zone ist schon eine ganze Weile signiert und man soll auch dort mal die Schlüssel wechseln. Das ist nicht einfach, weil alle Betreiber rekursiver Resolver daran mitwirken müssen.

Es gibt drei Typen von Betroffenen:

  • Der Resolver macht kein DNSSEC, dann vergesst es.
  • Der Resolver validiert DNSSEC und es interessiert Euch nicht, dann plant einen Paniktag ein.
  • Der Resolver validiert DNSSEC und ihr wollt Panik vermeiden, dann lest und testet.

Worum geht's?

Fragt man die Root-Nameserver nach ihrem Schlüsselmaterial so gibt es folgende Antwort:

;; QUESTION SECTION:
;.                      IN DNSKEY

;; ANSWER SECTION:
.                       DNSKEY  257 3 8 (
                                AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTO
                                ...
                                9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU=
                                ) ; key id = 20326
.                       DNSKEY  257 3 8 (
                                AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQ
                                ...
                                LmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0=
                                ) ; key id = 19036
.                       DNSKEY  256 3 8 (
                                AwEAAYvxrQOOujKdZz+37P+oL4l7e35/0diH/mZITGjl
                                ...
                                jpi+V7pgh0o1KYXZgDUbiA1s9oLAL1KLSdmoIYM=
                                ) ; key id = 15768
.                       RRSIG   DNSKEY 8 0 172800 20170831000000 (
                                20170810000000 19036 .
                                D2D0oJblRe/C86Eti+vOLIHll7hvI9mN/O6S9gZqKvN2
                                ...
                                fkMFPGKWv4Gi1BlE+r+a9FDFz4ypjwdbvA== )

Die Root-Zone hat also aktuell drei Schlüssel:

  • Einen Arbeitsschlüssel (key id = 15768) und
  • zwei  Hauptschlüssel von denen Ihr bisher den mit der ID 19036 auf der Platte habt.
  • Der Schlüssel mit der ID 20326 ist neu.

Zum 11. Oktober 2017 wird die Root-Zone mit dem neuen Schlüssel unterschrieben. Dann müsst Ihr den neuen Key auf der Platte haben, sonst fällt bei Euch DNS komplett aus.

Ich habe das selbst schon mal hinbekommen, als am 26.12. mir die Signaturen der Nameserver-Records meiner Test-Root ausliefen. Ein Remote-Zugriff klappte nicht mehr, weil die SSH die IPs nicht nachschlagen konnte, die einzelnen Geräte in Panik die Dienste neu starteten und diese schon beim Start keine Gegenstelle mehr erreichen konnten (IPs nachschlagen ging nicht mehr) und sich instantan wieder beendeten.

Manuell konnte ich an der Konsole des rekursiven Nameserver den Fehler beheben. Nach einer frostigen Fahrradtour in die Firma. Ich habe geschwitzt!

Was ist zu tun?

Eure Software sollte also erkennen, dass es einen neuen Schlüssel gibt und den auf die Platte legen.

Nehmen wir an, es sei unbound. Dann findet ihr eine Datei /etc/unbound/root.key (o.ä.)

; autotrust trust anchor file
;;id: . 1
;;last_queried: 1502962961 ;;Thu Aug 17 11:42:41 2017
;;last_success: 1502962961 ;;Thu Aug 17 11:42:41 2017
;;next_probe_time: 1503005390 ;;Thu Aug 17 23:29:50 2017
;;query_failed: 0
;;query_interval: 43200
;;retry_time: 8640
.       172800  IN      DNSKEY  257 3 8 AwEAAaz/t...V74bU= ;{id = 20326 (ksk), size = 2048b}
   ;;state=2 [  VALID  ] ;;count=0 ;;lastchange=1502399011 ;;Thu Aug 10 23:03:31 2017
.       3696    IN      DNSKEY  257 3 8 AwEAAagA...ihz0= ;{id = 19036 (ksk), size = 2048b}
   ;;state=2 [  VALID  ] ;;count=0 ;;lastchange=1291045951 ;;Mon Nov 29 16:52:31 2010

Diese Datei enthält bereits beide Schlüssel und alles ist gut(TM).

Hat man dagegen unbound irgendwann mal aufgesetzt und nie richtig eingerichtet, so schaut die Datei so aus:

;;id: . 1
;;last_queried: 1414101618 ;;Fri Oct 24 00:00:18 2014
;;last_success: 1414101618 ;;Fri Oct 24 00:00:18 2014
;;next_probe_time: 1414143412 ;;Fri Oct 24 11:36:52 2014
;;query_failed: 0
;;query_interval: 43200
;;retry_time: 8640
.       86390   IN      DNSKEY  257 3 8 AwEAA...k1ihz0= ;{id = 19036 (ksk), size = 2048b}
   ;;state=2 [  VALID  ] ;;count=0 ;;lastchange=1291283901 ;;Thu Dec  2 10:58:21 2010sdf 

Wie fixed man das?

Zuerst einmal sollte unbound wissen, dass er die Datei selbst aktualisieren soll:

$ fgrep root.key /etc/unbound/unbound.conf
 auto-trust-anchor-file: "/etc/unbound/root.key"

Klar soweit?

Wenn auch das nicht hilft, kann es sein, dass die Datei nicht die notwendigen Rechte hat.

-rw-r--r-- 1 root root 758 Okt 24  2014 /etc/unbound/root.key
-rw-r--r-- 1 unbound unbound 1250 2017-08-17 11:42 /etc/unbound/root.key 

Das ist doch wohl deutlich, oder? Und natürlich leicht zu fixen.

Und andere Resolver-Software? Im Kern geht es um RFC 5011 Unterstützung. Die findet man bei Bind und anderer Software.

Achja, bitte schaut doch mal nach den Kundensystemen, von denen ihr wisst oder ahnt.

« Zurück Weiter » 1 2 3 4 5 6 7 8 ...10