1# Copyright (c) 2013-2020 Philip Hane 2# All rights reserved. 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions are met: 6# 7# 1. Redistributions of source code must retain the above copyright notice, 8# this list of conditions and the following disclaimer. 9# 2. Redistributions in binary form must reproduce the above copyright notice, 10# this list of conditions and the following disclaimer in the documentation 11# and/or other materials provided with the distribution. 12# 13# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 17# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23# POSSIBILITY OF SUCH DAMAGE. 24 25from . import Net 26from .asn import IPASN 27from .nir import NIRWhois 28import logging 29 30log = logging.getLogger(__name__) 31 32 33class IPWhois: 34 """ 35 The wrapper class for performing whois/RDAP lookups and parsing for 36 IPv4 and IPv6 addresses. 37 38 Args: 39 address (:obj:`str`/:obj:`int`/:obj:`IPv4Address`/:obj:`IPv6Address`): 40 An IPv4 or IPv6 address 41 timeout (:obj:`int`): The default timeout for socket connections in 42 seconds. Defaults to 5. 43 proxy_opener (:obj:`urllib.request.OpenerDirector`): The request for 44 proxy support. Defaults to None. 45 """ 46 47 def __init__(self, address, timeout=5, proxy_opener=None): 48 49 self.net = Net( 50 address=address, timeout=timeout, proxy_opener=proxy_opener 51 ) 52 self.ipasn = IPASN(self.net) 53 54 self.address = self.net.address 55 self.timeout = self.net.timeout 56 self.address_str = self.net.address_str 57 self.version = self.net.version 58 self.reversed = self.net.reversed 59 self.dns_zone = self.net.dns_zone 60 61 def __repr__(self): 62 63 return 'IPWhois({0}, {1}, {2})'.format( 64 self.address_str, str(self.timeout), repr(self.net.opener) 65 ) 66 67 def lookup_whois(self, inc_raw=False, retry_count=3, get_referral=False, 68 extra_blacklist=None, ignore_referral_errors=False, 69 field_list=None, extra_org_map=None, 70 inc_nir=True, nir_field_list=None, asn_methods=None, 71 get_asn_description=True): 72 """ 73 The function for retrieving and parsing whois information for an IP 74 address via port 43 (WHOIS). 75 76 Args: 77 inc_raw (:obj:`bool`): Whether to include the raw whois results in 78 the returned dictionary. Defaults to False. 79 retry_count (:obj:`int`): The number of times to retry in case 80 socket errors, timeouts, connection resets, etc. are 81 encountered. Defaults to 3. 82 get_referral (:obj:`bool`): Whether to retrieve referral whois 83 information, if available. Defaults to False. 84 extra_blacklist (:obj:`list`): Blacklisted whois servers in 85 addition to the global BLACKLIST. Defaults to None. 86 ignore_referral_errors (:obj:`bool`): Whether to ignore and 87 continue when an exception is encountered on referral whois 88 lookups. Defaults to False. 89 field_list (:obj:`list`): If provided, a list of fields to parse: 90 ['name', 'handle', 'description', 'country', 'state', 'city', 91 'address', 'postal_code', 'emails', 'created', 'updated'] 92 If None, defaults to all. 93 extra_org_map (:obj:`dict`): Dictionary mapping org handles to 94 RIRs. This is for limited cases where ARIN REST (ASN fallback 95 HTTP lookup) does not show an RIR as the org handle e.g., DNIC 96 (which is now the built in ORG_MAP) e.g., {'DNIC': 'arin'}. 97 Valid RIR values are (note the case-sensitive - this is meant 98 to match the REST result): 99 'ARIN', 'RIPE', 'apnic', 'lacnic', 'afrinic' 100 Defaults to None. 101 inc_nir (:obj:`bool`): Whether to retrieve NIR (National Internet 102 Registry) information, if registry is JPNIC (Japan) or KRNIC 103 (Korea). If True, extra network requests will be required. 104 If False, the information returned for JP or KR IPs is 105 severely restricted. Defaults to True. 106 nir_field_list (:obj:`list`): If provided and inc_nir, a list of 107 fields to parse: 108 ['name', 'handle', 'country', 'address', 'postal_code', 109 'nameservers', 'created', 'updated', 'contacts'] 110 If None, defaults to all. 111 asn_methods (:obj:`list`): ASN lookup types to attempt, in order. 112 If None, defaults to all ['dns', 'whois', 'http']. 113 get_asn_description (:obj:`bool`): Whether to run an additional 114 query when pulling ASN information via dns, in order to get 115 the ASN description. Defaults to True. 116 117 Returns: 118 dict: The IP whois lookup results 119 120 :: 121 122 { 123 'query' (str) - The IP address 124 'asn' (str) - The Autonomous System Number 125 'asn_date' (str) - The ASN Allocation date 126 'asn_registry' (str) - The assigned ASN registry 127 'asn_cidr' (str) - The assigned ASN CIDR 128 'asn_country_code' (str) - The assigned ASN country code 129 'asn_description' (str) - The ASN description 130 'nets' (list) - Dictionaries containing network 131 information which consists of the fields listed in the 132 ipwhois.whois.RIR_WHOIS dictionary. 133 'raw' (str) - Raw whois results if the inc_raw parameter 134 is True. 135 'referral' (dict) - Referral whois information if 136 get_referral is True and the server is not blacklisted. 137 Consists of fields listed in the ipwhois.whois.RWHOIS 138 dictionary. 139 'raw_referral' (str) - Raw referral whois results if the 140 inc_raw parameter is True. 141 'nir' (dict) - ipwhois.nir.NIRWhois() results if inc_nir 142 is True. 143 } 144 """ 145 146 from .whois import Whois 147 148 # Create the return dictionary. 149 results = {'nir': None} 150 151 # Retrieve the ASN information. 152 log.debug('ASN lookup for {0}'.format(self.address_str)) 153 154 asn_data = self.ipasn.lookup( 155 inc_raw=inc_raw, retry_count=retry_count, 156 extra_org_map=extra_org_map, asn_methods=asn_methods, 157 get_asn_description=get_asn_description 158 ) 159 160 # Add the ASN information to the return dictionary. 161 results.update(asn_data) 162 163 # Retrieve the whois data and parse. 164 whois = Whois(self.net) 165 log.debug('WHOIS lookup for {0}'.format(self.address_str)) 166 whois_data = whois.lookup( 167 inc_raw=inc_raw, retry_count=retry_count, response=None, 168 get_referral=get_referral, extra_blacklist=extra_blacklist, 169 ignore_referral_errors=ignore_referral_errors, asn_data=asn_data, 170 field_list=field_list 171 ) 172 173 # Add the WHOIS information to the return dictionary. 174 results.update(whois_data) 175 176 if inc_nir: 177 178 nir = None 179 if 'JP' == asn_data['asn_country_code']: 180 nir = 'jpnic' 181 elif 'KR' == asn_data['asn_country_code']: 182 nir = 'krnic' 183 184 if nir: 185 186 nir_whois = NIRWhois(self.net) 187 nir_data = nir_whois.lookup( 188 nir=nir, inc_raw=inc_raw, retry_count=retry_count, 189 response=None, 190 field_list=nir_field_list, is_offline=False 191 ) 192 193 # Add the NIR information to the return dictionary. 194 results['nir'] = nir_data 195 196 return results 197 198 def lookup_rdap(self, inc_raw=False, retry_count=3, depth=0, 199 excluded_entities=None, bootstrap=False, 200 rate_limit_timeout=120, extra_org_map=None, 201 inc_nir=True, nir_field_list=None, asn_methods=None, 202 get_asn_description=True, root_ent_check=True): 203 """ 204 The function for retrieving and parsing whois information for an IP 205 address via HTTP (RDAP). 206 207 **This is now the recommended method, as RDAP contains much better 208 information to parse.** 209 210 Args: 211 inc_raw (:obj:`bool`): Whether to include the raw whois results in 212 the returned dictionary. Defaults to False. 213 retry_count (:obj:`int`): The number of times to retry in case 214 socket errors, timeouts, connection resets, etc. are 215 encountered. Defaults to 3. 216 depth (:obj:`int`): How many levels deep to run queries when 217 additional referenced objects are found. Defaults to 0. 218 excluded_entities (:obj:`list`): Entity handles to not perform 219 lookups. Defaults to None. 220 bootstrap (:obj:`bool`): If True, performs lookups via ARIN 221 bootstrap rather than lookups based on ASN data. ASN lookups 222 are not performed and no output for any of the asn* fields is 223 provided. Defaults to False. 224 rate_limit_timeout (:obj:`int`): The number of seconds to wait 225 before retrying when a rate limit notice is returned via 226 rdap+json. Defaults to 120. 227 extra_org_map (:obj:`dict`): Dictionary mapping org handles to 228 RIRs. This is for limited cases where ARIN REST (ASN fallback 229 HTTP lookup) does not show an RIR as the org handle e.g., DNIC 230 (which is now the built in ORG_MAP) e.g., {'DNIC': 'arin'}. 231 Valid RIR values are (note the case-sensitive - this is meant 232 to match the REST result): 233 'ARIN', 'RIPE', 'apnic', 'lacnic', 'afrinic' 234 Defaults to None. 235 inc_nir (:obj:`bool`): Whether to retrieve NIR (National Internet 236 Registry) information, if registry is JPNIC (Japan) or KRNIC 237 (Korea). If True, extra network requests will be required. 238 If False, the information returned for JP or KR IPs is 239 severely restricted. Defaults to True. 240 nir_field_list (:obj:`list`): If provided and inc_nir, a list of 241 fields to parse: 242 ['name', 'handle', 'country', 'address', 'postal_code', 243 'nameservers', 'created', 'updated', 'contacts'] 244 If None, defaults to all. 245 asn_methods (:obj:`list`): ASN lookup types to attempt, in order. 246 If None, defaults to all ['dns', 'whois', 'http']. 247 get_asn_description (:obj:`bool`): Whether to run an additional 248 query when pulling ASN information via dns, in order to get 249 the ASN description. Defaults to True. 250 root_ent_check (:obj:`bool`): If True, will perform 251 additional RDAP HTTP queries for missing entity data at the 252 root level. Defaults to True. 253 254 Returns: 255 dict: The IP RDAP lookup results 256 257 :: 258 259 { 260 'query' (str) - The IP address 261 'asn' (str) - The Autonomous System Number 262 'asn_date' (str) - The ASN Allocation date 263 'asn_registry' (str) - The assigned ASN registry 264 'asn_cidr' (str) - The assigned ASN CIDR 265 'asn_country_code' (str) - The assigned ASN country code 266 'asn_description' (str) - The ASN description 267 'entities' (list) - Entity handles referred by the top 268 level query. 269 'network' (dict) - Network information which consists of 270 the fields listed in the ipwhois.rdap._RDAPNetwork 271 dict. 272 'objects' (dict) - Mapping of entity handle->entity dict 273 which consists of the fields listed in the 274 ipwhois.rdap._RDAPEntity dict. The raw result is 275 included for each object if the inc_raw parameter 276 is True. 277 'raw' (dict) - Whois results in json format if the inc_raw 278 parameter is True. 279 'nir' (dict) - ipwhois.nir.NIRWhois results if inc_nir is 280 True. 281 } 282 """ 283 284 from .rdap import RDAP 285 286 # Create the return dictionary. 287 results = {'nir': None} 288 289 asn_data = None 290 response = None 291 if not bootstrap: 292 293 # Retrieve the ASN information. 294 log.debug('ASN lookup for {0}'.format(self.address_str)) 295 asn_data = self.ipasn.lookup( 296 inc_raw=inc_raw, retry_count=retry_count, 297 extra_org_map=extra_org_map, asn_methods=asn_methods, 298 get_asn_description=get_asn_description 299 ) 300 301 # Add the ASN information to the return dictionary. 302 results.update(asn_data) 303 304 # Retrieve the RDAP data and parse. 305 rdap = RDAP(self.net) 306 log.debug('RDAP lookup for {0}'.format(self.address_str)) 307 rdap_data = rdap.lookup( 308 inc_raw=inc_raw, retry_count=retry_count, asn_data=asn_data, 309 depth=depth, excluded_entities=excluded_entities, 310 response=response, bootstrap=bootstrap, 311 rate_limit_timeout=rate_limit_timeout, 312 root_ent_check=root_ent_check 313 ) 314 315 # Add the RDAP information to the return dictionary. 316 results.update(rdap_data) 317 318 if inc_nir: 319 320 nir = None 321 if 'JP' == asn_data['asn_country_code']: 322 nir = 'jpnic' 323 elif 'KR' == asn_data['asn_country_code']: 324 nir = 'krnic' 325 326 if nir: 327 nir_whois = NIRWhois(self.net) 328 nir_data = nir_whois.lookup( 329 nir=nir, inc_raw=inc_raw, retry_count=retry_count, 330 response=None, 331 field_list=nir_field_list, is_offline=False 332 ) 333 334 # Add the NIR information to the return dictionary. 335 results['nir'] = nir_data 336 337 return results 338