volatilityfoundation / volatility

An advanced memory forensics framework
http://volatilityfoundation.org/
GNU General Public License v2.0
7.39k stars 1.29k forks source link

dns cache plugin #201

Open gleeda opened 9 years ago

gleeda commented 9 years ago

plans to revise this for the current codebase: https://code.google.com/p/volatility/issues/detail?id=124

Original issue:


Reported by phatbuck...@gmail.com, Jul 24, 2011

Enhancement submission; would love to see the capability to dump contents of the DNS resolver cache if possible. Something akin to "ipconfig /displaydns".

Would also be interesting to see representation of which DNS resolvers are configured on the system.

Perhaps also useful to view the IP address of the DHCP server (if any) that provided the lease to the host.

Thinking about hostname-based attribution of connection information provided by connections/connscan/netscan, DNS hijacking malware, rogue DHCP server detection, etc.

Jul 24, 2011
Project Member #1 mike.auty@gmail.com

(No comment was entered for this change.)

Labels: -Type-Defect Type-Enhancement
Jul 25, 2011
Project Member #2 michael.hale@gmail.com

Thanks for the suggestion, I share the desire for a plugin of this sort. Actually I came close to finding the info when writing the netscan plugin. It may take a little bit, but we'll let you know when its done!

Nov 13, 2011
#3 marko.th...@gmail.com

Hello,here is dnscache plugin, please report all the bugs :) the code looks like crap :( To use this, you need a new version of my heap plugin which is available in another issue.

Nov 14, 2011
Project Member #4 michael.hale@gmail.com

Just CC'ing some others so they know the plugin exists and can check it out. 

Cc: mike.auty@gmail.com scude...@gmail.com labaru...@gmail.com jamie.l...@gmail.com moo...@gmail.com
Nov 15, 2011
#5 marko.th...@gmail.com

Here is a fixed version of the dnscache plugin.

    dnscache.py
8.5 KB   View   Download
Jan 23, 2012
Project Member #6 mike.auty@gmail.com

So I just tried this recently, and ran into a problem with the plugin requiring volatility.plugins.heap, is that another private plugin?  If so, would you be willing to post that as well please?

Jan 23, 2012
#7 phatbuck...@gmail.com

Found posted at least here:

https://code.google.com/p/volatility/issues/attachmentText?id=149&aid=1490011000&name=heap.py&token=YQ42pDThBAxlcjnaQ9VjZ0tw2j0%3A1327342168317

Unsure if updates available anywhere since then.

Jan 23, 2012
Project Member #8 mike.auty@gmail.com

Ok, seems to work on my XP image, but fails on Windows 7 with:

Traceback (most recent call last):
  File "vol.py", line 135, in <module>
    main()
  File "vol.py", line 126, in main
    command.execute()
  File "/home/mike/workspace/volatility/volatility/commands.py", line 101, in execute
    func(outfd, data)
  File "/home/mike/workspace/volatility/volatility/plugins/dnscache.py", line 267, in render_text
    for record_name,record_type,ttl,datalen,section,data in data:
  File "/home/mike/workspace/volatility/volatility/plugins/dnscache.py", line 191, in calculate
    for procname, pid, heap, heap_segments, heap_freelists, heap_virtual_blocks in heapscan.HeapScan(self._config).calculate(pid):
  File "/home/mike/workspace/volatility/volatility/plugins/heap.py", line 84, in calculate
    for offset in heap.Segments:
  File "/home/mike/workspace/volatility/volatility/obj.py", line 777, in __getattr__
    return self.m(attr)
  File "/home/mike/workspace/volatility/volatility/obj.py", line 762, in m
    raise AttributeError("Struct {0} has no member {1}".format(self.obj_name, attr))
AttributeError: Struct _HEAP has no member Segments

Jan 30, 2012
#9 marko.th...@gmail.com

Thanks for testing it. I try to fix it soon for Windows 7 :)

Jan 31, 2013
Project Member #10 michael.hale@gmail.com

Hey guys, I'm going to drop this down to low. Marko, if you happen to upgrade to support Windows 7 (and other hardware archs like x64) or need help doing so, we can bump back up and look into getting it into a future release. 

Labels: -Priority-Medium Priority-Low
Jun 12, 2013
#12 kha...@gmail.com

Updated dnscache.py to work in 2.3 beta.
Attached.

    dnscache.py
8.9 KB   View   Download
Aug 13, 2013
#13 bry...@gmail.com

