Hello,

I am currently in the process of dealing with water torture attacks on our cache DNS servers (<randomstring>.domain.com queries that never resolve and end up causing enormous upstream traffic, ultimately crushing the authoritative server for domain.com).

To this end, I toyed around with unbound, and noticed that unbound-control's lookup function has a very interesting feature :
$ sudo unbound-control lookup randomtest.google.com
The following name servers are used for lookup of randomtest.google.com.
;rrset 14224 4 0 8 0
google.com.     273424  IN      NS      ns2.google.com.
google.com.     273424  IN      NS      ns3.google.com.
google.com.     273424  IN      NS      ns4.google.com.
google.com.     273424  IN      NS      ns1.google.com.
;rrset 7350 1 0 8 0
ns1.google.com. 266550  IN      A       216.239.32.10
;rrset 57726 1 0 8 0
ns4.google.com. 316926  IN      A       216.239.38.10
;rrset 56988 1 0 8 0
ns3.google.com. 316188  IN      A       216.239.36.10
;rrset 28120 1 0 8 0
ns2.google.com. 287320  IN      A       216.239.34.10
Delegation with 4 names, of which 4 can be examined to query further addresses.
It provides 4 IP addresses.
216.239.34.10 rto 87 msec, ttl 628, ping 71 var 4 rtt 87, tA 0, tAAAA 0, tother 0, EDNS 0 probed. 216.239.36.10 rto 645 msec, ttl 19, ping 101 var 136 rtt 645, tA 0, tAAAA 0, tother 0, EDNS 0 probed. 216.239.38.10 rto 113 msec, ttl 31, ping 97 var 4 rtt 113, tA 0, tAAAA 0, tother 0, EDNS 0 probed. 216.239.32.10 rto 99 msec, ttl 328, ping 47 var 13 rtt 99, tA 0, tAAAA 0, tother 0, EDNS 0 probed.

Namely, for any given hostname, it can find the closest delegation point (in this case, remove unambiguously and with no danger the random part of the attack query, since it goes to the deepest component that retains any meaning DNS-wise).

I thought that since the information was available within the Unbound process, coding a Python module that would keep track of the count of queries to a DDoSed delegation point would be a good start to an algorithm for effectively blocking water torture attacks, but the required function, dns_cache_find_delegation() was not available readily from the Python API.

Therefore, I extended the Python API as per the attached file (also available at http://www.yomi.darkbsd.org/~darksoul/lookup-api-extension.patch), to export struct delegpt and a find_delegation() function that would allow to acquire the delegation point name, records and servers, from the Python module environment.

Example of use case (extremely simplified, implementation of allow_query() not disclosed) :
def operate(id, event, qstate, qdata):
delegation = find_delegation(qstate, qstate.qinfo.qname, len(qstate.qinfo.qname))

    if (event == MODULE_EVENT_NEW) or (event == MODULE_EVENT_PASS):
policy_result = allow_query(qstate.qinfo, delegation, delegation_name)
        if (policy_result): # Pass query to next module
            qstate.ext_state[id] = MODULE_WAIT_MODULE
        else:
            qstate.ext_state[id] = MODULE_ERROR
        return True

This patch, along with an actual module that will SERVFAIL (as above) cache-missing queries going over threshold (therefore reducing upstream traffic to a tenth of what it would be if honoring DDoS-related queries, AND keeping it within our AS), has been running in our production environment at ASAHI Net for several months now, and has been approved for upstream contribution on our side.

I thought this feature would be very useful to have in the Python module environment, so would it be possible for you to consider integrating this patch as a standard feature in Unbound?

Thanks for your time,
--
Stephane LAPIE, EPITA SRS, Promo 2005
"Even when they have digital readouts, I can't understand them."
--MegaTokyo
--- unbound-1.4.17.orig/pythonmod/interface.i
+++ unbound-1.4.17/pythonmod/interface.i
@@ -26,6 +26,8 @@
    #include "util/storage/lruhash.h"
    #include "services/cache/dns.h"
    #include "services/mesh.h"
+   #include "iterator/iter_delegpt.h"
+   #include "iterator/iter_hints.h"
 %}
 
 %include "stdint.i" // uint_16_t can be known type now
