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