I have updated the code to remove the dependency on heapscan - it now operates similar to a scanner going though the process memory (and could potentially be migrated to one.)

Unfortunately it still only produces results under a specific profile - 32 bit Windows XP. I suspect the format of the data structure in memory changed between XP and Vista.

Updated version attached, fully 2.3BetaSVN compatible. 

Note: I have changed the name of the command to "dnscachedump" to avoid conflicts against the older heapscan based version.

    dnscachedump.py
9.9 KB   View   Download
gleeda commented 9 years ago

dnscache.py:


import struct
import re
import volatility.commands as commands
import volatility.utils as utils
import volatility.obj as obj
import volatility.win32.tasks as tasks
import volatility.plugins.heap as heapscan
DNSREC = {
        0 : "QUESTION",
        1 : "ANSWER",
        2 : "AUTHORITY",
        3 : "ADDITIONAL"
}

DNS_TYPES = dict(
        DNS_TYPE_A          = 0x0001,
        DNS_TYPE_NS         = 0x0002,
        DNS_TYPE_MD         = 0x0003,
        DNS_TYPE_MF         = 0x0004,
        DNS_TYPE_CNAME      = 0x0005,
        DNS_TYPE_SOA        = 0x0006,
        DNS_TYPE_MB         = 0x0007,
        DNS_TYPE_MG         = 0x0008,
        DNS_TYPE_MR         = 0x0009,
        DNS_TYPE_NULL       = 0x000a,
        DNS_TYPE_WKS        = 0x000b,
        DNS_TYPE_PTR        = 0x000c,
        DNS_TYPE_HINFO      = 0x000d,
        DNS_TYPE_MINFO      = 0x000e,
        DNS_TYPE_MX         = 0x000f,
        DNS_TYPE_TEXT       = 0x0010,
        DNS_TYPE_RP         = 0x0011,
        DNS_TYPE_AFSDB      = 0x0012,
        DNS_TYPE_X25        = 0x0013,
        DNS_TYPE_ISDN       = 0x0014,
        DNS_TYPE_RT         = 0x0015,
        DNS_TYPE_NSAP       = 0x0016,
        DNS_TYPE_NSAPPTR    = 0x0017,
        DNS_TYPE_SIG        = 0x0018,
        DNS_TYPE_KEY        = 0x0019,
        DNS_TYPE_PX         = 0x001a,
        DNS_TYPE_GPOS       = 0x001b,
        DNS_TYPE_AAAA       = 0x001c,
        DNS_TYPE_LOC        = 0x001d,
        DNS_TYPE_NXT        = 0x001e,
        DNS_TYPE_EID        = 0x001f,
        DNS_TYPE_NIMLOC     = 0x0020,
        DNS_TYPE_SRV        = 0x0021,
        DNS_TYPE_ATMA       = 0x0022,
        DNS_TYPE_NAPTR      = 0x0023,
        DNS_TYPE_KX         = 0x0024,
        DNS_TYPE_CERT       = 0x0025,
        DNS_TYPE_A6         = 0x0026,
        DNS_TYPE_DNAME      = 0x0027,
        DNS_TYPE_SINK       = 0x0028,
        DNS_TYPE_OPT        = 0x0029,
        DNS_TYPE_UINFO      = 0x0064,
        DNS_TYPE_UID        = 0x0065,
        DNS_TYPE_GID        = 0x0066,
        DNS_TYPE_UNSPEC     = 0x0067,
        DNS_TYPE_ADDRS      = 0x00f8,
        DNS_TYPE_TKEY       = 0x00f9,
        DNS_TYPE_TSIG       = 0x00fa,
        DNS_TYPE_IXFR       = 0x00fb,
        DNS_TYPE_AXFR       = 0x00fc,
        DNS_TYPE_MAILB      = 0x00fd,
        DNS_TYPE_MAILA      = 0x00fe,
        DNS_TYPE_ALL        = 0x00ff,
        DNS_TYPE_ANY        = 0x00ff,
)