@@ -669,6 +672,99 @@ struct config_file {
 };
 
 /* ************************************************************************************ * 
+   ASN: Adding structures related to forwards_lookup and dns_cache_find_delegation
+ * ************************************************************************************ */
+struct delegpt_ns {
+    struct delegpt_ns* next;
+    int resolved;
+    uint8_t got4;
+    uint8_t got6;
+    uint8_t lame;
+    uint8_t done_pside4;
+    uint8_t done_pside6;
+};
+
+struct delegpt_addr {
+    struct delegpt_addr* next_result;
+    struct delegpt_addr* next_usable;
+    struct delegpt_addr* next_target;
+    int attempts;
+    int sel_rtt;
+    int bogus;
+    int lame;
+};
+
+struct delegpt {
+    int namelabs;
+    struct delegpt_ns* nslist;
+    struct delegpt_addr* target_list;
+    struct delegpt_addr* usable_list;
+    struct delegpt_addr* result_list;
+    int bogus;
+    uint8_t has_parent_side_NS;
+    uint8_t dp_type_mlc;
+};
+
+
+%inline %{
+   PyObject* _get_dp_dname(struct delegpt* dp) {
+      return PyString_FromStringAndSize((char*)dp->name, dp->namelen);
+   } 
+   PyObject* _get_dp_dname_components(struct delegpt* dp) {
+      return GetNameAsLabelList((char*)dp->name, dp->namelen);
+   }
+   PyObject* _get_dpns_dname(struct delegpt_ns* dpns) {
+      return PyString_FromStringAndSize((char*)dpns->name, dpns->namelen);
+   }
+   PyObject* _get_dpns_dname_components(struct delegpt_ns* dpns) {
+      return GetNameAsLabelList((char*)dpns->name, dpns->namelen);
+   }
+
+  PyObject* _delegpt_addr_addr_get(struct delegpt_addr* target) {
+     char dest[64];
+     delegpt_addr_addr2str(target, dest, 64);
+     if (dest[0] == 0)
+        return Py_None;
+     return PyString_FromString(dest);
+  }
+
+%}
+
+%extend delegpt {
+   %pythoncode %{
+        __swig_getmethods__["dname"] = _unboundmodule._get_dp_dname
+        if _newclass:dname = _swig_property(_unboundmodule._get_dp_dname)
+
+        __swig_getmethods__["dname_list"] = _unboundmodule._get_dp_dname_components
+        if _newclass:dname_list = _swig_property(_unboundmodule._get_dp_dname_components)
+
+        def _get_dname_str(self): return dnameAsStr(self.dname)
+        __swig_getmethods__["dname_str"] = _get_dname_str
+        if _newclass:dname_str = _swig_property(_get_dname_str)
+   %}
+}
+%extend delegpt_ns {
+   %pythoncode %{
+        __swig_getmethods__["dname"] = _unboundmodule._get_dpns_dname
+        if _newclass:dname = _swig_property(_unboundmodule._get_dpns_dname)
+
+        __swig_getmethods__["dname_list"] = _unboundmodule._get_dpns_dname_components
+        if _newclass:dname_list = _swig_property(_unboundmodule._get_dpns_dname_components)
+
+        def _get_dname_str(self): return dnameAsStr(self.dname)
+        __swig_getmethods__["dname_str"] = _get_dname_str
+        if _newclass:dname_str = _swig_property(_get_dname_str)
+   %}
+}
+%extend delegpt_addr {
+   %pythoncode %{
+        def _addr_get(self): return _delegpt_addr_addr_get(self)
+        __swig_getmethods__["addr"] = _addr_get
+        if _newclass:addr = _swig_property(_addr_get)
+   %}
+}
+
+/* ************************************************************************************ * 
    Enums
  * ************************************************************************************ */
 %rename ("MODULE_STATE_INITIAL") "module_state_initial";
