Home | History | Annotate | Line # | Download | only in ans9
ans.py revision 1.1.1.5
      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": {os.getenv("DEFAULT_HMAC", "hmac-sha256"), "aaaaaaaaaaaa"},
     50             "fake": {os.getenv("DEFAULT_HMAC", "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                     try:
    125                         if len(o.server) == 0:
    126                             cookie.server = o.client
    127                     except AttributeError:  # dnspython<2.7.0 compat
    128                         if len(o.data) == 8:
    129                             cookie.data = o.data + o.data
    130                         else:
    131                             cookie.data = o.data
    132                     r.use_edns(options=[cookie])
    133     r.flags |= dns.flags.AA
    134     return r
    135 
    136 
    137 def sigterm(signum, frame):
    138     print("Shutting down now...")
    139     os.remove("ans.pid")
    140     running = False
    141     sys.exit(0)
    142 
    143 
    144 ############################################################################
    145 # Main
    146 #
    147 # Set up responder and control channel, open the pid file, and start
    148 # the main loop, listening for queries on the query channel or commands
    149 # on the control channel and acting on them.
    150 ############################################################################
    151 ip4_addr1 = "10.53.0.9"
    152 ip4_addr2 = "10.53.0.10"
    153 ip6_addr1 = "fd92:7065:b8e:ffff::9"
    154 ip6_addr2 = "fd92:7065:b8e:ffff::10"
    155 
    156 try:
    157     port = int(os.environ["PORT"])
    158 except:
    159     port = 5300
    160 
    161 query4_udp1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    162 query4_udp1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    163 query4_udp1.bind((ip4_addr1, port))
    164 query4_tcp1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    165 query4_tcp1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    166 query4_tcp1.bind((ip4_addr1, port))
    167 query4_tcp1.listen(1)
    168 query4_tcp1.settimeout(1)
    169 
    170 query4_udp2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    171 query4_udp2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    172 query4_udp2.bind((ip4_addr2, port))
    173 query4_tcp2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    174 query4_tcp2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    175 query4_tcp2.bind((ip4_addr2, port))
    176 query4_tcp2.listen(1)
    177 query4_tcp2.settimeout(1)
    178 
    179 havev6 = True
    180 query6_udp1 = None
    181 query6_udp2 = None
    182 query6_tcp1 = None
    183 query6_tcp2 = None
    184 try:
    185     query6_udp1 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
    186     query6_udp1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    187     query6_udp1.bind((ip6_addr1, port))
    188     query6_tcp1 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
    189     query6_tcp1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    190     query6_tcp1.bind((ip6_addr1, port))
    191     query6_tcp1.listen(1)
    192     query6_tcp1.settimeout(1)
    193 
    194     query6_udp2 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
    195     query6_udp2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    196     query6_udp2.bind((ip6_addr2, port))
    197     query6_tcp2 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
    198     query6_tcp2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    199     query6_tcp2.bind((ip6_addr2, port))
    200     query6_tcp2.listen(1)
    201     query6_tcp2.settimeout(1)
    202 except:
    203     if query6_udp1 != None:
    204         query6_udp1.close()
    205     if query6_tcp1 != None:
    206         query6_tcp1.close()
    207     if query6_udp2 != None:
    208         query6_udp2.close()
    209     if query6_tcp2 != None:
    210         query6_tcp2.close()
    211     havev6 = False
    212 
    213 signal.signal(signal.SIGTERM, sigterm)
    214 
    215 f = open("ans.pid", "w")
    216 pid = os.getpid()
    217 print(pid, file=f)
    218 f.close()
    219 
    220 running = True
    221 
    222 print("Using DNS version %s" % dns.version.version)
    223 print("Listening on %s port %d" % (ip4_addr1, port))
    224 print("Listening on %s port %d" % (ip4_addr2, port))
    225 if havev6:
    226     print("Listening on %s port %d" % (ip6_addr1, port))
    227     print("Listening on %s port %d" % (ip6_addr2, port))
    228 print("Ctrl-c to quit")
    229 
    230 if havev6:
    231     input = [
    232         query4_udp1,
    233         query6_udp1,
    234         query4_tcp1,
    235         query6_tcp1,
    236         query4_udp2,
    237         query6_udp2,
    238         query4_tcp2,
    239         query6_tcp2,
    240     ]
    241 else:
    242     input = [query4_udp1, query4_tcp1, query4_udp2, query4_tcp2]
    243 
    244 while running:
    245     try:
    246         inputready, outputready, exceptready = select.select(input, [], [])
    247     except select.error as e:
    248         break
    249     except socket.error as e:
    250         break
    251     except KeyboardInterrupt:
    252         break
    253 
    254     for s in inputready:
    255         ns10 = False
    256         if s == query4_udp1 or s == query6_udp1 or s == query4_udp2 or s == query6_udp2:
    257             if s == query4_udp1 or s == query6_udp1:
    258                 print(
    259                     "UDP Query received on %s"
    260                     % (ip4_addr1 if s == query4_udp1 else ip6_addr1),
    261                     end=" ",
    262                 )
    263             if s == query4_udp2 or s == query6_udp2:
    264                 print(
    265                     "UDP Query received on %s"
    266                     % (ip4_addr2 if s == query4_udp2 else ip6_addr2),
    267                     end=" ",
    268                 )
    269                 ns10 = True
    270             # Handle incoming queries
    271             msg = s.recvfrom(65535)
    272             dopass2 = False
    273             rsp = create_response(msg[0], False, True, ns10)
    274             print(dns.rcode.to_text(rsp.rcode()))
    275             s.sendto(rsp.to_wire(), msg[1])
    276             if dopass2:
    277                 print("Sending second UDP response without TSIG", end=" ")
    278                 rsp = create_response(msg[0], False, False, ns10)
    279                 s.sendto(rsp.to_wire(), msg[1])
    280                 print(dns.rcode.to_text(rsp.rcode()))
    281 
    282         if s == query4_tcp1 or s == query6_tcp1 or s == query4_tcp2 or s == query6_tcp2:
    283             try:
    284                 (cs, _) = s.accept()
    285                 if s == query4_tcp1 or s == query6_tcp1:
    286                     print(
    287                         "TCP Query received on %s"
    288                         % (ip4_addr1 if s == query4_tcp1 else ip6_addr1),
    289                         end=" ",
    290                     )
    291                 if s == query4_tcp2 or s == query6_tcp2:
    292                     print(
    293                         "TCP Query received on %s"
    294                         % (ip4_addr2 if s == query4_tcp2 else ip6_addr2),
    295                         end=" ",
    296                     )
    297                     ns10 = True
    298                 # get TCP message length
    299                 buf = cs.recv(2)
    300                 length = struct.unpack(">H", buf[:2])[0]
    301                 # grep DNS message
    302                 msg = cs.recv(length)
    303                 rsp = create_response(msg, True, True, ns10)
    304                 print(dns.rcode.to_text(rsp.rcode()))
    305                 wire = rsp.to_wire()
    306                 cs.send(struct.pack(">H", len(wire)))
    307                 cs.send(wire)
    308                 cs.close()
    309             except s.timeout:
    310                 pass
    311     if not running:
    312         break
    313