dnstypes = {
    '_DNS_RECORD_FLAGS' : [ 4, {
    'Section'         : [ 0, ['BitField', dict(start_bit = 0, end_bit = 2)]],
    'Delete'          : [ 0, ['BitField', dict(start_bit = 2, end_bit = 3)]],
    'CharSet'         : [ 0, ['BitField', dict(start_bit = 3, end_bit = 5)]],
    'Unused'          : [ 0, ['BitField', dict(start_bit = 5, end_bit = 8)]],
    'Reserved'        : [ 0, ['BitField', dict(start_bit = 8, end_bit = 32)]],
    } ],

    '_DNS_RECORD' : [ None, {
    'pNext'       : [ 0x00, ['pointer', ['_DNS_RECORD']]],
    'pName'       : [ 0x04, ['pointer', ['unsigned short']]],
    'wType'       : [ 0x08, ['unsigned short']],
    'wDataLength' : [ 0x0a, ['unsigned short']],
    'Flags'       : [ 0x0c, ['_DNS_RECORD_FLAGS']],
    'dwTtl'       : [ 0x10, ['unsigned long']],
    'dwReserved'  : [ 0x14, ['unsigned long']],
    'Data'        : [ 0x18, ['unsigned long']], # this can be various size, depends on datalength
} ]}

class dnscache(commands.Command):
    #""" Checks entries from dnscache"""

    def __init__(self, config, *args):
        commands.Command.__init__(self, config, *args)

    def dword_to_ip(self,value):

        a = value & 0xff
        b = (value & 0xff00) >> 8
        c = (value & 0xff0000) >> 16
        d = (value & 0xff000000) >> 24
        ip = "%d.%d.%d.%d" % (a,b,c,d)

        return ip

    def get_name_by_flag(self, flags, val):

        if flags.has_key(val):
            return flags[val]
        else:
            return [f for f in flags if (flags[f] == val)]

    def get_dns_stuff(self,entry,dns):

        data = ""
        datalen = dns.wDataLength.v()
        ttl = dns.dwTtl.v()
        record_type = self.get_name_by_flag(DNS_TYPES,dns.wType.v())[0]

        if record_type == 'DNS_TYPE_A':
            data = self.dword_to_ip(dns.Data.v())

        elif record_type in ['DNS_TYPE_PTR','DNS_TYPE_CNAME','DNS_TYPE_DNAME','DNS_TYPE_NS']:
            data = self.get_record_name(entry,dns.Data.v())

        flags = dns.Flags.dereference_as('_DNS_RECORD_FLAGS')
        section = DNSREC[int(flags.Section)]
        record_name = self.get_record_name(entry,dns.pName.v())

        return record_name, record_type, ttl, datalen, section, data

    def get_record_name(self,entry,data, length=128):

        record_name = entry.obj_vm.zread(data,length)
        record_name = re.findall(r'(([\x20-\x80]\x00){6,128}\x00\x00)',record_name)
        if len(record_name) > 0:
            record_name = record_name[0][0].replace('\x00','')
        else:
            record_name = "-"

        return record_name

    def get_loaded_mods(self, eproc):

        if eproc.UniqueProcessId:
            if eproc.Peb.Ldr.InLoadOrderModuleList:
                for entry in eproc.Peb.Ldr.InLoadOrderModuleList.list_of_type("_LDR_DATA_TABLE_ENTRY", "InLoadOrderLinks"):
                    yield entry

    def calculate(self):

        cache_found = False

        address_space = utils.load_as(self._config)

        address_space.profile.add_types(dnstypes)

        for eproc in tasks.pslist(address_space):

            pid = eproc.UniqueProcessId.v()

            if str(eproc.ImageFileName) != 'svchost.exe':
                continue

            if 'networkservice' not in eproc.Peb.ProcessParameters.CommandLine.v().lower():
                continue

            break

        found_resolver_lib = False

        mods = self.get_loaded_mods(eproc)

        for mod in mods:

            if str(mod.BaseDllName).lower() != "dnsrslvr.dll":
                continue
            found_resolver_lib = True
            break

        if found_resolver_lib == False:
            return

        for procname, pid, heap, heap_segments, heap_freelists, heap_virtual_blocks in heapscan.HeapScan(self._config).calculate(pid):

            for segment in heap_segments:
                if (cache_found):
                    break

                entry_offs = segment.FirstEntry.v()

                while 1:

                    entry = obj.Object('_HEAP_ENTRY',offset = entry_offs, vm = heap.obj_vm)

                    if not entry:
                        break
                    if entry.Size == 0:
                        break

                    if entry.Size == 0x7:
                        block_data = entry.obj_vm.zread(entry.v()+8,block_size)

                        pointer = block_data[4:8]
                        pointer = struct.unpack("<L",pointer)[0]
                        if pointer != 0:
                            offset = pointer - entry.v() - 8
                            data = block_data[offset:offset+20].replace('\x00','')
                            if data == 'localhost':
                                cache_found = True
                                break

                    block_size = (entry.Size-1) * 8
                    entry_offs += block_size + 8

        if cache_found == False:
            return

        while 1:

            entry = obj.Object('_HEAP_ENTRY',offset = entry_offs, vm = heap.obj_vm)

            if not entry:
                break
            if entry.Size == 0:
                break

            block_size = (entry.Size-1) * 8
            entry_offs += block_size + 8
            block_data = entry.obj_vm.zread(entry.v()+8,block_size)

            main_name = block_data[0x1c:].replace('\x00','')

            pointer = struct.unpack("<L",block_data[0x10:0x14])[0]

            if pointer != 0:

                dns = obj.Object('_DNS_RECORD',vm = entry.obj_vm,offset = pointer)

                record_name,record_type,ttl,datalen,section,data = self.get_dns_stuff(entry,dns)

                yield record_name,record_type,ttl,datalen,section,data

                nextdns = dns.pNext.dereference_as('_DNS_RECORD')

                while nextdns:

                    record_name,record_type,ttl,datalen,section,data = self.get_dns_stuff(entry,nextdns)

                    yield record_name,record_type,ttl,datalen,section,data

                    nextdns = nextdns.pNext.dereference_as('_DNS_RECORD')

    def render_text(self, outfd, data):

        outfd.write("{0:<40} {1:<16} {2:<8} {3:<8} {4:<12} {5}\n".format("Name", "Type", "TTL", "DataLen", "Section", "Data"))

        for record_name,record_type,ttl,datalen,section,data in data:

            outfd.write("{0:<40} {1:<16} {2:<8} {3:<8} {4:<12} {5}\n".format(record_name,record_type,ttl,datalen,section,data))