@@ -853,6 +949,65 @@ int set_return_msg(struct module_qstate*
 
 %}
 /* ************************************************************************************ * 
+   ASN: Delegation pointer related functions
+ * ************************************************************************************ */
+
+/* Functions which we will need to lookup delegations */
+struct delegpt* dns_cache_find_delegation(struct module_env* env,
+        char* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass,
+        struct regional* region, struct dns_msg** msg, uint32_t timenow);
+int iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags,
+        struct delegpt* dp);
+struct iter_hints_stub* hints_lookup_stub(struct iter_hints* hints,
+        uint8_t* qname, uint16_t qclass, struct delegpt* dp);
+
+/* Custom function to perform logic similar to the one in daemon/cachedump.c */
+struct delegpt* find_delegation(struct module_qstate* qstate, char *nm, size_t nmlen);
+
+%{
+#define BIT_RD 0x100
+
+struct delegpt* find_delegation(struct module_qstate* qstate, char *nm, size_t nmlen)
+{
+    struct delegpt *dp;
+    struct dns_msg *msg = NULL;
+    struct regional* region = qstate->env->scratch;
+    char b[260];
+    struct query_info qinfo;
+    struct iter_hints_stub* stub;
+    uint32_t timenow = *qstate->env->now;
+
+    regional_free_all(region);
+    qinfo.qname = nm;
+    qinfo.qname_len = nmlen;
+    qinfo.qtype = LDNS_RR_TYPE_A;
+    qinfo.qclass = LDNS_RR_CLASS_IN;
+
+    while(1) {
+        dp = dns_cache_find_delegation(qstate->env, nm, nmlen, qinfo.qtype, qinfo.qclass, region, &msg, timenow);
+        if(!dp)
+            return NULL;
+        if(iter_dp_is_useless(&qinfo, BIT_RD, dp)) {
+            if (dname_is_root(nm))
+                return NULL;
+            nm = dp->name;
+            nmlen = dp->namelen;
+            dname_remove_label((uint8_t**)&nm, &nmlen);
+            dname_str(nm, b);
+            continue;
+        }
+        stub = hints_lookup_stub(qstate->env->hints, qinfo.qname, qinfo.qclass, dp);
+        if (stub) {
+            return stub->dp;
+        } else {
+            return dp;
+        }
+    }
+    return NULL;
+}
+%}
+
+/* ************************************************************************************ * 
    Functions
  * ************************************************************************************ */
 
--- unbound-1.4.17.orig/pythonmod/pythonmod_utils.c
+++ unbound-1.4.17/pythonmod/pythonmod_utils.c
@@ -48,6 +48,7 @@
 #include "util/data/msgreply.h"
 #include "util/storage/slabhash.h"
 #include "util/regional.h"
+#include "iterator/iter_delegpt.h"
 
 #undef _POSIX_C_SOURCE
 #undef _XOPEN_SOURCE
@@ -173,5 +174,19 @@ void reply_addr2str(struct comm_reply* r
 	dest[0] = 0;
 	if (inet_ntop(af, sinaddr, dest, (socklen_t)maxlen) == 0)
 	   return;
+	dest[maxlen-1] = 0;
+}
+
+/* Convert target->addr to string */
+void delegpt_addr_addr2str(struct delegpt_addr* target, char *dest, int maxlen)
+{
+	int af = (int)((struct sockaddr_in*) &(target->addr))->sin_family;
+	void* sinaddr = &((struct sockaddr_in*) &(target->addr))->sin_addr;
+
+	if(af == AF_INET6)
+		sinaddr = &((struct sockaddr_in6*)&(target->addr))->sin6_addr;
+	dest[0] = 0;
+	if (inet_ntop(af, sinaddr, dest, (socklen_t)maxlen) == 0)
+	   return;
 	dest[maxlen-1] = 0;
 }

_______________________________________________
Unbound-users mailing list
[email protected]
http://unbound.nlnetlabs.nl/mailman/listinfo/unbound-users

Reply via email to