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 2014-2016 VeriSign, Inc. 7# 8# Copyright 2016-2021 Casey Deccio 9# 10# DNSViz is free software; you can redistribute it and/or modify 11# it under the terms of the GNU General Public License as published by 12# the Free Software Foundation; either version 2 of the License, or 13# (at your option) any later version. 14# 15# DNSViz is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License along 21# with DNSViz. If not, see <http://www.gnu.org/licenses/>. 22# 23 24from __future__ import unicode_literals 25 26import binascii 27import codecs 28import re 29import socket 30 31INTERFACE_RE = re.compile(r'%[a-z0-9]+$') 32 33class IPAddr(str): 34 def __new__(cls, string): 35 # python 2/3 compatibility 36 if isinstance(string, bytes): 37 string = codecs.decode(string, 'latin1') 38 if ':' in string: 39 af = socket.AF_INET6 40 vers = 6 41 string = INTERFACE_RE.sub('', string) 42 else: 43 af = socket.AF_INET 44 vers = 4 45 46 try: 47 ipaddr_bytes = socket.inet_pton(af, string) 48 except socket.error: 49 raise ValueError('Invalid value for IP address: %s' % string) 50 obj = super(IPAddr, cls).__new__(cls, socket.inet_ntop(af, ipaddr_bytes)) 51 obj._ipaddr_bytes = ipaddr_bytes 52 obj.version = vers 53 return obj 54 55 def _check_class_for_cmp(self, other): 56 if self.__class__ != other.__class__: 57 raise TypeError('Cannot compare IPAddr to %s!' % other.__class__.__name__) 58 59 def __lt__(self, other): 60 self._check_class_for_cmp(other) 61 if len(self._ipaddr_bytes) < len(other._ipaddr_bytes): 62 return True 63 elif len(self._ipaddr_bytes) > len(other._ipaddr_bytes): 64 return False 65 else: 66 return self._ipaddr_bytes < other._ipaddr_bytes 67 68 def __eq__(self, other): 69 if other is None: 70 return False 71 if isinstance(other, IPAddr): 72 return self._ipaddr_bytes == other._ipaddr_bytes 73 else: 74 return super(IPAddr, self) == other 75 76 def __hash__(self): 77 return hash(self._ipaddr_bytes) 78 79 def arpa_name(self): 80 if self.version == 6: 81 nibbles = [n for n in binascii.hexlify(self._ipaddr_bytes)] 82 nibbles.reverse() 83 name = '.'.join(nibbles) 84 name += '.ip6.arpa.' 85 else: 86 octets = self.split('.') 87 octets.reverse() 88 name = '.'.join(octets) 89 name += '.in-addr.arpa.' 90 return name 91 92LOOPBACK_IPV4_RE = re.compile(r'^127') 93IPV4_MAPPED_IPV6_RE = re.compile(r'^::(ffff:)?\d+.\d+.\d+.\d+$', re.IGNORECASE) 94LOOPBACK_IPV6 = IPAddr('::1') 95RFC_1918_RE = re.compile(r'^(0?10|172\.0?(1[6-9]|2[0-9]|3[0-1])|192\.168)\.') 96LINK_LOCAL_RE = re.compile(r'^fe[89ab][0-9a-f]:', re.IGNORECASE) 97UNIQ_LOCAL_RE = re.compile(r'^fd[0-9a-f]{2}:', re.IGNORECASE) 98ZERO_SLASH8_RE = re.compile(r'^0\.') 99 100ANY_IPV6 = IPAddr('::') 101ANY_IPV4 = IPAddr('0.0.0.0') 102