gleeda commented 9 years ago

dnscachedump.py:

import struct
import re
import volatility.commands as commands
import volatility.utils as utils
import volatility.obj as obj
import volatility.win32.tasks as tasks
import volatility.debug as debug

DNSREC = {
        0 : "QUESTION",
        1 : "ANSWER",
        2 : "AUTHORITY",
        3 : "ADDITIONAL"
}

DNS_TYPES = dict(
        DNS_TYPE_A          = 0x0001,
        DNS_TYPE_NS         = 0x0002,
        DNS_TYPE_MD         = 0x0003,
        DNS_TYPE_MF         = 0x0004,
        DNS_TYPE_CNAME      = 0x0005,
        DNS_TYPE_SOA        = 0x0006,
        DNS_TYPE_MB         = 0x0007,
        DNS_TYPE_MG         = 0x0008,
        DNS_TYPE_MR         = 0x0009,
        DNS_TYPE_NULL       = 0x000a,
        DNS_TYPE_WKS        = 0x000b,
        DNS_TYPE_PTR        = 0x000c,
        DNS_TYPE_HINFO      = 0x000d,
        DNS_TYPE_MINFO      = 0x000e,
        DNS_TYPE_MX         = 0x000f,
        DNS_TYPE_TEXT       = 0x0010,
        DNS_TYPE_RP         = 0x0011,
        DNS_TYPE_AFSDB      = 0x0012,
        DNS_TYPE_X25        = 0x0013,
        DNS_TYPE_ISDN       = 0x0014,
        DNS_TYPE_RT         = 0x0015,
        DNS_TYPE_NSAP       = 0x0016,
        DNS_TYPE_NSAPPTR    = 0x0017,
        DNS_TYPE_SIG        = 0x0018,
        DNS_TYPE_KEY        = 0x0019,
        DNS_TYPE_PX         = 0x001a,
        DNS_TYPE_GPOS       = 0x001b,
        DNS_TYPE_AAAA       = 0x001c,
        DNS_TYPE_LOC        = 0x001d,
        DNS_TYPE_NXT        = 0x001e,
        DNS_TYPE_EID        = 0x001f,
        DNS_TYPE_NIMLOC     = 0x0020,
        DNS_TYPE_SRV        = 0x0021,
        DNS_TYPE_ATMA       = 0x0022,
        DNS_TYPE_NAPTR      = 0x0023,
        DNS_TYPE_KX         = 0x0024,
        DNS_TYPE_CERT       = 0x0025,
        DNS_TYPE_A6         = 0x0026,
        DNS_TYPE_DNAME      = 0x0027,
        DNS_TYPE_SINK       = 0x0028,
        DNS_TYPE_OPT        = 0x0029,
        DNS_TYPE_UINFO      = 0x0064,
        DNS_TYPE_UID        = 0x0065,
        DNS_TYPE_GID        = 0x0066,
        DNS_TYPE_UNSPEC     = 0x0067,
        DNS_TYPE_ADDRS      = 0x00f8,
        DNS_TYPE_TKEY       = 0x00f9,
        DNS_TYPE_TSIG       = 0x00fa,
        DNS_TYPE_IXFR       = 0x00fb,
        DNS_TYPE_AXFR       = 0x00fc,
        DNS_TYPE_MAILB      = 0x00fd,
        DNS_TYPE_MAILA      = 0x00fe,
        DNS_TYPE_ALL        = 0x00ff,
        DNS_TYPE_ANY        = 0x00ff,
)

