Home | History | Annotate | Line # | Download | only in ans9
ans.py revision 1.1
      1 ############################################################################
      2 # Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      3 #
      4 # This Source Code Form is subject to the terms of the Mozilla Public
      5 # License, v. 2.0. If a copy of the MPL was not distributed with this
      6 # file, you can obtain one at https://mozilla.org/MPL/2.0/.
      7 #
      8 # See the COPYRIGHT file distributed with this work for additional
      9 # information regarding copyright ownership.
     10 ############################################################################
     11 
     12 from __future__ import print_function
     13 import os
     14 import sys
     15 import signal
     16 import socket
     17 import select
     18 from datetime import datetime, timedelta
     19 import time
     20 import functools
     21 
     22 import dns
     23 import dns.edns
     24 import dns.flags
     25 import dns.message
     26 import dns.query
     27 import dns.tsig
     28 import dns.tsigkeyring
     29 import dns.version
     30 
     31 from dns.edns import *
     32 from dns.name import *
     33 from dns.rcode import *
     34 from dns.rdataclass import *
     35 from dns.rdatatype import *
     36 from dns.tsig import *
     37 
     38 # Log query to file
     39 def logquery(type, qname):
     40     with open("qlog", "a") as f:
     41         f.write("%s %s\n", type, qname)
     42 
     43 # DNS 2.0 keyring specifies the algorithm
     44 try:
     45     keyring = dns.tsigkeyring.from_text({ "foo" : {
     46                                                    "hmac-sha256",
     47                                                    "aaaaaaaaaaaa"
     48                                                   } ,
     49                                           "fake" : {
     50                                                    "hmac-sha256",
     51                                                    "aaaaaaaaaaaa"
     52                                                   }
     53                                          })
     54 except:
     55     keyring = dns.tsigkeyring.from_text({ "foo" : "aaaaaaaaaaaa",
     56                                            "fake" : "aaaaaaaaaaaa" })
     57 
     58 dopass2 = False
     59 
     60 ############################################################################
     61 #
     62 # This server will serve valid and spoofed answers. A spoofed answer will
     63 # have the address 10.53.0.10 included.
     64 #
     65 # When receiving a query over UDP:
     66 #
     67 # A query to "nocookie"/A will result in a spoofed answer with no cookie set.
     68 # A query to "tcponly"/A will result in a spoofed answer with no cookie set.
     69 # A query to "withtsig"/A will result in two responses, the first is a spoofed
     70 # answer that is TSIG signed, the second is a valid answer with a cookie set.
     71 # A query to anything else will result in a valid answer with a cookie set.
     72 #
     73 # When receiving a query over TCP:
     74 #
     75 # A query to "nocookie"/A will result in a valid answer with no cookie set.
     76 # A query to anything else will result in a valid answer with a cookie set.
     77 #
     78 ############################################################################
     79 def create_response(msg, tcp, first, ns10):
     80     global dopass2
     81     m = dns.message.from_wire(msg, keyring=keyring)
     82     qname = m.question[0].name.to_text()
     83     lqname = qname.lower()
     84     labels = lqname.split('.')
     85     rrtype = m.question[0].rdtype
     86     typename = dns.rdatatype.to_text(rrtype)
     87 
     88     with open("query.log", "a") as f:
     89         f.write("%s %s\n" % (typename, qname))
     90         print("%s %s" % (typename, qname), end=" ")
     91 
     92     r = dns.message.make_response(m)
     93     r.set_rcode(NOERROR)
     94     if rrtype == A:
     95         # exempt potential nameserver A records.
     96         if labels[0] == "ns" and ns10:
     97             r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10"))
     98         else:
     99             r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.9"))
    100         if not tcp and labels[0] == "nocookie":
    101             r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10"))
    102         if not tcp and labels[0] == "tcponly":
    103             r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10"))
    104         if first and not tcp and labels[0] == "withtsig":
    105             r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10"))
    106             dopass2 = True
    107     elif rrtype == NS:
    108         r.answer.append(dns.rrset.from_text(qname, 1, IN, NS, "."))
    109     elif rrtype == SOA:
    110         r.answer.append(dns.rrset.from_text(qname, 1, IN, SOA, ". . 0 0 0 0 0"))
    111     else:
    112         r.authority.append(dns.rrset.from_text(qname, 1, IN, SOA, ". . 0 0 0 0 0"))
    113     # Add a server cookie to the response
    114     if labels[0] != "nocookie":
    115         for o in m.options:
    116             if o.otype == 10: # Use 10 instead of COOKIE
    117                  if first and labels[0] == "withtsig" and not tcp:
    118                      r.use_tsig(keyring = keyring,
    119                                 keyname = dns.name.from_text("fake"),
    120                                 algorithm = HMAC_SHA256)
    121                  elif labels[0] != "tcponly" or tcp:
    122                      cookie = o
    123                      if len(o.data) == 8:
    124                          cookie.data = o.data + o.data
    125                      else:
    126                          cookie.data = o.data
    127                      r.use_edns(options=[cookie])
    128     r.flags |= dns.flags.AA
    129     return r
    130 
    131 def sigterm(signum, frame):
    132     print ("Shutting down now...")
    133     os.remove('ans.pid')
    134     running = False
    135     sys.exit(0)
    136 
    137 ############################################################################
    138 # Main
    139 #
    140 # Set up responder and control channel, open the pid file, and start
    141 # the main loop, listening for queries on the query channel or commands
    142 # on the control channel and acting on them.
    143 ############################################################################
    144 ip4_addr1 = "10.53.0.9"
    145 ip4_addr2 = "10.53.0.10"
    146 ip6_addr1 = "fd92:7065:b8e:ffff::9"
    147 ip6_addr2 = "fd92:7065:b8e:ffff::10"
    148 
    149 try: port=int(os.environ['PORT'])
    150 except: port=5300
    151 
    152 query4_udp1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    153 query4_udp1.bind((ip4_addr1, port))
    154 query4_tcp1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    155 query4_tcp1.bind((ip4_addr1, port))
    156 query4_tcp1.listen(1)
    157 query4_tcp1.settimeout(1)
    158 
    159 query4_udp2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    160 query4_udp2.bind((ip4_addr2, port))
    161 query4_tcp2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    162 query4_tcp2.bind((ip4_addr2, port))
    163 query4_tcp2.listen(1)
    164 query4_tcp2.settimeout(1)
    165 
    166 havev6 = True
    167 query6_udp1 = None
    168 query6_udp2 = None
    169 query6_tcp1 = None
    170 query6_tcp2 = None
    171 try:
    172     query6_udp1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
    173     query6_udp1.bind((ip6_addr1, port))
    174     query6_tcp1 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
    175     query6_tcp1.bind((ip6_addr1, port))
    176     query6_tcp1.listen(1)
    177     query6_tcp1.settimeout(1)
    178 
    179     query6_udp2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
    180     query6_udp2.bind((ip6_addr2, port))
    181     query6_tcp2 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
    182     query6_tcp2.bind((ip6_addr2, port))
    183     query6_tcp2.listen(1)
    184     query6_tcp2.settimeout(1)
    185 except:
    186     if query6_udp1 != None:
    187         query6_udp1.close()
    188     if query6_tcp1 != None:
    189         query6_tcp1.close()
    190     if query6_udp2 != None:
    191         query6_udp2.close()
    192     if query6_tcp2 != None:
    193         query6_tcp2.close()
    194     havev6 = False
    195 
    196 signal.signal(signal.SIGTERM, sigterm)
    197 
    198 f = open('ans.pid', 'w')
    199 pid = os.getpid()
    200 print (pid, file=f)
    201 f.close()
    202 
    203 running = True
    204 
    205 print ("Using DNS version %s" % dns.version.version)
    206 print ("Listening on %s port %d" % (ip4_addr1, port))
    207 print ("Listening on %s port %d" % (ip4_addr2, port))
    208 if havev6:
    209     print ("Listening on %s port %d" % (ip6_addr1, port))
    210     print ("Listening on %s port %d" % (ip6_addr2, port))
    211 print ("Ctrl-c to quit")
    212 
    213 if havev6:
    214     input = [query4_udp1, query6_udp1, query4_tcp1, query6_tcp1,
    215              query4_udp2, query6_udp2, query4_tcp2, query6_tcp2]
    216 else:
    217     input = [query4_udp1, query4_tcp1, query4_udp2, query4_tcp2]
    218 
    219 while running:
    220     try:
    221         inputready, outputready, exceptready = select.select(input, [], [])
    222     except select.error as e:
    223         break
    224     except socket.error as e:
    225         break
    226     except KeyboardInterrupt:
    227         break
    228 
    229     for s in inputready:
    230         ns10 = False
    231         if s == query4_udp1 or s == query6_udp1 or \
    232            s == query4_udp2 or s == query6_udp2:
    233             if s == query4_udp1 or s == query6_udp1:
    234                 print ("UDP Query received on %s" %
    235                        (ip4_addr1 if s == query4_udp1 else ip6_addr1), end=" ")
    236             if s == query4_udp2 or s == query6_udp2:
    237                 print ("UDP Query received on %s" %
    238                        (ip4_addr2 if s == query4_udp2 else ip6_addr2), end=" ")
    239                 ns10 = True
    240             # Handle incoming queries
    241             msg = s.recvfrom(65535)
    242             dopass2 = False
    243             rsp = create_response(msg[0], False, True, ns10)
    244             print(dns.rcode.to_text(rsp.rcode()))
    245             s.sendto(rsp.to_wire(), msg[1])
    246             if dopass2:
    247                 print ("Sending second UDP response without TSIG", end=" ")
    248                 rsp = create_response(msg[0], False, False, ns10)
    249                 s.sendto(rsp.to_wire(), msg[1])
    250                 print(dns.rcode.to_text(rsp.rcode()))
    251 
    252         if s == query4_tcp1 or s == query6_tcp1 or \
    253            s == query4_tcp2 or s == query6_tcp2:
    254             try:
    255                 (cs, _) = s.accept()
    256                 if s == query4_tcp1 or s == query6_tcp1:
    257                     print ("TCP Query received on %s" %
    258                            (ip4_addr1 if s == query4_tcp1 else ip6_addr1), end=" ")
    259                 if s == query4_tcp2 or s == query6_tcp2:
    260                     print ("TCP Query received on %s" %
    261                            (ip4_addr2 if s == query4_tcp2 else ip6_addr2), end=" ")
    262                     ns10 = True
    263                 # get TCP message length
    264                 buf = cs.recv(2)
    265                 length = struct.unpack('>H', buf[:2])[0]
    266                 # grep DNS message
    267                 msg = cs.recv(length)
    268                 rsp = create_response(msg, True, True, ns10)
    269                 print(dns.rcode.to_text(rsp.rcode()))
    270                 wire = rsp.to_wire()
    271                 cs.send(struct.pack('>H', len(wire)))
    272                 cs.send(wire)
    273                 cs.close()
    274             except s.timeout:
    275                 pass
    276     if not running:
    277         break
    278