DNS Dampening

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.

Avatar
Lutz Donnerhacke 19/11/2015 8:08 am
Please use the language selector in the top navigation to switch to English
Avatar
Erid 15/11/2015 10:42 pm
Could you please have your whole blog in English.We also would like to read
Avatar
Karl Ranseier 27/09/2012 10:58 pm
Das eigentliche Problem des Lösungsansatzes ist, daß er nicht allgemein genutzt werden kann:

Blockiere ich die gespooften IPs (allgemein), so kann ich dafür sorgen, daß ganze Access Netze einen Resolver nicht mehr erreichen. Ein einfaches Beispiel hierfür ist:

Man nehme an, die DNSes von google z.B. würden ein Dampening anwenden wollen. Ich suggeriere eine Amplification attack seitens der recursor mehrerer großer Provider. Der DNS des Anbieters (im Beispiel google) würde also die Recursor der Zugangsprovider 'blacklisten' zumindest solange ich regelmässig (aber nur gelegentlich) einige Anfragen aussende, um die Penalty gezielt oben zu halten. Die Amplification-Attacke ist abgewendet, dafür habe ich sie gegen eine Service-Denial beim Dienstanbieter getauscht. Daher wäre ein Dampening für keinen Diensteanbieter umsetzbar, sofern er gelegentlich erreichbar sein möchte. Das schlimmste aber ist, ich kann spezifische Targets für spezifische Kunden (Clients) gezielt ins Dampening treiben und somit 2 Parteien direkt schaden, wenn ich es geschickt anstelle. (Sorge dafür das Firma X nicht mehr bei Clouddiensteanbieter Y resolven kann ... Nicht so klug ;-) )
Avatar
Lutz Donnerhacke 27/09/2012 10:49 pm
Namecoin skaliert nicht, weil es zu viele "zentrale" Stellen hat. Namecoin ist nicht massentauglich, weil es nicht in die aktuelle Protokollversion hineinwächst, wie es DNSSEC derzeit tut.
Avatar
jym 27/09/2012 5:28 pm
Hallo Lutz,

ist vielleicht ein bisschen off-topic, aber hast du dich mal mit Namecoin beschäftigt?

http://de.wikipedia.org/wiki/Namecoin

Was hältst du von so einem Ansatz?

Gruß
jym
Avatar
Lutz Donnerhacke 25/09/2012 9:51 pm
Es gibt keine offensichtliche Lösung des Problems. Allerdings hat mir Dein Kommentar geholfen, meine aktuellen Experimente an diesem Problem auszurichten: Wie kann ich wenigstens ansatzweise legitime Anfragen beantworten, während der Angriff selbst versandet.

Abwarten!
Avatar
Thomas 25/09/2012 6:03 pm
Wie sinnvoll ist eine Lösung, die "DoS Tür und Tor öffnet"?
Sie macht es sogar noch viel einfacher, Domains für manche Gruppen, zum Beispiel DTAG-Kunden oder x-beliebige andere aus dem Netz zu schießen. Ich muß Deine Nameserver gar nicht mehr unter Dauerfeuer nehmen, sondern ich kann das bequem und bandbreitenschonend über die Strafpunkte steuern. Also DoS statt dDoS.
Avatar
Lutz Donnerhacke 25/09/2012 3:49 pm
IP Spoofing ist genau der Grund, weswegen der Zinnober gemacht wird. Meine Antwortpakete stellen ja genau den Angriff dar.

Ich schweig dann also eine Weile (bis die Punkte verfallen) und mache dann erst weiter. Ja, das öffnet einem DoS Tür und Tor. Ja, es kann sein, daß dann der DTAG Resolver meine Domains gar nicht mehr auflösen kann, weil ich seinen legitimen Paketen auch nicht antworte.

Post a comment

Related content