dnstypes = {
    '_DNS_RECORD_FLAGS' : [ 4, {
    'Section'         : [ 0, ['BitField', dict(start_bit = 0, end_bit = 2)]],
    'Delete'          : [ 0, ['BitField', dict(start_bit = 2, end_bit = 3)]],
    'CharSet'         : [ 0, ['BitField', dict(start_bit = 3, end_bit = 5)]],
    'Unused'          : [ 0, ['BitField', dict(start_bit = 5, end_bit = 8)]],
    'Reserved'        : [ 0, ['BitField', dict(start_bit = 8, end_bit = 32)]],
    } ],

    '_DNS_RECORD' : [ None, {
    'pNext'       : [ 0x00, ['pointer', ['_DNS_RECORD']]],
    'pName'       : [ 0x04, ['pointer', ['unsigned short']]],
    'wType'       : [ 0x08, ['unsigned short']],
    'wDataLength' : [ 0x0a, ['unsigned short']],
    'Flags'       : [ 0x0c, ['_DNS_RECORD_FLAGS']],
    'dwTtl'       : [ 0x10, ['unsigned long']],
    'dwReserved'  : [ 0x14, ['unsigned long']],
    'Data'        : [ 0x18, ['unsigned long']], # this can be various size, depends on datalength
} ]}

class DNSCacheDumpRecords(obj.ProfileModification):
    conditions = {'os': lambda x: x == 'windows'}
    def modification(self, profile):
        profile.vtypes.update(dnstypes)

