1# 2# This file is a part of DNSViz, a tool suite for DNS/DNSSEC monitoring, 3# analysis, and visualization. 4# Created by Casey Deccio (casey@deccio.net) 5# 6# Copyright 2012-2014 Sandia Corporation. Under the terms of Contract 7# DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains 8# certain rights in this software. 9# 10# Copyright 2014-2016 VeriSign, Inc. 11# 12# Copyright 2016-2021 Casey Deccio 13# 14# DNSViz is free software; you can redistribute it and/or modify 15# it under the terms of the GNU General Public License as published by 16# the Free Software Foundation; either version 2 of the License, or 17# (at your option) any later version. 18# 19# DNSViz is distributed in the hope that it will be useful, 20# but WITHOUT ANY WARRANTY; without even the implied warranty of 21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22# GNU General Public License for more details. 23# 24# You should have received a copy of the GNU General Public License along 25# with DNSViz. If not, see <http://www.gnu.org/licenses/>. 26# 27 28from __future__ import unicode_literals 29 30import base64 31import binascii 32import copy 33import errno 34import codecs 35import datetime 36import hashlib 37import io 38import logging 39import socket 40import struct 41import time 42 43# minimal support for python2.6 44try: 45 from collections import OrderedDict 46except ImportError: 47 from ordereddict import OrderedDict 48 49# python3/python2 dual compatibility 50try: 51 from html import escape 52except ImportError: 53 from cgi import escape 54 55import dns.edns, dns.flags, dns.message, dns.rcode, dns.rdataclass, dns.rdatatype, dns.rrset 56 57from . import base32 58from . import crypto 59from . import format as fmt 60from .ipaddr import IPAddr 61from .util import tuple_to_dict 62lb2s = fmt.latin1_binary_to_string 63 64class DNSResponse: 65 '''A DNS response, including meta information''' 66 67 def __init__(self, message, msg_size, error, errno1, history, response_time, query, server_cookie, server_cookie_status, review_history=True): 68 self.message = message 69 self.msg_size = msg_size 70 self.error = error 71 self.errno = errno1 72 self.history = history 73 self.response_time = response_time 74 75 self.query = query 76 self.server_cookie = server_cookie 77 self.server_cookie_status = server_cookie_status 78 79 self.effective_flags = None 80 self.effective_edns = None 81 self.effective_edns_max_udp_payload = None 82 self.effective_edns_flags = None 83 self.effective_edns_options = None 84 self.effective_tcp = None 85 self.effective_server_cookie_status = None 86 87 self.udp_attempted = None 88 self.udp_responsive = None 89 self.tcp_attempted = None 90 self.tcp_responsive = None 91 self.responsive_cause_index = None 92 93 if review_history: 94 self._review_history() 95 96 def __str__(self): 97 from . import query as Q 98 if self.message is not None: 99 return repr(self.message) 100 else: 101 return Q.response_errors.get(self.error) 102 103 def __repr__(self): 104 return '<%s: "%s">' % (self.__class__.__name__, str(self)) 105 106 @classmethod 107 def _query_tag_bind(cls, tcp, flags, edns, edns_flags, edns_max_udp_payload, edns_options, qname): 108 s = [] 109 if flags & dns.flags.RD: 110 s.append('+') 111 else: 112 s.append('-') 113 if edns >= 0: 114 s.append('E(%d)' % (edns)) 115 if tcp: 116 s.append('T') 117 if edns >= 0 and edns_flags & dns.flags.DO: 118 s.append('D') 119 if flags & dns.flags.CD: 120 s.append('C') 121 # Flags other than the ones commonly seen in queries 122 if flags & dns.flags.AD: 123 s.append('A') 124 if flags & dns.flags.AA: 125 s.append('a') 126 if flags & dns.flags.TC: 127 s.append('t') 128 if flags & dns.flags.RA: 129 s.append('r') 130 if edns >= 0: 131 # EDNS max UDP payload 132 s.append('P(%d)' % edns_max_udp_payload) 133 # EDNS flags other than DO 134 if edns_flags & ~dns.flags.DO: 135 s.append('F(0x%x)' % edns_flags) 136 # other options 137 for opt in edns_options: 138 if opt.otype == 3: 139 # NSID 140 s.append('N') 141 elif opt.otype == 8: 142 # EDNS Client Subnet 143 s.append('s') 144 elif opt.otype == 10: 145 # DNS cookies 146 s.append('K') 147 if qname.to_text() != qname.to_text().lower(): 148 s.append('X') 149 return s 150 151 @classmethod 152 def _query_tag_human(cls, tcp, flags, edns, edns_flags, edns_max_udp_payload, edns_options, qname): 153 s = '' 154 if tcp: 155 s += 'TCP_' 156 else: 157 s += 'UDP_' 158 159 if flags & dns.flags.RD: 160 s += '+' 161 else: 162 s += '-' 163 if flags & dns.flags.CD: 164 s += 'C' 165 # Flags other than the ones commonly seen in queries 166 if flags & dns.flags.AD: 167 s += 'A' 168 if flags & dns.flags.AA: 169 s += 'a' 170 if flags & dns.flags.TC: 171 s += 't' 172 if flags & dns.flags.RA: 173 s += 'r' 174 s += '_' 175 176 if edns < 0: 177 s += 'NOEDNS_' 178 else: 179 s += 'EDNS%d_' % (edns) 180 181 # EDNS max UDP payload 182 s += '%d_' % edns_max_udp_payload 183 184 if edns_flags & dns.flags.DO: 185 s += 'D' 186 # EDNS flags other than DO 187 if edns_flags & ~dns.flags.DO: 188 s += '%d' % edns_flags 189 190 if edns_options: 191 s += '_' 192 193 # other options 194 for opt in edns_options: 195 if opt.otype == 3: 196 # NSID 197 s += 'N' 198 elif opt.otype == 8: 199 # EDNS Client Subnet 200 s += 's' 201 elif opt.otype == 10: 202 # DNS cookies 203 s += 'K' 204 else: 205 # DNS cookies 206 s += 'O(%d)' % opt.otype 207 208 if qname.to_text() != qname.to_text().lower(): 209 s += '_0x20' 210 return s 211 212 def nsid_val(self): 213 if self.message is None: 214 return None 215 216 if self.message.edns < 0: 217 return None 218 219 try: 220 nsid_opt = [o for o in self.message.options if o.otype == dns.edns.NSID][0] 221 except IndexError: 222 return None 223 224 try: 225 nsid_val = nsid_opt.data.decode('ascii') 226 except UnicodeDecodeError: 227 nsid_val = '0x' + lb2s(binascii.hexlify(nsid_opt.data)) 228 return nsid_val 229 230 def request_cookie_tag(self): 231 from . import query as Q 232 233 if self.effective_server_cookie_status == Q.DNS_COOKIE_NO_COOKIE: 234 return 'NO_COOKIE' 235 elif self.effective_server_cookie_status == Q.DNS_COOKIE_IMPROPER_LENGTH: 236 return 'MALFORMED_COOKIE' 237 elif self.effective_server_cookie_status == Q.DNS_COOKIE_CLIENT_COOKIE_ONLY: 238 return 'CLIENT_COOKIE_ONLY' 239 elif self.effective_server_cookie_status == Q.DNS_COOKIE_SERVER_COOKIE_FRESH: 240 return 'VALID_SERVER_COOKIE' 241 elif self.effective_server_cookie_status == Q.DNS_COOKIE_SERVER_COOKIE_BAD: 242 return 'INVALID_SERVER_COOKIE' 243 else: 244 raise Exception('Unknown cookie status!') 245 246 def response_cookie_tag(self): 247 248 if self.message is None: 249 return 'ERROR' 250 251 if self.message.edns < 0: 252 return 'NO_EDNS' 253 254 try: 255 cookie_opt = [o for o in self.message.options if o.otype == 10][0] 256 except IndexError: 257 return 'NO_COOKIE_OPT' 258 259 if len(cookie_opt.data) < 8 or len(cookie_opt.data) > 40: 260 return 'MALFORMED_COOKIE' 261 262 elif len(cookie_opt.data) == 8: 263 return 'CLIENT_COOKIE_ONLY' 264 265 else: 266 return 'CLIENT_AND_SERVER_COOKIE' 267 268 def initial_query_tag(self): 269 return ''.join(self._query_tag_human(self.query.tcp, self.query.flags, self.query.edns, self.query.edns_flags, self.query.edns_max_udp_payload, self.query.edns_options, self.query.qname)) 270 271 def effective_query_tag(self): 272 return ''.join(self._query_tag_human(self.effective_tcp, self.effective_flags, self.effective_edns, self.effective_edns_flags, self.query.edns_max_udp_payload, self.effective_edns_options, self.query.qname)) 273 274 def section_rr_count(self, section): 275 if self.message is None: 276 return None 277 n = 0 278 for i in section: 279 n += len(i) 280 if section is self.message.additional and self.message.edns >= 0: 281 n += 1 282 return n 283 284 def section_digest(self, section): 285 if self.message is None: 286 return None 287 d = '' 288 rrsets = section[:] 289 rrsets.sort() 290 for rrset in rrsets: 291 d += RRsetInfo.rrset_canonicalized_to_wire(rrset, rrset.name, rrset.ttl) 292 return 'md5'+hashlib.md5(d).hexdigest() 293 294 def retries(self): 295 return len(self.history) 296 297 def total_response_time(self): 298 t = self.response_time 299 for retry in self.history: 300 t += retry.response_time 301 return t 302 303 def get_cookie_opt(self): 304 if self.message is None: 305 return None 306 try: 307 return [o for o in self.message.options if o.otype == 10][0] 308 except IndexError: 309 return None 310 311 def get_server_cookie(self): 312 cookie_opt = self.get_cookie_opt() 313 if cookie_opt is not None and len(cookie_opt.data) > 8: 314 return cookie_opt.data[8:] 315 return None 316 317 def copy(self): 318 clone = DNSResponse(self.message, self.msg_size, self.error, self.errno, self.history, self.response_time, self.query, self.server_cookie, self.server_cookie_status, review_history=False) 319 clone.set_effective_request_options(self.effective_flags, self.effective_edns, self.effective_edns_max_udp_payload, self.effective_edns_flags, self.effective_edns_options, self.effective_tcp, self.effective_server_cookie_status) 320 clone.set_responsiveness(self.udp_attempted, self.udp_responsive, self.tcp_attempted, self.tcp_responsive, self.responsive_cause_index, self.responsive_cause_index_tcp) 321 return clone 322 323 def set_effective_request_options(self, flags, edns, edns_max_udp_payload, edns_flags, edns_options, tcp, server_cookie_status): 324 self.effective_flags = flags 325 self.effective_edns = edns 326 self.effective_edns_max_udp_payload = edns_max_udp_payload 327 self.effective_edns_flags = edns_flags 328 self.effective_edns_options = edns_options 329 self.effective_tcp = tcp 330 self.effective_server_cookie_status = server_cookie_status 331 332 def set_responsiveness(self, udp_attempted, udp_responsive, tcp_attempted, tcp_responsive, responsive_cause_index, responsive_cause_index_tcp): 333 self.udp_attempted = udp_attempted 334 self.udp_responsive = udp_responsive 335 self.tcp_attempted = tcp_attempted 336 self.tcp_responsive = tcp_responsive 337 self.responsive_cause_index = responsive_cause_index 338 self.responsive_cause_index_tcp = responsive_cause_index_tcp 339 340 def _review_history(self): 341 from . import query as Q 342 343 flags = self.query.flags 344 edns = self.query.edns 345 edns_max_udp_payload = self.query.edns_max_udp_payload 346 edns_flags = self.query.edns_flags 347 edns_options = copy.deepcopy(self.query.edns_options) 348 server_cookie_status = self.server_cookie_status 349 350 # mark whether TCP or UDP was attempted initially 351 tcp_attempted = tcp = self.query.tcp 352 udp_attempted = not tcp 353 354 tcp_responsive = False 355 udp_responsive = False 356 tcp_valid = False 357 udp_valid = False 358 359 #TODO - there could be room for both a responsiveness check and a valid 360 # check here, rather than just a valid check 361 362 responsive_cause_index = None 363 responsive_cause_index_tcp = tcp 364 365 prev_index = None 366 for i, retry in enumerate(self.history): 367 # mark if TCP or UDP was attempted prior to this retry 368 if tcp: 369 tcp_attempted = True 370 else: 371 udp_attempted = True 372 373 # Mark responsiveness if this retry wasn't caused by network error 374 # or timeout. 375 if retry.cause not in (Q.RETRY_CAUSE_NETWORK_ERROR, Q.RETRY_CAUSE_TIMEOUT): 376 if tcp: 377 tcp_responsive = True 378 else: 379 udp_responsive = True 380 381 # If the last cause/action resulted in a valid response where there 382 # wasn't previously on the same protocol, then mark the 383 # cause/action. 384 if retry.cause in (Q.RETRY_CAUSE_TC_SET, Q.RETRY_CAUSE_DIAGNOSTIC): 385 if tcp: 386 if responsive_cause_index is None and \ 387 not tcp_valid and prev_index is not None and self.history[prev_index].action != Q.RETRY_ACTION_USE_TCP: 388 responsive_cause_index = prev_index 389 responsive_cause_index_tcp = tcp 390 tcp_valid = True 391 else: 392 if responsive_cause_index is None and \ 393 not udp_valid and prev_index is not None and self.history[prev_index].action != Q.RETRY_ACTION_USE_UDP: 394 responsive_cause_index = prev_index 395 responsive_cause_index_tcp = tcp 396 udp_valid = True 397 398 if retry.action == Q.RETRY_ACTION_NO_CHANGE: 399 pass 400 elif retry.action == Q.RETRY_ACTION_USE_TCP: 401 tcp = True 402 elif retry.action == Q.RETRY_ACTION_USE_UDP: 403 tcp = False 404 elif retry.action == Q.RETRY_ACTION_SET_FLAG: 405 flags |= retry.action_arg 406 elif retry.action == Q.RETRY_ACTION_CLEAR_FLAG: 407 flags &= ~retry.action_arg 408 elif retry.action == Q.RETRY_ACTION_DISABLE_EDNS: 409 edns = -1 410 elif retry.action == Q.RETRY_ACTION_CHANGE_UDP_MAX_PAYLOAD: 411 edns_max_udp_payload = retry.action_arg 412 tcp = False 413 elif retry.action == Q.RETRY_ACTION_SET_EDNS_FLAG: 414 edns_flags |= retry.action_arg 415 elif retry.action == Q.RETRY_ACTION_CLEAR_EDNS_FLAG: 416 edns_flags &= ~retry.action_arg 417 elif retry.action == Q.RETRY_ACTION_ADD_EDNS_OPTION: 418 #TODO option data 419 edns_options.append(dns.edns.GenericOption(retry.action_arg, b'')) 420 elif retry.action == Q.RETRY_ACTION_REMOVE_EDNS_OPTION: 421 filtered_options = [x for x in edns_options if retry.action_arg == x.otype] 422 if filtered_options: 423 edns_options.remove(filtered_options[0]) 424 # If COOKIE option was removed, then reset 425 # server_cookie_status 426 if filtered_options[0].otype == 10: 427 server_cookie_status = Q.DNS_COOKIE_NO_COOKIE 428 elif retry.action == Q.RETRY_ACTION_CHANGE_SPORT: 429 pass 430 elif retry.action == Q.RETRY_ACTION_CHANGE_EDNS_VERSION: 431 edns = retry.action_arg 432 elif retry.action == Q.RETRY_ACTION_UPDATE_DNS_COOKIE: 433 server_cookie_status = Q.DNS_COOKIE_SERVER_COOKIE_FRESH 434 435 prev_index = i 436 437 # Mark responsiveness if the ultimate query didn't result in network 438 # error or timeout. 439 if self.error not in (Q.RESPONSE_ERROR_NETWORK_ERROR, Q.RESPONSE_ERROR_TIMEOUT): 440 if tcp: 441 tcp_responsive = True 442 else: 443 udp_responsive = True 444 445 # If the last cause/action resulted in a valid response where there 446 # wasn't previously on the same protocol, then mark the cause/action. 447 if self.is_valid_response(): 448 if tcp: 449 if responsive_cause_index is None and \ 450 not tcp_valid and prev_index is not None and self.history[prev_index].action != Q.RETRY_ACTION_USE_TCP: 451 responsive_cause_index = prev_index 452 responsive_cause_index_tcp = tcp 453 else: 454 if responsive_cause_index is None and \ 455 not udp_valid and prev_index is not None and self.history[prev_index].action != Q.RETRY_ACTION_USE_UDP: 456 responsive_cause_index = prev_index 457 responsive_cause_index_tcp = tcp 458 459 # If EDNS was effectively disabled, reset EDNS options 460 if edns < 0: 461 edns_max_udp_payload = None 462 edns_flags = 0 463 edns_options = [] 464 server_cookie_status = Q.DNS_COOKIE_NO_COOKIE 465 466 self.set_effective_request_options(flags, edns, edns_max_udp_payload, edns_flags, edns_options, tcp, server_cookie_status) 467 self.set_responsiveness(udp_attempted, udp_responsive, tcp_attempted, tcp_responsive, responsive_cause_index, responsive_cause_index_tcp) 468 469 def recursion_desired(self): 470 '''Return True if the recursion desired (RD) bit was set in the request to the 471 server.''' 472 473 return self.is_valid_response() and self.is_complete_response() and \ 474 bool(self.effective_flags & dns.flags.RD) 475 476 def recursion_available(self): 477 '''Return True if the server indicated that recursion was available.''' 478 479 return self.is_valid_response() and self.is_complete_response() and \ 480 bool(self.message.flags & dns.flags.RA) 481 482 def recursion_desired_and_available(self): 483 '''Return True if the recursion desired (RD) bit was set in the request to the 484 server AND the server indicated that recursion was available.''' 485 486 return self.is_valid_response() and self.is_complete_response() and \ 487 bool(self.effective_flags & dns.flags.RD) and \ 488 bool(self.message.flags & dns.flags.RA) 489 490 def dnssec_requested(self): 491 '''Return True if the DNSSEC OK (DO) bit was set in the request to the 492 server.''' 493 494 return self.effective_edns >= 0 and self.effective_edns_flags & dns.flags.DO 495 496 def is_valid_response(self): 497 '''Return True if the message has a sane error code, namely NOERROR or 498 NXDOMAIN.''' 499 500 return self.message is not None and self.message.rcode() in (dns.rcode.NOERROR, dns.rcode.NXDOMAIN) 501 502 def is_complete_response(self): 503 '''Return True if the message does not have the truncation (TC) bit 504 set.''' 505 506 return self.message is not None and not bool(self.message.flags & dns.flags.TC) 507 508 def is_authoritative(self): 509 '''Return True if the message has the authoritative answer (AA) bit 510 set.''' 511 512 return self.message is not None and bool(self.message.flags & dns.flags.AA) 513 514 def is_referral(self, qname, rdtype, rdclass, bailiwick, proper=False): 515 '''Return True if this response yields a referral for the queried 516 name.''' 517 518 if not (self.is_valid_response() and self.is_complete_response()): 519 return False 520 # if no bailiwick is specified, then we cannot classify it as a 521 # referral 522 if bailiwick is None: 523 return False 524 # if the qname is not a proper subdomain of the bailiwick, then it 525 # is not a referral 526 if not (qname != bailiwick and qname.is_subdomain(bailiwick)): 527 return False 528 # if the name exists in the answer section with the requested rdtype or 529 # CNAME, then it can't be a referral 530 if [x for x in self.message.answer if x.name == qname and x.rdtype in (rdtype, dns.rdatatype.CNAME) and x.rdclass == rdclass]: 531 return False 532 # if an SOA record with the given qname exists, then the server 533 # is authoritative for the name, so it is a referral 534 try: 535 self.message.find_rrset(self.message.authority, qname, rdclass, dns.rdatatype.SOA) 536 return False 537 except KeyError: 538 pass 539 # if proper referral is requested and qname is equal to of an NS RRset 540 # in the authority, then it is a referral 541 if proper: 542 if [x for x in self.message.authority if qname == x.name and x.rdtype == dns.rdatatype.NS and x.rdclass == rdclass]: 543 return True 544 # if proper referral is NOT requested, qname is a subdomain of 545 # (including equal to) an NS RRset in the authority, and qname is not 546 # equal to bailiwick, then it is a referral 547 else: 548 if [x for x in self.message.authority if qname.is_subdomain(x.name) and bailiwick != x.name and x.rdtype == dns.rdatatype.NS and x.rdclass == rdclass]: 549 return True 550 return False 551 552 def is_upward_referral(self, qname): 553 '''Return True if this response yields an upward referral (i.e., a name 554 that is a superdomain of qname).''' 555 556 if not (self.is_valid_response() and self.is_complete_response()): 557 return False 558 return bool(not self.is_authoritative() and \ 559 [x for x in self.message.authority if x.name != qname and qname.is_subdomain(x.name)]) 560 561 def is_answer(self, qname, rdtype, include_cname=True): 562 '''Return True if this response yields an answer for the queried name 563 and type in the answer section. If include_cname is False, then only 564 non-CNAME records count.''' 565 566 if not (self.is_valid_response() and self.is_complete_response()): 567 return False 568 if rdtype == dns.rdatatype.ANY and [x for x in self.message.answer if x.name == qname]: 569 return True 570 rdtypes = [rdtype] 571 if include_cname: 572 rdtypes.append(dns.rdatatype.CNAME) 573 if [x for x in self.message.answer if x.name == qname and x.rdtype in rdtypes]: 574 return True 575 return False 576 577 def is_nxdomain(self, qname, rdtype): 578 '''Return True if this response indicates that the queried name does 579 not exist (i.e., is NXDOMAIN).''' 580 581 if not (self.is_valid_response() and self.is_complete_response()): 582 return False 583 584 if [x for x in self.message.answer if x.name == qname and x.rdtype in (rdtype, dns.rdatatype.CNAME)]: 585 return False 586 587 if self.message.rcode() == dns.rcode.NXDOMAIN: 588 return True 589 590 return False 591 592 def is_delegation(self, qname, rdtype): 593 '''Return True if this response (from a request to a server 594 authoritative for the immediate parent) yields NS records for the name 595 or provides a referral or NXDOMAIN or no data response.''' 596 597 # if NS or SOA records were found in the answer or authority section 598 return self.message.get_rrset(self.message.answer, qname, dns.rdataclass.IN, dns.rdatatype.NS) is not None or \ 599 self.message.get_rrset(self.message.authority, qname, dns.rdataclass.IN, dns.rdatatype.NS) is not None or \ 600 self.message.get_rrset(self.message.authority, qname, dns.rdataclass.IN, dns.rdatatype.SOA) is not None 601 602 def not_delegation(self, qname, rdtype): 603 return not self.is_delegation(qname, rdtype) 604 605 def ns_ip_mapping_from_additional(self, qname, bailiwick=None): 606 ip_mapping = {} 607 608 if not (self.is_valid_response() and self.is_complete_response()): 609 return ip_mapping 610 611 try: 612 ns_rrset = self.message.find_rrset(self.message.answer, qname, dns.rdataclass.IN, dns.rdatatype.NS) 613 except KeyError: 614 try: 615 ns_rrset = self.message.find_rrset(self.message.authority, qname, dns.rdataclass.IN, dns.rdatatype.NS) 616 except KeyError: 617 return ip_mapping 618 619 # iterate over each RR in the RR RRset 620 for ns_rr in ns_rrset: 621 ip_mapping[ns_rr.target] = set() 622 623 if bailiwick is not None and not ns_rr.target.is_subdomain(bailiwick): 624 continue 625 626 for rdtype in (dns.rdatatype.A, dns.rdatatype.AAAA): 627 try: 628 a_rrset = self.message.find_rrset(self.message.additional, ns_rr.target, dns.rdataclass.IN, rdtype) 629 except KeyError: 630 continue 631 632 ip_mapping[ns_rr.target].update([IPAddr(a_rr.to_text()) for a_rr in a_rrset]) 633 634 return ip_mapping 635 636 def serialize_meta(self): 637 from . import query as Q 638 639 d = OrderedDict() 640 641 # populate history, if not already populated 642 if self.effective_flags is None: 643 self._review_history() 644 645 if self.message is None: 646 d['error'] = Q.response_errors[self.error] 647 if self.errno is not None: 648 errno_name = errno.errorcode.get(self.errno, None) 649 if errno_name is not None: 650 d['errno'] = errno_name 651 else: 652 d['rcode'] = dns.rcode.to_text(self.message.rcode()) 653 if self.message.edns >= 0: 654 d['edns_version'] = self.message.edns 655 d['answer'] = OrderedDict(( 656 ('count', self.section_rr_count(self.message.answer)), 657 ('digest', self.section_digest(self.message.answer)), 658 )) 659 d['authority'] = OrderedDict(( 660 ('count', self.section_rr_count(self.message.authority)), 661 ('digest', self.section_digest(self.message.authority)), 662 )) 663 d['additional'] = OrderedDict(( 664 ('count', self.section_rr_count(self.message.additional)), 665 ('digest', self.section_digest(self.message.additional)), 666 )) 667 if not d['answer']['count']: 668 del d['answer']['digest'] 669 if not d['authority']['count']: 670 del d['authority']['digest'] 671 if not d['additional']['count']: 672 del d['additional']['digest'] 673 674 if self.msg_size is not None: 675 d['msg_size'] = self.msg_size 676 d['time_elapsed'] = int(self.response_time * 1000) 677 d['retries'] = self.retries() 678 if self.history: 679 d['cumulative_response_time'] = int(self.total_response_time() * 1000) 680 d['effective_query_options'] = OrderedDict(( 681 ('flags', self.effective_flags), 682 ('edns_version', self.effective_edns), 683 ('edns_max_udp_payload', self.effective_edns_max_udp_payload), 684 ('edns_flags', self.effective_edns_flags), 685 ('edns_options', []), 686 )) 687 for o in self.effective_edns_options: 688 s = io.BytesIO() 689 o.to_wire(s) 690 d['effective_query_options']['edns_options'].append((o.type, binascii.hexlify(s.getvalue()))) 691 d['effective_query_options']['tcp'] = self.effective_tcp 692 693 if self.responsive_cause_index is not None: 694 d['responsiveness_impediment'] = OrderedDict(( 695 ('cause', Q.retry_causes[self.history[self.responsive_cause_index].cause]), 696 ('action', Q.retry_actions[self.history[self.responsive_cause_index].action]) 697 )) 698 699 return d 700 701 def serialize(self): 702 from . import query as Q 703 704 d = OrderedDict() 705 if self.message is None: 706 d['message'] = None 707 d['error'] = Q.response_errors[self.error] 708 if self.errno is not None: 709 errno_name = errno.errorcode.get(self.errno, None) 710 if errno_name is not None: 711 d['errno'] = errno_name 712 else: 713 d['message'] = lb2s(base64.b64encode(self.message.to_wire())) 714 if self.msg_size is not None: 715 d['msg_size'] = self.msg_size 716 d['time_elapsed'] = int(self.response_time * 1000) 717 d['history'] = [] 718 for retry in self.history: 719 d['history'].append(retry.serialize()) 720 return d 721 722 @classmethod 723 def deserialize(cls, d, query, server_cookie, server_cookie_status): 724 from . import query as Q 725 726 if 'msg_size' in d: 727 msg_size = int(d['msg_size']) 728 else: 729 msg_size = None 730 if 'error' in d: 731 error = Q.response_error_codes[d['error']] 732 else: 733 error = None 734 if 'errno' in d: 735 # compatibility with version 1.0 736 if isinstance(d['errno'], int): 737 errno1 = d['errno'] 738 else: 739 if hasattr(errno, d['errno']): 740 errno1 = getattr(errno, d['errno']) 741 else: 742 errno1 = None 743 else: 744 errno1 = None 745 746 if d['message'] is None: 747 message = None 748 else: 749 wire = base64.b64decode(d['message']) 750 try: 751 message = dns.message.from_wire(wire) 752 except Exception as e: 753 message = None 754 if isinstance(e, (struct.error, dns.exception.FormError)): 755 error = Q.RESPONSE_ERROR_FORMERR 756 #XXX need to determine how to handle non-parsing 757 # validation errors with dnspython (e.g., signature with 758 # no keyring) 759 else: 760 error = Q.RESPONSE_ERROR_OTHER 761 762 # compatibility with version 1.0 763 if 'response_time' in d: 764 response_time = d['response_time'] 765 else: 766 response_time = d['time_elapsed']/1000.0 767 history = [] 768 for retry in d['history']: 769 history.append(Q.DNSQueryRetryAttempt.deserialize(retry)) 770 return DNSResponse(message, msg_size, error, errno1, history, response_time, query, server_cookie, server_cookie_status) 771 772class DNSResponseComponent(object): 773 def __init__(self): 774 self.servers_clients = {} 775 776 def add_server_client(self, server, client, response): 777 if (server, client) not in self.servers_clients: 778 self.servers_clients[(server, client)] = [] 779 self.servers_clients[(server, client)].append(response) 780 781 @classmethod 782 def insert_into_list(cls, component_info, component_info_list, server, client, response): 783 try: 784 index = component_info_list.index(component_info) 785 component_info = component_info_list[index] 786 except ValueError: 787 component_info_list.append(component_info) 788 component_info.add_server_client(server, client, response) 789 return component_info 790 791class RDataMeta(DNSResponseComponent): 792 def __init__(self, name, ttl, rdtype, rdata): 793 super(RDataMeta, self).__init__() 794 self.name = name 795 self.ttl = ttl 796 self.rdtype = rdtype 797 self.rdata = rdata 798 self.rrset_info = set() 799 800class DNSKEYMeta(DNSResponseComponent): 801 def __init__(self, name, rdata, ttl): 802 super(DNSKEYMeta, self).__init__() 803 self.name = name 804 self.rdata = rdata 805 self.ttl = ttl 806 self.warnings = [] 807 self.errors = [] 808 self.rrset_info = [] 809 810 self.key_tag = self.calc_key_tag(rdata) 811 self.key_tag_no_revoke = self.calc_key_tag(rdata, True) 812 self.key_len = self.calc_key_len(rdata) 813 814 def __str__(self): 815 return 'DNSKEY for %s (algorithm %d (%s), key tag %d)' % (fmt.humanize_name(self.name), self.rdata.algorithm, fmt.DNSKEY_ALGORITHMS.get(self.rdata.algorithm, self.rdata.algorithm), self.key_tag) 816 817 @classmethod 818 def calc_key_tag(cls, rdata, clear_revoke=False): 819 '''Return the key_tag for the key, as specified in RFC 4034. If 820 clear_revoke is True, then clear the revoke flag of the DNSKEY RR 821 first.''' 822 823 # python3/python2 dual compatibility 824 if isinstance(rdata.key, bytes): 825 if isinstance(rdata.key, str): 826 map_func = lambda x, y: ord(x[y]) 827 else: 828 map_func = lambda x, y: x[y] 829 else: 830 map_func = lambda x, y: struct.unpack(b'B',x[y])[0] 831 832 # algorithm 1 is a special case 833 if rdata.algorithm == 1: 834 b1 = map_func(rdata.key, -3) 835 b2 = map_func(rdata.key, -2) 836 return (b1 << 8) | b2 837 838 if clear_revoke: 839 flags = rdata.flags & (~fmt.DNSKEY_FLAGS['revoke']) 840 else: 841 flags = rdata.flags 842 843 key_str = struct.pack(b'!HBB', flags, rdata.protocol, rdata.algorithm) + rdata.key 844 845 ac = 0 846 for i in range(len(key_str)): 847 b = map_func(key_str, i) 848 if i & 1: 849 ac += b 850 else: 851 ac += (b << 8) 852 853 ac += (ac >> 16) & 0xffff 854 return ac & 0xffff 855 856 @classmethod 857 def calc_key_len(cls, rdata): 858 '''Return the length of the key modulus, in bits.''' 859 860 key_str = rdata.key 861 862 # python3/python2 dual compatibility 863 if isinstance(rdata.key, bytes): 864 if isinstance(rdata.key, str): 865 map_func = lambda x, y: ord(x[y]) 866 else: 867 map_func = lambda x, y: x[y] 868 else: 869 map_func = lambda x, y: struct.unpack(b'B',x[y])[0] 870 871 # RSA keys 872 if rdata.algorithm in (1,5,7,8,10): 873 try: 874 # get the exponent length 875 e_len = map_func(key_str, 0) 876 except IndexError: 877 return 0 878 879 offset = 1 880 if e_len == 0: 881 b1 = map_func(key_str, 1) 882 b2 = map_func(key_str, 2) 883 e_len = (b1 << 8) | b2 884 offset = 3 885 886 # get the exponent 887 offset += e_len 888 889 # get the modulus 890 key_len = len(key_str) - offset 891 892 # if something went wrong here, use key length of rdata key 893 if key_len <= 0: 894 return len(key_str)<<3 895 896 return key_len << 3 897 898 # DSA keys 899 elif rdata.algorithm in (3,6): 900 t = map_func(key_str, 0) 901 return (64 + t*8)<<3 902 903 # GOST keys 904 elif rdata.algorithm in (12,): 905 return len(key_str)<<3 906 907 # EC keys 908 elif rdata.algorithm in (13,14): 909 return len(key_str)<<3 910 911 # EDDSA keys 912 elif rdata.algorithm in (15,16): 913 return len(key_str)<<3 914 915 # other keys - just guess, based on the length of the raw key material 916 else: 917 return len(key_str)<<3 918 919 def message_for_ds(self, clear_revoke=False): 920 '''Return the string value suitable for hashing to create a DS 921 record.''' 922 923 if clear_revoke: 924 flags = self.rdata.flags & (~fmt.DNSKEY_FLAGS['revoke']) 925 else: 926 flags = self.rdata.flags 927 928 name_wire = self.name.canonicalize().to_wire() 929 930 # write DNSKEY rdata in wire format 931 rdata_wire = struct.pack(b'!HBB', flags, self.rdata.protocol, self.rdata.algorithm) 932 933 return name_wire + rdata_wire + self.rdata.key 934 935 def serialize(self, consolidate_clients=True, show_servers=True, loglevel=logging.DEBUG, html_format=False, map_ip_to_ns_name=None): 936 from .analysis import status as Status 937 938 show_id = loglevel <= logging.INFO or \ 939 (self.warnings and loglevel <= logging.WARNING) or \ 940 (self.errors and loglevel <= logging.ERROR) 941 942 d = OrderedDict() 943 944 if html_format: 945 formatter = lambda x: escape(x, True) 946 else: 947 formatter = lambda x: x 948 949 if show_id: 950 d['id'] = '%d/%d' % (self.rdata.algorithm, self.key_tag) 951 if loglevel <= logging.DEBUG: 952 d['description'] = formatter(str(self)) 953 d['flags'] = self.rdata.flags 954 d['protocol'] = self.rdata.protocol 955 d['algorithm'] = self.rdata.algorithm 956 d['key'] = lb2s(base64.b64encode(self.rdata.key)) 957 d['ttl'] = self.ttl 958 d['key_length'] = self.key_len 959 d['key_tag'] = self.key_tag 960 if self.rdata.flags & fmt.DNSKEY_FLAGS['revoke']: 961 d['key_tag_pre_revoke'] = self.key_tag_no_revoke 962 963 if html_format: 964 flags = [t for (t,c) in fmt.DNSKEY_FLAGS.items() if c & self.rdata.flags] 965 d['flags'] = '%d (%s)' % (self.rdata.flags, ', '.join(flags)) 966 d['protocol'] = '%d (%s)' % (self.rdata.protocol, fmt.DNSKEY_PROTOCOLS.get(self.rdata.protocol, self.rdata.protocol)) 967 d['algorithm'] = '%d (%s)' % (self.rdata.algorithm, fmt.DNSKEY_ALGORITHMS.get(self.rdata.algorithm, self.rdata.algorithm)) 968 d['ttl'] = '%d (%s)' % (self.ttl, fmt.humanize_time(self.ttl)) 969 if self.key_len is None: 970 d['key_length'] = 'unknown' 971 else: 972 d['key_length'] = '%d bits' % (self.key_len) 973 974 #TODO: put DNSKEY roles in meta, if it makes sense 975 976 if loglevel <= logging.INFO: 977 servers = tuple_to_dict(self.servers_clients) 978 if consolidate_clients: 979 servers = list(servers) 980 servers.sort() 981 d['servers'] = servers 982 983 if map_ip_to_ns_name is not None: 984 ns_names = list(set([lb2s(map_ip_to_ns_name(s)[0][0].canonicalize().to_text()) for s in servers])) 985 ns_names.sort() 986 d['ns_names'] = ns_names 987 988 tags = set() 989 nsids = set() 990 for server,client in self.servers_clients: 991 for response in self.servers_clients[(server,client)]: 992 tags.add(response.effective_query_tag()) 993 nsid = response.nsid_val() 994 if nsid is not None: 995 nsids.add(nsid) 996 997 if nsids: 998 d['nsid_values'] = list(nsids) 999 d['nsid_values'].sort() 1000 1001 d['query_options'] = list(tags) 1002 d['query_options'].sort() 1003 1004 if self.warnings and loglevel <= logging.WARNING: 1005 d['warnings'] = [w.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for w in self.warnings] 1006 1007 if self.errors and loglevel <= logging.ERROR: 1008 d['errors'] = [e.serialize(consolidate_clients=consolidate_clients, html_format=html_format) for e in self.errors] 1009 1010 return d 1011 1012#XXX This class is necessary because of a bug in dnspython, in which 1013# comparisons are not properly made for the purposes of sorting rdata for RRSIG 1014# validation 1015class RdataWrapper(object): 1016 def __init__(self, rdata): 1017 self._rdata = rdata 1018 1019 def __eq__(self, other): 1020 return self._rdata.to_digestable() == other._rdata.to_digestable() 1021 1022 def __lt__(self, other): 1023 return self._rdata.to_digestable() < other._rdata.to_digestable() 1024 1025class RRsetInfo(DNSResponseComponent): 1026 def __init__(self, rrset, ttl_cmp, dname_info=None): 1027 super(RRsetInfo, self).__init__() 1028 self.rrset = rrset 1029 self.ttl_cmp = ttl_cmp 1030 self.rrsig_info = {} 1031 self.wildcard_info = {} 1032 1033 self.dname_info = dname_info 1034 if self.dname_info is not None: 1035 self.servers_clients = dname_info.servers_clients 1036 1037 self.cname_info_from_dname = [] 1038 1039 def __str__(self): 1040 if self.rrset.rdtype == dns.rdatatype.NSEC3: 1041 return 'RRset for %s/%s' % (fmt.format_nsec3_name(self.rrset.name).rstrip('.'), dns.rdatatype.to_text(self.rrset.rdtype)) 1042 else: 1043 return 'RRset for %s/%s' % (fmt.humanize_name(self.rrset.name), dns.rdatatype.to_text(self.rrset.rdtype)) 1044 1045 def __repr__(self): 1046 return '<%s: "%s">' % (self.__class__.__name__, str(self)) 1047 1048 def __eq__(self, other): 1049 if not (self.rrset == other.rrset and self.dname_info == other.dname_info): 1050 return False 1051 if self.ttl_cmp and self.rrset.ttl != other.rrset.ttl: 1052 return False 1053 return True 1054 1055 def __hash__(self): 1056 return hash(id(self)) 1057 1058 @classmethod 1059 def rrset_canonicalized_to_wire(cls, rrset, name, ttl): 1060 s = b'' 1061 name_wire = name.to_wire() 1062 1063 rdata_list = [RdataWrapper(x) for x in rrset] 1064 rdata_list.sort() 1065 1066 for rdataw in rdata_list: 1067 rdata = rdataw._rdata 1068 rdata_wire = rdata.to_digestable() 1069 rdata_len = len(rdata_wire) 1070 1071 stuff = struct.pack(b'!HHIH', rrset.rdtype, rrset.rdclass, 1072 ttl, rdata_len) 1073 s += name_wire + stuff + rdata_wire 1074 1075 return s 1076 1077 def get_rrsig_info(self, rrsig): 1078 return self.rrsig_info[rrsig] 1079 1080 def update_rrsig_info(self, server, client, response, section, rdclass, is_referral): 1081 try: 1082 rrsig_rrset = response.message.find_rrset(section, self.rrset.name, rdclass, dns.rdatatype.RRSIG, self.rrset.rdtype) 1083 for rrsig in rrsig_rrset: 1084 self.create_or_update_rrsig_info(rrsig, rrsig_rrset.ttl, server, client, response, rdclass, is_referral) 1085 except KeyError: 1086 pass 1087 1088 if self.dname_info is not None: 1089 self.dname_info.update_rrsig_info(server, client, response, section, rdclass, is_referral) 1090 1091 def create_or_update_rrsig_info(self, rrsig, ttl, server, client, response, rdclass, is_referral): 1092 try: 1093 rrsig_info = self.get_rrsig_info(rrsig) 1094 except KeyError: 1095 rrsig_info = self.rrsig_info[rrsig] = RDataMeta(self.rrset.name, ttl, dns.rdatatype.RRSIG, rrsig) 1096 rrsig_info.add_server_client(server, client, response) 1097 self.set_wildcard_info(rrsig, server, client, response, rdclass, is_referral) 1098 1099 def create_or_update_cname_from_dname_info(self, synthesized_cname_info, server, client, response, rdclass): 1100 return self.insert_into_list(synthesized_cname_info, self.cname_info_from_dname, server, client, response) 1101 1102 def is_wildcard(self, rrsig): 1103 if self.rrset.name[0] == b'*': 1104 return False 1105 return len(self.rrset.name) - 1 > rrsig.labels 1106 1107 def reduce_wildcard(self, rrsig): 1108 if self.is_wildcard(rrsig): 1109 return dns.name.Name(('*',)+self.rrset.name.labels[-(rrsig.labels+1):]) 1110 return self.rrset.name 1111 1112 def set_wildcard_info(self, rrsig, server, client, response, rdclass, is_referral): 1113 if self.is_wildcard(rrsig): 1114 wildcard_name = self.reduce_wildcard(rrsig) 1115 if wildcard_name not in self.wildcard_info: 1116 self.wildcard_info[wildcard_name] = NegativeResponseInfo(self.rrset.name, self.rrset.rdtype, self.ttl_cmp) 1117 self.wildcard_info[wildcard_name].add_server_client(server, client, response) 1118 self.wildcard_info[wildcard_name].create_or_update_nsec_info(server, client, response, rdclass, is_referral) 1119 1120 def message_for_rrsig(self, rrsig): 1121 1122 # write RRSIG in wire format 1123 rdata_wire = struct.pack(b'!HBBIIIH', rrsig.type_covered, 1124 rrsig.algorithm, rrsig.labels, 1125 rrsig.original_ttl, rrsig.expiration, 1126 rrsig.inception, rrsig.key_tag) 1127 signer_wire = rrsig.signer.canonicalize().to_wire() 1128 rrsig_canonicalized_wire = rdata_wire + signer_wire 1129 1130 rrset_name = self.reduce_wildcard(rrsig).canonicalize() 1131 rrset_canonicalized_wire = self.rrset_canonicalized_to_wire(self.rrset, rrset_name, rrsig.original_ttl) 1132 1133 return rrsig_canonicalized_wire + rrset_canonicalized_wire 1134 1135 def serialize(self, consolidate_clients=True, show_servers=True, loglevel=logging.DEBUG, html_format=False, map_ip_to_ns_name=None): 1136 d = OrderedDict() 1137 1138 if html_format: 1139 formatter = lambda x: escape(x, True) 1140 else: 1141 formatter = lambda x: x 1142 1143 if self.rrset.rdtype == dns.rdatatype.NSEC3: 1144 d['name'] = formatter(fmt.format_nsec3_name(self.rrset.name)) 1145 else: 1146 d['name'] = formatter(lb2s(self.rrset.name.canonicalize().to_text())) 1147 d['ttl'] = self.rrset.ttl 1148 d['type'] = dns.rdatatype.to_text(self.rrset.rdtype) 1149 d['rdata'] = [] 1150 rdata_list = [RdataWrapper(x) for x in self.rrset] 1151 rdata_list.sort() 1152 for rdataw in rdata_list: 1153 rdata = rdataw._rdata 1154 if self.rrset.rdtype == dns.rdatatype.NSEC3: 1155 d['rdata'].append(fmt.format_nsec3_rrset_text(self.rrset[0].to_text())) 1156 else: 1157 s = rdata.to_text() 1158 # python3/python2 dual compatibility 1159 if not isinstance(s, str): 1160 s = lb2s(s) 1161 d['rdata'].append(formatter(s)) 1162 1163 if loglevel <= logging.INFO: 1164 servers = tuple_to_dict(self.servers_clients) 1165 if consolidate_clients: 1166 servers = list(servers) 1167 servers.sort() 1168 d['servers'] = servers 1169 1170 if map_ip_to_ns_name is not None: 1171 ns_names = list(set([lb2s(map_ip_to_ns_name(s)[0][0].canonicalize().to_text()) for s in servers])) 1172 ns_names.sort() 1173 d['ns_names'] = ns_names 1174 1175 tags = set() 1176 nsids = set() 1177 for server,client in self.servers_clients: 1178 for response in self.servers_clients[(server,client)]: 1179 tags.add(response.effective_query_tag()) 1180 nsid = response.nsid_val() 1181 if nsid is not None: 1182 nsids.add(nsid) 1183 1184 if nsids: 1185 d['nsid_values'] = list(nsids) 1186 d['nsid_values'].sort() 1187 1188 d['query_options'] = list(tags) 1189 d['query_options'].sort() 1190 1191 return d 1192 1193def cname_from_dname(name, dname_rrset): 1194 synthesized_cname = dns.name.Name(name.labels[:-len(dname_rrset.name)] + dname_rrset[0].target.labels) 1195 rrset = dns.rrset.RRset(name, dns.rdataclass.IN, dns.rdatatype.CNAME) 1196 rrset.update_ttl(dname_rrset.ttl) 1197 rrset.add(dns.rdtypes.ANY.CNAME.CNAME(dns.rdataclass.IN, dns.rdatatype.CNAME, synthesized_cname)) 1198 return rrset 1199 1200class NegativeResponseInfo(DNSResponseComponent): 1201 def __init__(self, qname, rdtype, ttl_cmp): 1202 super(NegativeResponseInfo, self).__init__() 1203 self.qname = qname 1204 self.rdtype = rdtype 1205 self.ttl_cmp = ttl_cmp 1206 self.soa_rrset_info = [] 1207 self.nsec_set_info = [] 1208 1209 def __repr__(self): 1210 return '<%s %s/%s>' % (self.__class__.__name__, self.qname, dns.rdatatype.to_text(self.rdtype)) 1211 1212 def __eq__(self, other): 1213 return self.qname == other.qname and self.rdtype == other.rdtype 1214 1215 def __hash__(self): 1216 return hash(id(self)) 1217 1218 def create_or_update_soa_info(self, server, client, response, rdclass, is_referral): 1219 soa_rrsets = [x for x in response.message.authority if x.rdtype == dns.rdatatype.SOA and x.rdclass == rdclass and self.qname.is_subdomain(x.name)] 1220 if not soa_rrsets: 1221 soa_rrsets = [x for x in response.message.authority if x.rdtype == dns.rdatatype.SOA and x.rdclass == rdclass] 1222 soa_rrsets.sort(reverse=True) 1223 try: 1224 soa_rrset = soa_rrsets[0] 1225 except IndexError: 1226 soa_rrset = None 1227 1228 if soa_rrset is None: 1229 return None 1230 1231 soa_rrset_info = RRsetInfo(soa_rrset, self.ttl_cmp) 1232 soa_rrset_info = self.insert_into_list(soa_rrset_info, self.soa_rrset_info, server, client, response) 1233 soa_rrset_info.update_rrsig_info(server, client, response, response.message.authority, rdclass, is_referral) 1234 1235 return soa_rrset_info 1236 1237 def create_or_update_nsec_info(self, server, client, response, rdclass, is_referral): 1238 for rdtype in dns.rdatatype.NSEC, dns.rdatatype.NSEC3: 1239 nsec_rrsets = [x for x in response.message.authority if x.rdtype == rdtype and x.rdclass == rdclass] 1240 if not nsec_rrsets: 1241 continue 1242 1243 nsec_set_info = NSECSet(nsec_rrsets, is_referral, self.ttl_cmp) 1244 nsec_set_info = self.insert_into_list(nsec_set_info, self.nsec_set_info, server, client, response) 1245 1246 for name in nsec_set_info.rrsets: 1247 nsec_set_info.rrsets[name].update_rrsig_info(server, client, response, response.message.authority, rdclass, is_referral) 1248 1249class NSECSet(DNSResponseComponent): 1250 def __init__(self, rrsets, referral, ttl_cmp): 1251 super(NSECSet, self).__init__() 1252 self.rrsets = {} 1253 self.referral = referral 1254 self.ttl_cmp = ttl_cmp 1255 self.nsec3_params = {} 1256 self.invalid_nsec3_owner = set() 1257 self.invalid_nsec3_hash = set() 1258 self.use_nsec3 = False 1259 for rrset in rrsets: 1260 #XXX There shouldn't be multiple NSEC(3) RRsets of the same owner 1261 # name in the same response, but check for it and address it (if 1262 # necessary) 1263 assert rrset.name not in self.rrsets 1264 self.rrsets[rrset.name] = RRsetInfo(rrset, self.ttl_cmp) 1265 1266 if rrset.rdtype == dns.rdatatype.NSEC3: 1267 self.use_nsec3 = True 1268 key = (rrset[0].salt, rrset[0].algorithm, rrset[0].iterations) 1269 if key not in self.nsec3_params: 1270 self.nsec3_params[key] = set() 1271 self.nsec3_params[key].add(rrset.name) 1272 if not self.is_valid_nsec3_name(rrset.name, rrset[0].algorithm): 1273 self.invalid_nsec3_owner.add(rrset.name) 1274 if not self.is_valid_nsec3_hash(rrset[0].next, rrset[0].algorithm): 1275 self.invalid_nsec3_hash.add(rrset.name) 1276 1277 self.servers_clients = {} 1278 1279 def __repr__(self): 1280 return '<%s>' % (self.__class__.__name__) 1281 1282 def __eq__(self, other): 1283 return self.rrsets == other.rrsets 1284 1285 def __hash__(self): 1286 return hash(id(self)) 1287 1288 def project(self, *names): 1289 if set(names).difference(self.rrsets): 1290 raise ValueError('NSEC name(s) don\'t exist in NSECSet') 1291 1292 obj = self.__class__((), self.referral, self.ttl_cmp) 1293 for name in names: 1294 obj.rrsets[name] = self.rrsets[name] 1295 rrset = obj.rrsets[name].rrset 1296 if rrset.rdtype == dns.rdatatype.NSEC3: 1297 obj.use_nsec3 = True 1298 key = (rrset[0].salt, rrset[0].algorithm, rrset[0].iterations) 1299 if key not in obj.nsec3_params: 1300 obj.nsec3_params[key] = set() 1301 obj.nsec3_params[key].add(rrset.name) 1302 if not obj.is_valid_nsec3_name(rrset.name, rrset[0].algorithm): 1303 obj.invalid_nsec3_owner.add(rrset.name) 1304 if not obj.is_valid_nsec3_hash(rrset[0].next, rrset[0].algorithm): 1305 obj.invalid_nsec3_hash.add(rrset.name) 1306 1307 obj.servers_clients = self.servers_clients.copy() 1308 return obj 1309 1310 def add_server_client(self, server, client, response): 1311 super(NSECSet, self).add_server_client(server, client, response) 1312 for name, rrset_info in self.rrsets.items(): 1313 rrset_info.add_server_client(server, client, response) 1314 1315 def create_or_update_rrsig_info(self, name, rrsig, ttl, server, client, response, rdclass, is_referral): 1316 self.rrsets[name].create_or_update_rrsig_info(rrsig, ttl, server, client, response, rdclass, is_referral) 1317 1318 def is_valid_nsec3_name(self, nsec_name, algorithm): 1319 # python3/python2 dual compatibility 1320 if isinstance(nsec_name[0], str): 1321 map_func = lambda x: codecs.encode(x.upper(), 'latin1') 1322 else: 1323 map_func = lambda x: codecs.encode(chr(x).upper(), 'latin1') 1324 1325 # check that NSEC3 name is valid 1326 if algorithm == 1: 1327 # base32hex encoding of SHA1 should be 32 bytes 1328 if len(nsec_name[0]) != 32: 1329 return False 1330 if [x for x in nsec_name[0] if map_func(x) not in base32.b32alphabet]: 1331 return False 1332 return True 1333 1334 def is_valid_nsec3_hash(self, nsec3_hash, algorithm): 1335 # check that NSEC3 hash is valid 1336 if algorithm == 1: 1337 # length of SHA1 hash should be 20 bytes 1338 if len(nsec3_hash) != 20: 1339 return False 1340 return True 1341 1342 def get_algorithm_support(self): 1343 valid_algorithms = set() 1344 invalid_algorithms = set() 1345 for (salt, alg, iterations) in self.nsec3_params: 1346 if crypto.nsec3_alg_is_supported(alg): 1347 valid_algorithms.add(alg) 1348 else: 1349 invalid_algorithms.add(alg) 1350 return valid_algorithms, invalid_algorithms 1351 1352 def rdtype_exists_in_bitmap(self, nsec_name, rdtype): 1353 '''Return True if the rdtype exists in the bitmap of the NSEC(3) record 1354 corresponding to the name; False otherwise.''' 1355 1356 rdtype_window = (rdtype >> 8) 1357 rdtype_bitmap = rdtype & 0x00ff 1358 bitmap_index, bitmap_offset = divmod(rdtype_bitmap, 8) 1359 for (window, bitmap) in self.rrsets[nsec_name].rrset[0].windows: 1360 try: 1361 # dnspython <= 1.12.x uses strings, but dnspython 1.13 uses bytearray (for python3) 1362 byte = bitmap[bitmap_index] 1363 if isinstance(bitmap, str): 1364 byte = ord(byte) 1365 if window == rdtype_window and byte & (0x80 >> bitmap_offset): 1366 return True 1367 except IndexError: 1368 pass 1369 return False 1370 1371 def name_for_nsec3_next(self, nsec_name): 1372 '''Convert the next field of an NSEC3 RR to a DNS name.''' 1373 1374 next_name = self.rrsets[nsec_name].rrset[0].next 1375 next_name_txt = base32.b32encode(next_name) 1376 origin = dns.name.Name(nsec_name.labels[1:]) 1377 return dns.name.from_text(next_name_txt, origin) 1378 1379 def _nsec_covers_name(self, name, nsec_name): 1380 '''Return True if the NSEC record corresponding to NSEC name provided 1381 covers a name (i.e., proves its non-existence); False otherwise.''' 1382 1383 prev_name = nsec_name 1384 if self.use_nsec3: 1385 next_name = self.name_for_nsec3_next(nsec_name) 1386 # test that NSEC3 names have the same parent 1387 try: 1388 if not (name.parent() == nsec_name.parent() == next_name.parent()): 1389 return False 1390 except dns.name.NoParent: 1391 return False 1392 else: 1393 next_name = self.rrsets[nsec_name].rrset[0].next 1394 1395 if prev_name == next_name: 1396 return prev_name != name 1397 elif prev_name > next_name: 1398 return not (next_name <= name <= prev_name) 1399 else: 1400 return (prev_name < name < next_name) and not next_name.is_subdomain(name) 1401 1402 def nsec_covering_name(self, name): 1403 '''Return the set of owner names corresponding to NSEC records in the 1404 response that cover the given name.''' 1405 1406 excluding_names = set() 1407 for nsec_name in set(self.rrsets).difference(self.invalid_nsec3_owner.union(self.invalid_nsec3_hash)): 1408 if self._nsec_covers_name(name, nsec_name): 1409 excluding_names.add(nsec_name) 1410 return excluding_names 1411 1412 def get_digest_name_for_nsec3(self, name, origin, salt, alg, iterations): 1413 '''Return the DNS name corresponding to the name, origin, and NSEC3 1414 hash parameters provided.''' 1415 1416 val = name.canonicalize().to_wire() 1417 digest = crypto.get_digest_for_nsec3(val, salt, alg, iterations) 1418 if digest is None: 1419 return None 1420 else: 1421 return dns.name.from_text(base32.b32encode(digest), origin) 1422 1423 def nsec3_covering_name(self, name, salt, alg, iterations): 1424 '''Return the set of owner names corresponding to NSEC3 records in the 1425 response that cover the given (digest) name.''' 1426 1427 excluding_names = set() 1428 for nsec_name in set(self.nsec3_params[(salt, alg, iterations)]).difference(self.invalid_nsec3_owner.union(self.invalid_nsec3_hash)): 1429 if self._nsec_covers_name(name, nsec_name): 1430 excluding_names.add(nsec_name) 1431 return excluding_names 1432 1433 def _find_potential_closest_enclosers(self, qname, origin, salt, alg, iterations): 1434 '''Return a mapping of potential closest enclosers for a given name and 1435 origin, with digests computed with the given salt, algorithm, and 1436 iterations parameters. The mapping maps a name to a set of 1437 corresponding digest names. The algorithm follows that specified in RFC 1438 5155 8.3.''' 1439 1440 closest_enclosers = {} 1441 nsec3_names = self.nsec3_params[(salt, alg, iterations)] 1442 1443 sname = qname 1444 flag = False 1445 while len(sname) >= len(origin): 1446 digest_name = self.get_digest_name_for_nsec3(sname, origin, salt, alg, iterations) 1447 1448 # unsupported algorithm 1449 if digest_name is None: 1450 return closest_enclosers 1451 1452 if digest_name not in nsec3_names: 1453 flag = False 1454 1455 if self.nsec_covering_name(digest_name): 1456 flag = True 1457 1458 if digest_name in nsec3_names: 1459 if flag: 1460 if sname not in closest_enclosers: 1461 closest_enclosers[sname] = set() 1462 closest_enclosers[sname].add(digest_name) 1463 break 1464 1465 sname = dns.name.Name(sname.labels[1:]) 1466 return closest_enclosers 1467 1468 def check_closest_encloser(self, name, nsec_name, origin): 1469 '''Return True if the candidate closest encloser meets the requirements 1470 for a closest encloser in RFC 5155.''' 1471 1472 if not name.is_subdomain(origin): 1473 return False 1474 if self.rdtype_exists_in_bitmap(nsec_name, dns.rdatatype.DNAME): 1475 return False 1476 if self.rdtype_exists_in_bitmap(nsec_name, dns.rdatatype.NS) and \ 1477 not self.rdtype_exists_in_bitmap(nsec_name, dns.rdatatype.SOA): 1478 return False 1479 return True 1480 1481 def get_closest_encloser(self, qname, origin): 1482 '''Return a mapping of closest enclosers for a given name and 1483 origin.''' 1484 1485 potential_closest_enclosers = {} 1486 for salt, alg, iterations in self.nsec3_params: 1487 ret = self._find_potential_closest_enclosers(qname, origin, salt, alg, iterations) 1488 for name in ret: 1489 if name in potential_closest_enclosers: 1490 potential_closest_enclosers[name].update(ret[name]) 1491 else: 1492 potential_closest_enclosers[name] = ret[name] 1493 1494 for name in list(potential_closest_enclosers): 1495 for nsec_name in list(potential_closest_enclosers[name]): 1496 if not self.check_closest_encloser(name, nsec_name, origin): 1497 potential_closest_enclosers[name].remove(nsec_name) 1498 if not potential_closest_enclosers[name]: 1499 del potential_closest_enclosers[name] 1500 1501 return potential_closest_enclosers 1502 1503class DNSResponseError(DNSResponseComponent): 1504 def __init__(self, code, arg): 1505 super(DNSResponseError, self).__init__() 1506 self.code = code 1507 self.arg = arg 1508 1509 def __eq__(self, other): 1510 return self.code == other.code and self.arg == other.arg 1511 1512 def __hash__(self): 1513 return hash(id(self)) 1514 1515class ReferralResponse(DNSResponseComponent): 1516 def __init__(self, name): 1517 super(ReferralResponse, self).__init__() 1518 self.name = name 1519 1520 def __eq__(self, other): 1521 return self.name == other.name 1522 1523 def __hash__(self): 1524 return hash(id(self)) 1525 1526class TruncatedResponse(DNSResponseComponent): 1527 def __init__(self, wire): 1528 super(TruncatedResponse, self).__init__() 1529 self.wire = wire 1530 1531 def __eq__(self, other): 1532 return self.wire == other.wire 1533 1534 def __hash__(self): 1535 return hash(id(self)) 1536