├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── rr-types.py ├── qos_server ├── __init__.py └── query.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.swp 3 | *.pyc 4 | qos_server.egg-info/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | cache: pip 4 | 5 | services: 6 | - redis-server 7 | 8 | python: 9 | - "3.3" 10 | - "3.4" 11 | - "3.5" 12 | - "3.5-dev" 13 | - "nightly" 14 | 15 | install: 16 | - pip install coveralls 17 | - pip install codecov 18 | - pip install . 19 | 20 | script: 21 | - coverage run --parallel-mode --source=qos_server qos_server/__init__.py & 22 | - pid=$! 23 | - sleep 5 24 | - curl http://127.0.0.1:8888/query/www.microsoft.com 25 | - curl http://127.0.0.1:8888/query/80.169.63.162 26 | - kill -s INT $pid 27 | 28 | after_success: 29 | - codecov 30 | - coveralls 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012,2013 Alexandre Dulaunoy - a(AT)foo(DOT)be 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | (1) Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | (2) Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | 15 | (3)The name of the author may not be used to 16 | endorse or promote products derived from this software without 17 | specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 23 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 27 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 28 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/adulau/pdns-qof-server.svg?branch=master)](https://travis-ci.org/adulau/pdns-qof-server) 2 | 3 | Passive DNS server interface 4 | ============================ 5 | 6 | pdns-qof server is a [cof](https://github.com/adulau/pdns-qof) "Common Output Format" compliant passive DNS query interface for the [pdns-toolkit](https://github.com/adulau/pdns-toolkit) or similar passive dns. 7 | 8 | Requirements 9 | ------------ 10 | 11 | - Python 3 12 | - [Tornado](http://www.tornadoweb.org) 13 | - Python [redis](https://pypi.python.org/pypi/redis/) client 14 | 15 | Installation 16 | ------------ 17 | 18 | ``` 19 | pip3 install . 20 | ``` 21 | 22 | 23 | Running the qof-server 24 | ---------------------- 25 | 26 | The server is using the default Redis configuration for the pdns-toolkit. Don't forget to change it if you have different 27 | configuration for your Passive dns data store. 28 | 29 | ```bash 30 | qos-server 31 | ``` 32 | 33 | Usage 34 | ----- 35 | 36 | ```bash 37 | curl http://127.0.0.1:8888/query/www.microsoft.com 38 | ``` 39 | 40 | ```json 41 | {"count": 127814, "time_first": 1298398002, "rrtype": "CNAME", "rrname": "www.microsoft.com", "rdata": "toggle.www.ms.akadns.net", "time_last": 1389022792} 42 | ``` 43 | ```bash 44 | curl http://127.0.0.1:8888/query/80.169.63.162 45 | ``` 46 | 47 | ```json 48 | {"count": 112, "time_first": 1298398002, "rrtype": "A", "rrname": "infosports.dhnet.be", "rdata": "212.35.116.234", "time_last": 1354530214} 49 | {"count": 4, "time_first": 1361180820, "rrtype": "A", "rrname": "infosports.dh.be", "rdata": "80.169.63.162", "time_last": 1366210757} 50 | {"count": 2, "time_first": 1357803074, "rrtype": "A", "rrname": "maintenance.lalibre.be", "rdata": "212.35.116.249", "time_last": 1357803074} 51 | {"count": 2, "time_first": 1388399295, "rrtype": "A", "rrname": "www.llb.be", "rdata": "80.169.63.162", "time_last": 1388399295} 52 | {"count": 48, "time_first": 1374008604, "rrtype": "A", "rrname": "s.llb.be", "rdata": "80.169.63.162", "time_last": 1384916107} 53 | {"count": 94256, "time_first": 1298398002, "rrtype": "A", "rrname": "www.lalibre.be", "rdata": "212.35.116.249", "time_last": 1361278027} 54 | {"count": 213, "time_first": 1298398834, "rrtype": "A", "rrname": "infosports.lalibre.be", "rdata": "212.35.116.234", "time_last": 1355432823} 55 | ``` 56 | 57 | rr-types tool 58 | ------------- 59 | 60 | rr-types.py is a tool to dump current IANA DNS RR types in various formats. 61 | 62 | ```bash 63 | python3 bin/rr-types.py --help 64 | usage: rr-types.py [-h] [-d] [-j] [-i] [-v] 65 | 66 | Dump IANA DNS parameters in various formats 67 | 68 | optional arguments: 69 | -h, --help show this help message and exit 70 | -d Python dict 71 | -j JSON output (default format) 72 | -i Disable integer value RR check 73 | -v Verbose output 74 | ``` 75 | 76 | -------------------------------------------------------------------------------- /bin/rr-types.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Tool to dump in IANA RR DNS parameters in JSON or Python dict format 5 | # 6 | # Software is free software released under the "Modified BSD license" 7 | # 8 | # Copyright (c) 2013 Alexandre Dulaunoy - a@foo.be 9 | 10 | import argparse 11 | import csv 12 | import json 13 | import codecs 14 | import urllib.request 15 | 16 | #IANA format (CSV) is TYPE,Value,Meaning,Reference,Template,Registration Date 17 | ianarrurl = "http://www.iana.org/assignments/dns-parameters/dns-parameters-4.csv" 18 | 19 | argParser = argparse.ArgumentParser(description='Dump IANA DNS parameters in various formats') 20 | argParser.add_argument('-d', action='store_true', help='Python dict') 21 | argParser.add_argument('-j', action='store_true', default=True, help='JSON output (default format)') 22 | argParser.add_argument('-i', action='store_false', default=True, help='Disable integer value RR check') 23 | argParser.add_argument('-v', action='store_true', help='Verbose output') 24 | args = argParser.parse_args() 25 | 26 | 27 | rrset=[row for row in csv.DictReader(codecs.iterdecode(urllib.request.urlopen(ianarrurl),'utf-8'),fieldnames = ( "Type","Value","Meaning","Reference","Template","Registration Date" ))] 28 | 29 | if args.i: 30 | rri = [] 31 | for rr in rrset[1:]: 32 | if rr['Value'].isdigit(): 33 | rri.append(rr) 34 | rrset=rri 35 | else: 36 | rrset=rrset[1:] 37 | 38 | if args.d: 39 | print (rrset) 40 | 41 | if args.j: 42 | jsonout = json.dumps( rrset ) 43 | print (jsonout) 44 | -------------------------------------------------------------------------------- /qos_server/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # A pdns-qof compliant passive DNS interface for the pdns-toolkit 5 | # 6 | # https://github.com/adulau/pdns-qof-server/ 7 | # https://github.com/adulau/pdns-toolkit/ 8 | # 9 | # The pdns-qof - Passive DNS Query Output Format Description are described at 10 | # 11 | # https://github.com/adulau/pdns-qof 12 | # 13 | # Software is free software released under the "Modified BSD license" 14 | # 15 | # Copyright (c) 2013 Alexandre Dulaunoy - a@foo.be 16 | 17 | import tornado.escape 18 | import tornado.web 19 | import tornado.process 20 | from tornado.ioloop import IOLoop 21 | from tornado.concurrent import run_on_executor 22 | 23 | from concurrent.futures import ThreadPoolExecutor 24 | import argparse 25 | import sys 26 | import signal 27 | from ipaddress import ip_address 28 | 29 | from qos_server.query import QueryRecords 30 | 31 | 32 | def handle_signal(sig, frame): 33 | IOLoop.instance().add_callback(IOLoop.instance().stop) 34 | 35 | 36 | def is_ip(q): 37 | try: 38 | ip_address(q) 39 | return True 40 | except: 41 | return False 42 | 43 | 44 | class InfoHandler(tornado.web.RequestHandler): 45 | def get(self): 46 | response = {'version': 'git', 'software': 'pdns-qof-server'} 47 | self.write(response) 48 | 49 | 50 | class QueryHandler(tornado.web.RequestHandler): 51 | 52 | # Default value in Python 3.5 53 | # https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor 54 | nb_threads = tornado.process.cpu_count() * 5 55 | executor = ThreadPoolExecutor(nb_threads) 56 | 57 | @run_on_executor 58 | def run_request(self, q): 59 | if is_ip(q): 60 | q = query.getAssociatedRecords(q) 61 | else: 62 | q = [q] 63 | return [query.getRecord(x) for x in q] 64 | 65 | @tornado.gen.coroutine 66 | def get(self, q): 67 | print("query: " + q) 68 | try: 69 | responses = yield self.run_request(q) 70 | for r in responses: 71 | self.write(r) 72 | except Exception as e: 73 | print('Something went wrong with {}:\n{}'.format(q, e)) 74 | finally: 75 | self.finish() 76 | 77 | 78 | def main(): 79 | global query 80 | signal.signal(signal.SIGINT, handle_signal) 81 | signal.signal(signal.SIGTERM, handle_signal) 82 | argParser = argparse.ArgumentParser(description='qof-server server') 83 | argParser.add_argument('-o', default='https://www.circl.lu/pdns/', help='Origin of the PDNS (default: https://www.circl.lu/pdns/)') 84 | argParser.add_argument('-p', default=8888, help='qof-server TCP port (default 8888)') 85 | argParser.add_argument('-l', default='localhost', help='qof-server listen address (default localhost)') 86 | argParser.add_argument('-rp', default=6379, help='redis-server TCP port (default 8888)') 87 | argParser.add_argument('-rl', default='localhost', help='redis-server listen address (default localhost)') 88 | argParser.add_argument('-rd', default=0, help='redis-server database (default 0)') 89 | args = argParser.parse_args() 90 | 91 | origin = args.o 92 | port = args.p 93 | listen = args.l 94 | redis_port = args.rp 95 | redis_listen = args.rl 96 | redis_db = args.rd 97 | 98 | query = QueryRecords(redis_listen, redis_port, redis_db, origin) 99 | 100 | application = tornado.web.Application([(r"/query/(.*)", QueryHandler), 101 | (r"/info", InfoHandler)]) 102 | 103 | application.listen(port, address=listen) 104 | IOLoop.instance().start() 105 | IOLoop.instance().stop() 106 | return 0 107 | 108 | if __name__ == '__main__': 109 | sys.exit(main()) 110 | elif __name__ == "test": 111 | query = Query('localhost', 6379, 0, 'https://www.circl.lu/pdns/') 112 | qq = ["foo.be", "8.8.8.8"] 113 | 114 | for q in qq: 115 | if is_ip(q): 116 | for x in query.getAssociatedRecords(q): 117 | print(query.getRecord(x)) 118 | else: 119 | print(query.getRecord(t=q)) 120 | -------------------------------------------------------------------------------- /qos_server/query.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | import redis 6 | 7 | 8 | class QueryRecords(object): 9 | 10 | def __init__(self, redis_listen, redis_port, redis_db, origin): 11 | self.rrset = [ 12 | {"Reference": "[RFC1035]", "Type": "A", "Value": "1", "Meaning": "a host address", "Template": "", "Registration Date": ""}, 13 | {"Reference": "[RFC1035]", "Type": "NS", "Value": "2", "Meaning": "an authoritative name server", "Template": "", "Registration Date": ""}, 14 | {"Reference": "[RFC1035]", "Type": "MD", "Value": "3", "Meaning": "a mail destination (OBSOLETE - use MX)", "Template": "", "Registration Date": ""}, 15 | {"Reference": "[RFC1035]", "Type": "MF", "Value": "4", "Meaning": "a mail forwarder (OBSOLETE - use MX)", "Template": "", "Registration Date": ""}, 16 | {"Reference": "[RFC1035]", "Type": "CNAME", "Value": "5", "Meaning": "the canonical name for an alias", "Template": "", "Registration Date": ""}, 17 | {"Reference": "[RFC1035]", "Type": "SOA", "Value": "6", "Meaning": "marks the start of a zone of authority", "Template": "", "Registration Date": ""}, 18 | {"Reference": "[RFC1035]", "Type": "MB", "Value": "7", "Meaning": "a mailbox domain name (EXPERIMENTAL)", "Template": "", "Registration Date": ""}, 19 | {"Reference": "[RFC1035]", "Type": "MG", "Value": "8", "Meaning": "a mail group member (EXPERIMENTAL)", "Template": "", "Registration Date": ""}, 20 | {"Reference": "[RFC1035]", "Type": "MR", "Value": "9", "Meaning": "a mail rename domain name (EXPERIMENTAL)", "Template": "", "Registration Date": ""}, 21 | {"Reference": "[RFC1035]", "Type": "NULL", "Value": "10", "Meaning": "a null RR (EXPERIMENTAL)", "Template": "", "Registration Date": ""}, 22 | {"Reference": "[RFC1035]", "Type": "WKS", "Value": "11", "Meaning": "a well known service description", "Template": "", "Registration Date": ""}, 23 | {"Reference": "[RFC1035]", "Type": "PTR", "Value": "12", "Meaning": "a domain name pointer", "Template": "", "Registration Date": ""}, 24 | {"Reference": "[RFC1035]", "Type": "HINFO", "Value": "13", "Meaning": "host information", "Template": "", "Registration Date": ""}, 25 | {"Reference": "[RFC1035]", "Type": "MINFO", "Value": "14", "Meaning": "mailbox or mail list information", "Template": "", "Registration Date": ""}, 26 | {"Reference": "[RFC1035]", "Type": "MX", "Value": "15", "Meaning": "mail exchange", "Template": "", "Registration Date": ""}, 27 | {"Reference": "[RFC1035]", "Type": "TXT", "Value": "16", "Meaning": "text strings", "Template": "", "Registration Date": ""}, 28 | {"Reference": "[RFC1183]", "Type": "RP", "Value": "17", "Meaning": "for Responsible Person", "Template": "", "Registration Date": ""}, 29 | {"Reference": "[RFC1183][RFC5864]", "Type": "AFSDB", "Value": "18", "Meaning": "for AFS Data Base location", "Template": "", "Registration Date": ""}, 30 | {"Reference": "[RFC1183]", "Type": "X25", "Value": "19", "Meaning": "for X.25 PSDN address", "Template": "", "Registration Date": ""}, 31 | {"Reference": "[RFC1183]", "Type": "ISDN", "Value": "20", "Meaning": "for ISDN address", "Template": "", "Registration Date": ""}, 32 | {"Reference": "[RFC1183]", "Type": "RT", "Value": "21", "Meaning": "for Route Through", "Template": "", "Registration Date": ""}, 33 | {"Reference": "[RFC1706]", "Type": "NSAP", "Value": "22", "Meaning": "for NSAP address, NSAP style A record", "Template": "", "Registration Date": ""}, 34 | {"Reference": "[RFC1348][RFC1637][RFC1706]", "Type": "NSAP-PTR", "Value": "23", "Meaning": "for domain name pointer, NSAP style", "Template": "", "Registration Date": ""}, 35 | {"Reference": "[RFC4034][RFC3755][RFC2535][RFC2536][RFC2537][RFC2931][RFC3110][RFC3008]", "Type": "SIG", "Value": "24", "Meaning": "for security signature", "Template": "", "Registration Date": ""}, 36 | {"Reference": "[RFC4034][RFC3755][RFC2535][RFC2536][RFC2537][RFC2539][RFC3008][RFC3110]", "Type": "KEY", "Value": "25", "Meaning": "for security key", "Template": "", "Registration Date": ""}, 37 | {"Reference": "[RFC2163]", "Type": "PX", "Value": "26", "Meaning": "X.400 mail mapping information", "Template": "", "Registration Date": ""}, 38 | {"Reference": "[RFC1712]", "Type": "GPOS", "Value": "27", "Meaning": "Geographical Position", "Template": "", "Registration Date": ""}, 39 | {"Reference": "[RFC3596]", "Type": "AAAA", "Value": "28", "Meaning": "IP6 Address", "Template": "", "Registration Date": ""}, 40 | {"Reference": "[RFC1876]", "Type": "LOC", "Value": "29", "Meaning": "Location Information", "Template": "", "Registration Date": ""}, 41 | {"Reference": "[RFC3755][RFC2535]", "Type": "NXT", "Value": "30", "Meaning": "Next Domain (OBSOLETE)", "Template": "", "Registration Date": ""}, 42 | {"Reference": "[Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt]", "Type": "EID", "Value": "31", "Meaning": "Endpoint Identifier", "Template": "", "Registration Date": "1995-06"}, 43 | {"Reference": "[1][Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt]", "Type": "NIMLOC", "Value": "32", "Meaning": "Nimrod Locator", "Template": "", "Registration Date": "1995-06"}, 44 | {"Reference": "[1][RFC2782]", "Type": "SRV", "Value": "33", "Meaning": "Server Selection", "Template": "", "Registration Date": ""}, 45 | {"Reference": "[\n ATM Forum Technical Committee, \"ATM Name System, V2.0\", Doc ID: AF-DANS-0152.000, July 2000. Available from and held in escrow by IANA.]", "Type": "ATMA", "Value": "34", "Meaning": "ATM Address", "Template": "", "Registration Date": ""}, 46 | {"Reference": "[RFC2915][RFC2168][RFC3403]", "Type": "NAPTR", "Value": "35", "Meaning": "Naming Authority Pointer", "Template": "", "Registration Date": ""}, 47 | {"Reference": "[RFC2230]", "Type": "KX", "Value": "36", "Meaning": "Key Exchanger", "Template": "", "Registration Date": ""}, 48 | {"Reference": "[RFC4398]", "Type": "CERT", "Value": "37", "Meaning": "CERT", "Template": "", "Registration Date": ""}, 49 | {"Reference": "[RFC3226][RFC2874][RFC6563]", "Type": "A6", "Value": "38", "Meaning": "A6 (OBSOLETE - use AAAA)", "Template": "", "Registration Date": ""}, 50 | {"Reference": "[RFC6672]", "Type": "DNAME", "Value": "39", "Meaning": "DNAME", "Template": "", "Registration Date": ""}, 51 | {"Reference": "[Donald_E_Eastlake][http://tools.ietf.org/html/draft-eastlake-kitchen-sink]", "Type": "SINK", "Value": "40", "Meaning": "SINK", "Template": "", "Registration Date": "1997-11"}, 52 | {"Reference": "[RFC6891][RFC3225]", "Type": "OPT", "Value": "41", "Meaning": "OPT", "Template": "", "Registration Date": ""}, 53 | {"Reference": "[RFC3123]", "Type": "APL", "Value": "42", "Meaning": "APL", "Template": "", "Registration Date": ""}, 54 | {"Reference": "[RFC4034][RFC3658]", "Type": "DS", "Value": "43", "Meaning": "Delegation Signer", "Template": "", "Registration Date": ""}, 55 | {"Reference": "[RFC4255]", "Type": "SSHFP", "Value": "44", "Meaning": "SSH Key Fingerprint", "Template": "", "Registration Date": ""}, 56 | {"Reference": "[RFC4025]", "Type": "IPSECKEY", "Value": "45", "Meaning": "IPSECKEY", "Template": "", "Registration Date": ""}, 57 | {"Reference": "[RFC4034][RFC3755]", "Type": "RRSIG", "Value": "46", "Meaning": "RRSIG", "Template": "", "Registration Date": ""}, 58 | {"Reference": "[RFC4034][RFC3755]", "Type": "NSEC", "Value": "47", "Meaning": "NSEC", "Template": "", "Registration Date": ""}, 59 | {"Reference": "[RFC4034][RFC3755]", "Type": "DNSKEY", "Value": "48", "Meaning": "DNSKEY", "Template": "", "Registration Date": ""}, 60 | {"Reference": "[RFC4701]", "Type": "DHCID", "Value": "49", "Meaning": "DHCID", "Template": "", "Registration Date": ""}, 61 | {"Reference": "[RFC5155]", "Type": "NSEC3", "Value": "50", "Meaning": "NSEC3", "Template": "", "Registration Date": ""}, 62 | {"Reference": "[RFC5155]", "Type": "NSEC3PARAM", "Value": "51", "Meaning": "NSEC3PARAM", "Template": "", "Registration Date": ""}, 63 | {"Reference": "[RFC6698]", "Type": "TLSA", "Value": "52", "Meaning": "TLSA", "Template": "", "Registration Date": ""}, 64 | {"Reference": "[RFC5205]", "Type": "HIP", "Value": "55", "Meaning": "Host Identity Protocol", "Template": "", "Registration Date": ""}, 65 | {"Reference": "[Jim_Reid]", "Type": "NINFO", "Value": "56", "Meaning": "NINFO", "Template": "NINFO/ninfo-completed-template", "Registration Date": "2008-01-21"}, 66 | {"Reference": "[Jim_Reid]", "Type": "RKEY", "Value": "57", "Meaning": "RKEY", "Template": "RKEY/rkey-completed-template", "Registration Date": "2008-01-21"}, 67 | {"Reference": "[Wouter_Wijngaards]", "Type": "TALINK", "Value": "58", "Meaning": "Trust Anchor LINK", "Template": "TALINK/talink-completed-template", "Registration Date": "2010-02-17"}, 68 | {"Reference": "[George_Barwood]", "Type": "CDS", "Value": "59", "Meaning": "Child DS", "Template": "CDS/cds-completed-template", "Registration Date": "2011-06-06"}, 69 | {"Reference": "[RFC4408]", "Type": "SPF", "Value": "99", "Meaning": "", "Template": "", "Registration Date": ""}, 70 | {"Reference": "[IANA-Reserved]", "Type": "UINFO", "Value": "100", "Meaning": "", "Template": "", "Registration Date": ""}, 71 | {"Reference": "[IANA-Reserved]", "Type": "UID", "Value": "101", "Meaning": "", "Template": "", "Registration Date": ""}, 72 | {"Reference": "[IANA-Reserved]", "Type": "GID", "Value": "102", "Meaning": "", "Template": "", "Registration Date": ""}, 73 | {"Reference": "[IANA-Reserved]", "Type": "UNSPEC", "Value": "103", "Meaning": "", "Template": "", "Registration Date": ""}, 74 | {"Reference": "[RFC6742]", "Type": "NID", "Value": "104", "Meaning": "", "Template": "ILNP/nid-completed-template", "Registration Date": ""}, 75 | {"Reference": "[RFC6742]", "Type": "L32", "Value": "105", "Meaning": "", "Template": "ILNP/l32-completed-template", "Registration Date": ""}, 76 | {"Reference": "[RFC6742]", "Type": "L64", "Value": "106", "Meaning": "", "Template": "ILNP/l64-completed-template", "Registration Date": ""}, 77 | {"Reference": "[RFC6742]", "Type": "LP", "Value": "107", "Meaning": "", "Template": "ILNP/lp-completed-template", "Registration Date": ""}, 78 | {"Reference": "[RFC7043]", "Type": "EUI48", "Value": "108", "Meaning": "an EUI-48 address", "Template": "EUI48/eui48-completed-template", "Registration Date": "2013-03-27"}, 79 | {"Reference": "[RFC7043]", "Type": "EUI64", "Value": "109", "Meaning": "an EUI-64 address", "Template": "EUI64/eui64-completed-template", "Registration Date": "2013-03-27"}, 80 | {"Reference": "[RFC2930]", "Type": "TKEY", "Value": "249", "Meaning": "Transaction Key", "Template": "", "Registration Date": ""}, 81 | {"Reference": "[RFC2845]", "Type": "TSIG", "Value": "250", "Meaning": "Transaction Signature", "Template": "", "Registration Date": ""}, 82 | {"Reference": "[RFC1995]", "Type": "IXFR", "Value": "251", "Meaning": "incremental transfer", "Template": "", "Registration Date": ""}, 83 | {"Reference": "[RFC1035][RFC5936]", "Type": "AXFR", "Value": "252", "Meaning": "transfer of an entire zone", "Template": "", "Registration Date": ""}, 84 | {"Reference": "[RFC1035]", "Type": "MAILB", "Value": "253", "Meaning": "mailbox-related RRs (MB, MG or MR)", "Template": "", "Registration Date": ""}, 85 | {"Reference": "[RFC1035]", "Type": "MAILA", "Value": "254", "Meaning": "mail agent RRs (OBSOLETE - see MX)", "Template": "", "Registration Date": ""}, 86 | {"Reference": "[RFC1035][RFC6895]", "Type": "*", "Value": "255", "Meaning": "A request for all records the server/cache has available", "Template": "", "Registration Date": ""}, 87 | {"Reference": "[Patrik_Faltstrom]", "Type": "URI", "Value": "256", "Meaning": "URI", "Template": "URI/uri-completed-template", "Registration Date": "2011-02-22"}, 88 | {"Reference": "[RFC6844]", "Type": "CAA", "Value": "257", "Meaning": "Certification Authority Restriction", "Template": "CAA/caa-completed-template", "Registration Date": "2011-04-07"}, 89 | {"Reference": "[Sam_Weiler][http://cameo.library.cmu.edu/][\n Deploying DNSSEC Without a Signed Root. Technical Report 1999-19,\nInformation Networking Institute, Carnegie Mellon University, April 2004.]", "Type": "TA", "Value": "32768", "Meaning": "DNSSEC Trust Authorities", "Template": "", "Registration Date": "2005-12-13"}, 90 | {"Reference": "[RFC4431]", "Type": "DLV", "Value": "32769", "Meaning": "DNSSEC Lookaside Validation", "Template": "", "Registration Date": ""}, 91 | {"Reference": "", "Type": "Reserved", "Value": "65535", "Meaning": "", "Template": "", "Registration Date": ""}] 92 | self.rrset_supported = ['1', '2', '5', '15', '28', '33'] 93 | self.r = redis.StrictRedis(host=redis_listen, port=redis_port, db=redis_db, decode_responses=True) 94 | self.origin = origin 95 | 96 | def _getFirstSeen(self, t1=None, t2=None): 97 | if t1 is None or t2 is None: 98 | return False 99 | rec = "s:" + t1.lower() + ":" + t2.lower() 100 | recget = self.r.get(rec) 101 | if recget is not None: 102 | return int(recget) 103 | 104 | def _getLastSeen(self, t1=None, t2=None): 105 | if t1 is None or t2 is None: 106 | return False 107 | rec = "l:" + t1.lower() + ":" + t2.lower() 108 | recget = self.r.get(rec) 109 | if recget is not None: 110 | return int(recget) 111 | 112 | def _getCount(self, t1=None, t2=None): 113 | if t1 is None or t2 is None: 114 | return False 115 | rec = "o:" + t1.lower() + ":" + t2.lower() 116 | recget = self.r.get(rec) 117 | if recget is not None: 118 | return int(recget) 119 | 120 | def getRecord(self, t=None): 121 | if t is None: 122 | return False 123 | rrfound = [] 124 | for rr in self.rrset: 125 | if (rr['Value']) is not None and rr['Value'] in self.rrset_supported: 126 | rec = "r:" + t + ":" + rr['Value'] 127 | rs = self.r.smembers(rec) 128 | if rs: 129 | for v in rs: 130 | rrval = {} 131 | rdata = v.strip() 132 | rrval['time_first'] = self._getFirstSeen(t1=t, t2=rdata) 133 | rrval['time_last'] = self._getLastSeen(t1=t, t2=rdata) 134 | if rrval['time_first'] is None: 135 | break 136 | rrval['count'] = self._getCount(t1=t, t2=rdata) 137 | rrval['rrtype'] = rr['Type'] 138 | rrval['rrname'] = t 139 | rrval['rdata'] = rdata 140 | if self.origin: 141 | rrval['origin'] = self.origin 142 | rrfound.append(rrval) 143 | return self._JsonQOF(rrfound) 144 | 145 | def getAssociatedRecords(self, rdata=None): 146 | if rdata is None: 147 | return False 148 | rec = "v:" + rdata.lower() 149 | records = [] 150 | if self.r.smembers(rec): 151 | for v in self.r.smembers(rec): 152 | records.append(v) 153 | return records 154 | 155 | def _RemDuplicate(self, d=None): 156 | if d is None: 157 | return False 158 | outd = [dict(t) for t in set([tuple(o.items()) for o in d])] 159 | return outd 160 | 161 | def _JsonQOF(self, rrfound=None, RemoveDuplicate=True): 162 | if rrfound is None: 163 | return False 164 | rrqof = "" 165 | 166 | if RemoveDuplicate: 167 | rrfound = self._RemDuplicate(d=rrfound) 168 | 169 | for rr in rrfound: 170 | rrqof = rrqof + json.dumps(rr) + "\n" 171 | return rrqof 172 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | from setuptools import setup, find_packages 4 | 5 | setup( 6 | name='qos-server', 7 | version='1.0', 8 | author='Alexandre Dulaunoy', 9 | author_email='alexandre.dulaunoy@circl.lu', 10 | maintainer='Alexandre Dulaunoy', 11 | url='https://github.com/adulau/pdns-qof-server', 12 | description='pdns-qof server is a "Common Output Format" compliant passive DNS query interface', 13 | packages=find_packages(), 14 | entry_points={'console_scripts': ['qos-server = qos_server:main']}, 15 | classifiers=[ 16 | 'License :: OSI Approved :: GNU Affero General Public License v3', 17 | 'Development Status :: 5 - Production/Stable', 18 | 'Environment :: Console', 19 | 'Intended Audience :: Science/Research', 20 | 'Programming Language :: Python :: 3', 21 | 'Topic :: Security', 22 | ], 23 | install_requires=[ 24 | 'tornado', 25 | 'redis', 26 | ] 27 | ) 28 | --------------------------------------------------------------------------------