class dnscachedump(commands.Command):
    #""" Checks entries from dnscache"""

    def __init__(self, config, *args):
        commands.Command.__init__(self, config, *args)

    def dword_to_ip(self, value):

        a = value & 0xff
        b = (value & 0xff00) >> 8
        c = (value & 0xff0000) >> 16
        d = (value & 0xff000000) >> 24
        ip = "%d.%d.%d.%d" % (a,b,c,d)

        return ip

    def get_name_by_flag(self, flags, val):

        if flags.has_key(val):
            return flags[val]
        else:
            return [f for f in flags if (flags[f] == val)]

    def get_dns_stuff(self, entry, dns):

        data = ""
        datalen = dns.wDataLength.v()
        ttl = dns.dwTtl.v()
        record_type = self.get_name_by_flag(DNS_TYPES,dns.wType.v())[0]

        if record_type == 'DNS_TYPE_A':
            data = self.dword_to_ip(dns.Data.v())

        elif record_type in ['DNS_TYPE_PTR','DNS_TYPE_CNAME','DNS_TYPE_DNAME','DNS_TYPE_NS']:
            data = self.get_record_name(entry,dns.Data.v())

        flags = dns.Flags.dereference_as('_DNS_RECORD_FLAGS')
        section = DNSREC[int(flags.Section)]
        record_name = self.get_record_name(entry,dns.pName.v())

        return record_name, record_type, ttl, datalen, section, data

    def get_record_name(self, entry, data, length=128):

        record_name = entry.obj_vm.zread(data,length)
        record_name = re.findall(r'(([\x20-\x80]\x00){6,128}\x00\x00)',record_name)
        if len(record_name) > 0:
            record_name = record_name[0][0].replace('\x00','')
        else:
            record_name = "-"

        return record_name

    def calculate(self):
        cache_found = False
        resolver_lib_found = False
        self._task = None

        address_space = utils.load_as(self._config)

        for task in tasks.pslist(address_space):
            if str(task.ImageFileName) in 'svchost.exe':
                task_command_line = task.Peb.ProcessParameters.CommandLine.v().lower()
                if 'networkservice' in task_command_line and not 'restricted' in task_command_line:
                    self._task = task

        if self._task is not None:
            pid = self._task.UniqueProcessId.v()
            task_space = self._task.get_process_address_space()
            pages = task_space.get_available_pages()
            if self._config.verbose:
                debug.info("FOUND TARGET PID {0} {1}".format(pid, self._task.Peb.ProcessParameters.CommandLine.v().lower()))

        else:
            debug.error("Unable to find target process.")
            return

        for mod in self._task.get_load_modules():
            if str(mod.BaseDllName).lower() not in "dnsrslvr.dll":
                continue
            resolver_lib_found = True
            break

        if not resolver_lib_found:
            return
        else:
            if self._config.verbose:
                debug.info("FOUND TARGET DLL")

        if pages:
            for p in pages:

                if cache_found:
                    break

                data = task_space.read(p[0], p[1])
                if data == None:
                    if self._config.verbose:
                        debug.info("Memory Not Accessible: Virtual Address: 0x{0:x} File Offset: 0x{1:x} Size: 0x{2:x}".format(p[0], task.obj_offset, p[1]))
                else:                   
                    entry_offs = p[0]

                    while 1: 
                        entry = obj.Object('_HEAP_ENTRY', offset=entry_offs, vm=task_space)

                        if not entry:
                            break
                        if entry.Size == 0:
                            break

                        if entry.Size == 0x7:
                            block_data = entry.obj_vm.zread(entry.v()+8,block_size)

                            pointer = block_data[4:8]
                            if len(pointer) < 4:
                                pointer = 0
                            else:
                                pointer = struct.unpack("<L",pointer)[0]
                            if pointer != 0:
                                offset = pointer - entry.v() - 8
                                data = block_data[offset:offset+20].replace('\x00','')
                                if data == 'localhost':
                                    cache_found = True
                                    debug.info("Cache Entry Found at Virtual Address 0x{0:x}".format(p[0]))
                                    break

                        block_size = (entry.Size-1) * 8
                        entry_offs += block_size + 8

            if not cache_found:
                return

            while 1:

                entry = obj.Object('_HEAP_ENTRY', offset=entry_offs, vm=task_space)

                if not entry:
                    break
                if entry.Size == 0:
                    break

                block_size = (entry.Size-1) * 8
                entry_offs += block_size + 8
                block_data = entry.obj_vm.zread(entry.v()+8,block_size)

                main_name = block_data[0x1c:].replace('\x00','')

                pointer = struct.unpack("<L",block_data[0x10:0x14])[0]

                if pointer != 0:

                    dns = obj.Object('_DNS_RECORD', vm=entry.obj_vm, offset=pointer)

                    record_name,record_type,ttl,datalen,section,data = self.get_dns_stuff(entry,dns)
                    yield record_name,record_type,ttl,datalen,section,data

                    nextdns = dns.pNext.dereference_as('_DNS_RECORD')

                    while nextdns:
                        record_name,record_type,ttl,datalen,section,data = self.get_dns_stuff(entry,nextdns)
                        yield record_name,record_type,ttl,datalen,section,data
                        nextdns = nextdns.pNext.dereference_as('_DNS_RECORD')

        else:
            debug.error("Unable to read pages for task.")

    def render_text(self, outfd, data):

        if data is not None:
            outfd.write("{0:<40} {1:<16} {2:<8} {3:<8} {4:<12} {5}\n".format("Name", "Type", "TTL", "DataLen", "Section", "Data"))

            for record_name,record_type,ttl,datalen,section,data in data:
                outfd.write("{0:<40} {1:<16} {2:<8} {3:<8} {4:<12} {5}\n".format(record_name,record_type,ttl,datalen,section,data))
tomchop commented 8 years ago

Are you still working on this? If not, I'm happy to take over (or start from scratch if the code is broken on 2.5)

gleeda commented 8 years ago

Actually, I'm not working on this at all, I just put it here so we wouldn't forget. If you'd like to take it over, please do :-)

geirskjo commented 7 years ago

An attempt to solve this problem. https://github.com/mnemonic-no/dnscache

Any feedback is greatly appreciated.

iMHLv2 commented 7 years ago

@geirskjo looks nicely done, good work. do you mind if we tweet it and start getting some feedback from the community for you?

geirskjo commented 7 years ago

I don't mind at all. Any feedback would be great.