├── .codeclimate.yml ├── .coveragerc ├── .github └── workflows │ ├── python-publish-test.yml │ └── python-publish.yml ├── .gitignore ├── .readthedocs.yaml ├── ASN.rst ├── CHANGES.old.rst ├── CHANGES.rst ├── CLI.rst ├── CONTRIBUTING.rst ├── EXPERIMENTAL.rst ├── LICENSE.txt ├── MANIFEST.in ├── NIR.rst ├── RDAP.rst ├── README.rst ├── UPGRADING.rst ├── UTILS.rst ├── WHOIS.rst ├── ipwhois ├── __init__.py ├── asn.py ├── data │ ├── iso_3166-1.csv │ └── iso_3166-1_list_en.xml ├── docs │ ├── Makefile │ ├── make.bat │ ├── requirements.txt │ └── source │ │ ├── ASN.rst │ │ ├── CHANGES.old.rst │ │ ├── CHANGES.rst │ │ ├── CLI.rst │ │ ├── CONTRIBUTING.rst │ │ ├── EXPERIMENTAL.rst │ │ ├── LICENSE.rst │ │ ├── NIR.rst │ │ ├── RDAP.rst │ │ ├── README.rst │ │ ├── UPGRADING.rst │ │ ├── UTILS.rst │ │ ├── WHOIS.rst │ │ ├── _templates │ │ └── layout.html │ │ ├── conf.py │ │ ├── index.rst │ │ └── ipwhois.rst ├── examples │ ├── __init__.py │ ├── elastic_search │ │ ├── README.rst │ │ ├── data │ │ │ ├── geo_coord.json │ │ │ └── kibana.json │ │ ├── elastic_search.py │ │ └── requirements.txt │ └── redis_cache │ │ ├── README.rst │ │ ├── redis_cache.py │ │ ├── requirements.txt │ │ └── requirements26.txt ├── exceptions.py ├── experimental.py ├── hr.py ├── ipwhois.py ├── net.py ├── nir.py ├── rdap.py ├── scripts │ ├── docs │ │ └── generate_examples.py │ ├── ipwhois_cli.py │ └── ipwhois_utils_cli.py ├── tests │ ├── __init__.py │ ├── asn.json │ ├── entity.json │ ├── jpnic.json │ ├── krnic.json │ ├── online │ │ ├── __init__.py │ │ ├── test_asn.py │ │ ├── test_experimental.py │ │ ├── test_ipwhois.py │ │ ├── test_net.py │ │ ├── test_nir.py │ │ ├── test_rdap.py │ │ └── test_whois.py │ ├── rdap.json │ ├── stress │ │ ├── __init__.py │ │ ├── test_experimental.py │ │ └── test_net.py │ ├── test_asn.py │ ├── test_experimental.py │ ├── test_ipwhois.py │ ├── test_net.py │ ├── test_nir.py │ ├── test_rdap.py │ ├── test_utils.py │ ├── test_whois.py │ └── whois.json ├── utils.py └── whois.py ├── pyproject.toml ├── requirements ├── python2.txt └── python3.txt └── setup.cfg /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | duplication: 3 | enabled: true 4 | config: 5 | languages: 6 | python: 7 | fixme: 8 | enabled: true 9 | pep8: 10 | enabled: true 11 | radon: 12 | enabled: true 13 | 14 | ratings: 15 | paths: 16 | - "ipwhois/*.py" 17 | - "ipwhois/scripts/*.py" 18 | 19 | exclude_paths: 20 | - "ipwhois/data/" 21 | - "ipwhois/docs/" 22 | - "ipwhois/examples/" 23 | - "ipwhois/scripts/docs/" 24 | - "ipwhois/tests/" 25 | - "requirements/" 26 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | show_missing = True 3 | omit = 4 | */python?.?/* 5 | */site-packages/nose/* 6 | ipwhois/data/* 7 | ipwhois/docs/* 8 | ipwhois/examples/* 9 | ipwhois/scripts/* 10 | ipwhois/tests/* 11 | ipwhois/hr.py 12 | -------------------------------------------------------------------------------- /.github/workflows/python-publish-test.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package to Test PyPi 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | deploy: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up Python 13 | uses: actions/setup-python@v2 14 | with: 15 | python-version: '3.x' 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install setuptools wheel twine 20 | - name: Build and publish 21 | env: 22 | TWINE_USERNAME: secynic 23 | TWINE_PASSWORD: ${{ secrets.TEST_PYPI_PASSWORD }} 24 | run: | 25 | python setup.py sdist bdist_wheel 26 | twine upload --repository testpypi dist/* 27 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package to PyPi 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Set up Python 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: '3.x' 18 | - name: Install dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | pip install setuptools wheel twine 22 | - name: Build and publish 23 | env: 24 | TWINE_USERNAME: secynic 25 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 26 | run: | 27 | python setup.py sdist bdist_wheel 28 | twine upload dist/* 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | MANIFEST 39 | .idea 40 | .history 41 | .vscode 42 | .venv -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | 6 | # Required 7 | 8 | version: 2 9 | 10 | 11 | # Set the OS, Python version and other tools you might need 12 | 13 | build: 14 | 15 | os: ubuntu-22.04 16 | 17 | tools: 18 | 19 | python: "3.12" 20 | 21 | # Build documentation in the "docs/" directory with Sphinx 22 | 23 | sphinx: 24 | 25 | configuration: ipwhois/docs/source/conf.py 26 | 27 | formats: 28 | 29 | - pdf 30 | - epub 31 | 32 | python: 33 | 34 | install: 35 | 36 | - requirements: ipwhois/docs/requirements.txt -------------------------------------------------------------------------------- /ASN.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | IP ASN Lookups 3 | ============== 4 | 5 | This is new functionality as of v0.15.0. This functionality was migrated from 6 | net.Net and is still used by IPWhois.lookup*(). 7 | 8 | .. note:: 9 | 10 | Cymru ASN data should not be considered a primary source for data points 11 | like country code. 12 | 13 | Message from the Cymru site:: 14 | 15 | The country code, registry, and allocation date are all based on data 16 | obtained directly from the regional registries including: ARIN, RIPE, 17 | AFRINIC, APNIC, LACNIC. The information returned relating to these 18 | categories will only be as accurate as the data present in the RIR 19 | databases. 20 | 21 | IMPORTANT NOTE: Country codes are likely to vary significantly from 22 | actual IP locations, and we must strongly advise that the IP to ASN 23 | mapping tool not be used as an IP geolocation (GeoIP) service. 24 | 25 | https://team-cymru.com/community-services/ip-asn-mapping/ 26 | 27 | .. _ip-asn-input: 28 | 29 | IP ASN Input 30 | ============ 31 | 32 | Arguments supported by IPASN.lookup(). 33 | 34 | +------------------------+--------+-------------------------------------------+ 35 | | **Key** |**Type**| **Description** | 36 | +------------------------+--------+-------------------------------------------+ 37 | | inc_raw | bool | Whether to include the raw whois results | 38 | | | | in the returned dictionary. Defaults to | 39 | | | | False. | 40 | +------------------------+--------+-------------------------------------------+ 41 | | retry_count | int | The number of times to retry in case | 42 | | | | socket errors, timeouts, connection | 43 | | | | resets, etc. are encountered. | 44 | | | | Defaults to 3. | 45 | +------------------------+--------+-------------------------------------------+ 46 | | extra_org_map | dict | Dictionary mapping org handles to RIRs. | 47 | | | | This is for limited cases where ARIN | 48 | | | | REST (ASN fallback HTTP lookup) does not | 49 | | | | show an RIR as the org handle e.g., DNIC | 50 | | | | (which is now built in ORG_MAP) | 51 | | | | e.g., {'DNIC': 'arin'} | 52 | | | | Valid RIR values are (note the | 53 | | | | case-sensitive - this is meant to match | 54 | | | | the REST result): 'ARIN', 'RIPE', | 55 | | | | 'apnic', 'lacnic', 'afrinic' | 56 | | | | Defaults to None. | 57 | +------------------------+--------+-------------------------------------------+ 58 | | asn_methods | list | ASN lookup types to attempt, in order. If | 59 | | | | None, defaults to all ['dns', 'whois', | 60 | | | | 'http']. | 61 | +------------------------+--------+-------------------------------------------+ 62 | | get_asn_description | bool | Whether to run an additional query when | 63 | | | | pulling ASN information via dns, in order | 64 | | | | to get the ASN description. Defaults to | 65 | | | | True. | 66 | +------------------------+--------+-------------------------------------------+ 67 | 68 | .. _ip-asn-output: 69 | 70 | IP ASN Output 71 | ============= 72 | 73 | .. _ip-asn-results-dictionary: 74 | 75 | IP ASN Results Dictionary 76 | ------------------------- 77 | 78 | The output dictionary from IPASN.lookup(). 79 | 80 | +------------------+--------+-------------------------------------------------+ 81 | | **Key** |**Type**| **Description** | 82 | +------------------+--------+-------------------------------------------------+ 83 | | asn | str | The Autonomous System Number | 84 | +------------------+--------+-------------------------------------------------+ 85 | | asn_date | str | The ASN Allocation date | 86 | +------------------+--------+-------------------------------------------------+ 87 | | asn_registry | str | The assigned ASN registry | 88 | +------------------+--------+-------------------------------------------------+ 89 | | asn_cidr | str | The assigned ASN CIDR | 90 | +------------------+--------+-------------------------------------------------+ 91 | | asn_country_code | str | The assigned ASN country code | 92 | +------------------+--------+-------------------------------------------------+ 93 | | asn_description | str | The ASN description | 94 | +------------------+--------+-------------------------------------------------+ 95 | | raw | str | Raw ASN results if inc_raw is True. | 96 | +------------------+--------+-------------------------------------------------+ 97 | 98 | .. _ip-asn-usage-examples: 99 | 100 | IP ASN Usage Examples 101 | ===================== 102 | 103 | Basic usage 104 | ----------- 105 | 106 | .. OUTPUT_IP_ASN_BASIC START 107 | 108 | :: 109 | 110 | >>>> from ipwhois.net import Net 111 | >>>> from ipwhois.asn import IPASN 112 | >>>> from pprint import pprint 113 | 114 | >>>> net = Net('2001:43f8:7b0::') 115 | >>>> obj = IPASN(net) 116 | >>>> results = obj.lookup() 117 | >>>> pprint(results) 118 | 119 | { 120 | "asn": "37578", 121 | "asn_cidr": "2001:43f8:7b0::/48", 122 | "asn_country_code": "KE", 123 | "asn_date": "2013-03-22", 124 | "asn_description": "Tespok, KE", 125 | "asn_registry": "afrinic" 126 | } 127 | 128 | .. OUTPUT_IP_ASN_BASIC END 129 | 130 | ================== 131 | ASN Origin Lookups 132 | ================== 133 | 134 | This is new functionality as of v0.15.0. 135 | 136 | Both Whois and HTTP protocols are supported. 137 | 138 | RADB is the only query destination at the moment. 139 | 140 | Parsing is currently limited to the keys in the output 141 | :ref:`asn-origin-results-dictionary`. 142 | This is assuming that those fields are present. 143 | 144 | .. _asn-origin-input: 145 | 146 | ASN Origin Input 147 | ================ 148 | 149 | Arguments supported by ASNOrigin.lookup(). 150 | 151 | +------------------------+--------+-------------------------------------------+ 152 | | **Key** |**Type**| **Description** | 153 | +------------------------+--------+-------------------------------------------+ 154 | | asn | str | The autonomous system number (ASN) to | 155 | | | | lookup. May be in format '1234'/'AS1234' | 156 | +------------------------+--------+-------------------------------------------+ 157 | | inc_raw | bool | Whether to include the raw whois results | 158 | | | | in the returned dictionary. Defaults to | 159 | | | | False. | 160 | +------------------------+--------+-------------------------------------------+ 161 | | retry_count | int | The number of times to retry in case | 162 | | | | socket errors, timeouts, connection | 163 | | | | resets, etc. are encountered. | 164 | | | | Defaults to 3. | 165 | +------------------------+--------+-------------------------------------------+ 166 | | response | str | Optional response object, this bypasses | 167 | | | | the Whois lookup. Defaults to None. | 168 | +------------------------+--------+-------------------------------------------+ 169 | | field_list | list | If provided, fields to parse: | 170 | | | | ['description', 'maintainer', 'updated', | 171 | | | | 'source']. If None, defaults to all. | 172 | +------------------------+--------+-------------------------------------------+ 173 | | asn_methods | list | ASN lookup types to attempt, in order. If | 174 | | | | None, defaults to all ['whois', 'http']. | 175 | +------------------------+--------+-------------------------------------------+ 176 | 177 | .. _asn-origin-output: 178 | 179 | ASN Origin Output 180 | ================= 181 | 182 | .. _asn-origin-results-dictionary: 183 | 184 | ASN Origin Results Dictionary 185 | ----------------------------- 186 | 187 | The output dictionary from ASNOrigin.lookup(). 188 | 189 | +------------------+--------+-------------------------------------------------+ 190 | | **Key** |**Type**| **Description** | 191 | +------------------+--------+-------------------------------------------------+ 192 | | query | str | The ASN input | 193 | +------------------+--------+-------------------------------------------------+ 194 | | nets | list | List of network dictionaries. | 195 | | | | See :ref:`asn-origin-network-dictionary`. | 196 | +------------------+--------+-------------------------------------------------+ 197 | | raw | str | Raw ASN origin whois results if inc_raw is True.| 198 | +------------------+--------+-------------------------------------------------+ 199 | 200 | .. _asn-origin-network-dictionary: 201 | 202 | ASN Origin Network Dictionary 203 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 204 | 205 | The dictionary mapped to the nets key in the 206 | :ref:`asn-origin-results-dictionary`. 207 | 208 | +-------------+--------+------------------------------------------------------+ 209 | | **Key** |**Type**| **Description** | 210 | +-------------+--------+------------------------------------------------------+ 211 | | cidr | str | Network routing block an IP address belongs to. | 212 | +-------------+--------+------------------------------------------------------+ 213 | | description | str | Description for a registered network. | 214 | +-------------+--------+------------------------------------------------------+ 215 | | maintainer | str | The entity that maintains this network. | 216 | +-------------+--------+------------------------------------------------------+ 217 | | updated | str | Network registration updated information. | 218 | +-------------+--------+------------------------------------------------------+ 219 | | source | str | The source of this network information. | 220 | +-------------+--------+------------------------------------------------------+ 221 | 222 | .. _asn-origin-usage-examples: 223 | 224 | ASN Origin Usage Examples 225 | ========================= 226 | 227 | Basic usage 228 | ----------- 229 | 230 | .. OUTPUT_ASN_ORIGIN_BASIC START 231 | 232 | :: 233 | 234 | >>>> from ipwhois.net import Net 235 | >>>> from ipwhois.asn import ASNOrigin 236 | >>>> from pprint import pprint 237 | 238 | >>>> net = Net('2001:43f8:7b0::') 239 | >>>> obj = ASNOrigin(net) 240 | >>>> results = obj.lookup(asn='AS37578') 241 | >>>> pprint(results) 242 | 243 | { 244 | "nets": [ 245 | { 246 | "cidr": "196.6.220.0/24", 247 | "description": "KIXP Nairobi Management Network", 248 | "maintainer": "TESPOK-MNT", 249 | "source": "AFRINIC", 250 | "updated": "***@isoc.org 20160720" 251 | }, 252 | { 253 | "cidr": "196.49.22.0/24", 254 | "description": "KIXP Mombasa Management Network", 255 | "maintainer": "TESPOK-MNT", 256 | "source": "AFRINIC", 257 | "updated": "***@tespok.co.ke 20210510" 258 | }, 259 | { 260 | "cidr": "2001:43f8:c1::/48", 261 | "description": "KIXP Mombasa Management Network", 262 | "maintainer": "TESPOK-MNT", 263 | "source": "AFRINIC", 264 | "updated": "***@tespok.co.ke 20201218" 265 | }, 266 | { 267 | "cidr": "2001:43f8:7b0::/48", 268 | "description": "KIXP Nairobi Management Network", 269 | "maintainer": "TESPOK-MNT", 270 | "source": "AFRINIC", 271 | "updated": "***@isoc.org 20160721" 272 | }, 273 | { 274 | "cidr": "2001:43f8:c40::/48", 275 | "description": "KIXP GRX Management", 276 | "maintainer": "TESPOK-MNT", 277 | "source": "AFRINIC", 278 | "updated": "***@tespok.co.ke 20201218" 279 | } 280 | ], 281 | "query": "AS37578", 282 | "raw": None 283 | } 284 | 285 | .. OUTPUT_ASN_ORIGIN_BASIC END 286 | -------------------------------------------------------------------------------- /CHANGES.old.rst: -------------------------------------------------------------------------------- 1 | Changelog (Archive) 2 | =================== 3 | 4 | 0.9.1 (2014-10-14) 5 | ------------------ 6 | 7 | - Added ignore_referral_errors parameter to lookup(). 8 | - Fixed ipaddress import conflicts with alternate ipaddress module. 9 | - Tuned import exception in ipwhois.utils. 10 | - Fixed retry handling in get_whois(). 11 | - Fixed CIDR regex parsing bug where some nets were excluded from the results. 12 | 13 | 0.9.0 (2014-07-27) 14 | ------------------ 15 | 16 | - Fixed order on REST email fields 17 | - Fixed setup error for initial install when dependencies don't exist. 18 | - Added RWhois support. 19 | - Added server and port parameters to IPWhois.get_whois(). 20 | - Added unique_addresses() to ipwhois.utils and unit tests. 21 | - Added some unit tests to test_lookup(). 22 | - Replaced dict.copy() with copy.deepcopy(dict). 23 | - Fixed bug in abuse emails parsing. 24 | - Added handle and range values to returned nets dictionary. 25 | 26 | 0.8.2 (2014-05-12) 27 | ------------------ 28 | 29 | - Fixed multi-line field parsing (Issue #36). 30 | - Added unique_everseen() to ipwhois.utils to fix multi-line field order. 31 | - Re-added support for RIPE RWS now that their API is fixed. 32 | 33 | 0.8.1 (2014-03-05) 34 | ------------------ 35 | 36 | - Fixed encoding error in IPWhois.get_whois(). 37 | 38 | 0.8.0 (2014-02-18) 39 | ------------------ 40 | 41 | - Added ASNRegistryError to handle unknown ASN registry return values. 42 | - Added ASN registry lookup third tier fallback to ARIN. 43 | - Fixed variable naming to avoid shadows built-in confusion. 44 | - Fixed some type errors: Expected type 'str', got 'dict[str, dict]' instead. 45 | - Fixed RIPE RWS links, since they changed their API. 46 | - Temporarily removed RIPE RWS functionality until they fix their API. 47 | - Removed RADB fallback, since RIPE removed it. 48 | 49 | 0.7.0 (2014-01-14) 50 | ------------------ 51 | 52 | - Added Python 2.6+ support. 53 | - The country field in net dicts is now forced uppercase. 54 | 55 | 0.6.0 (2014-01-13) 56 | ------------------ 57 | 58 | - Added APNIC RWS support for IPWhois.lookup_rws(). 59 | - Fixed issue in IPWhois.lookup_rws() for radb-grs fallback. 60 | 61 | 0.5.2 (2013-12-07) 62 | ------------------ 63 | 64 | - Fixed special character issue in countries XML file (Issue #23). 65 | 66 | 0.5.1 (2013-12-03) 67 | ------------------ 68 | 69 | - Moved regex string literal declarations to NIC_WHOIS dict. 70 | - Moved RWS parsing to own private functions. 71 | - Moved base_net dict to global BASE_NET. 72 | - More granular exception handling in lookup functions. 73 | - Fixed email parsing for ARIN and RIPE RWS. 74 | - Changed some 'if key in dict' statements to try/except for slight performance 75 | increase in lookup functions. 76 | - Removed generic exception handling (returned blank dict) on get_countries(). 77 | - More PEP 8 reformatting. 78 | - Minor docstring modifications. 79 | - Added some unit tests to test_lookup() and test_lookup_rws(). 80 | 81 | 0.5.0 (2013-11-20) 82 | ------------------ 83 | 84 | - Reformatting for PEP 8 compliance. 85 | - Added LACNIC RWS (Beta v2) support for IPWhois.lookup_rws(). 86 | 87 | 0.4.0 (2013-10-17) 88 | ------------------ 89 | 90 | - Added support for network registered and updated time stamps (keys: created, 91 | updated). Value in ISO 8601 format. 92 | - Added value assertion to test_utils.py. 93 | - Fixed IPWhois.lookup() handling of processed values. If processing throws 94 | an exception, discard the value and not the net dictionary. 95 | 96 | 0.3.0 (2013-09-30) 97 | ------------------ 98 | 99 | - Fixed get_countries() to work with frozen executables. 100 | - Added dnspython3 rdtypes import to fix issue with frozen executables. 101 | - Moved iso_3166-1_list_en.xml to /data. 102 | - Added retry_count to IPWhois.lookup() and IPWhois.lookup_rws(). 103 | 104 | 0.2.1 (2013-09-27) 105 | ------------------ 106 | 107 | - Fixed LACNIC CIDR validation on IPWhois.lookup(). 108 | - Fixed bug in IPWhois.get_whois() for query rate limiting. This was discovered 109 | via testing multiprocessing with 8+ processes running asynchronously. 110 | 111 | 0.2.0 (2013-09-23) 112 | ------------------ 113 | 114 | - Added support for emails (keys: abuse_emails, tech_emails, misc_emails). 115 | - Changed regex to use group naming for more complex searching. 116 | - Added some missing exception handling in lookup_rws(). 117 | 118 | 0.1.9 (2013-09-18) 119 | ------------------ 120 | 121 | - Added exceptions to import in __init__.py. 122 | - Added IPWhois.__repr__(). 123 | - Moved exceptions to get_*() functions. 124 | - Added exception HostLookupError. 125 | - Various optimizations. 126 | - Added some unit tests. 127 | 128 | 0.1.8 (2013-09-17) 129 | ------------------ 130 | 131 | - Removed set_proxy() in favor of having the user provide their own 132 | urllib.request.OpenerDirector instance as a parameter to IPWhois(). 133 | - Restructured package in favor of modularity. get_countries() is now located 134 | in ipwhois.utils. 135 | - Added exception WhoisLookupError for IPWhois.lookup() and 136 | IPWhois.lookup_rws(). 137 | 138 | 0.1.7 (2013-09-16) 139 | ------------------ 140 | 141 | - Fixed bug in set_proxy(). 142 | - Removed ARIN top level network entries from return dictionary of 143 | IPWhois.lookup_rws(). 144 | - Fixed bug in ARIN RWS parsing when only one network. 145 | 146 | 0.1.6 (2013-09-16) 147 | ------------------ 148 | 149 | - Added IPWhois.get_host() to resolve hostname information. 150 | - Added address and postal_code fields to parsed results. 151 | - Normalized single/double quote use. 152 | 153 | 0.1.5 (2013-09-13) 154 | ------------------ 155 | 156 | - Added set_proxy() function for proxy support in Whois-RWS queries. 157 | - Added IPWhois.lookup_rws() function for Whois-RWS queries. 158 | 159 | 0.1.4 (2013-09-12) 160 | ------------------ 161 | 162 | - Added validity checks for the asn_registry value due to a bug in the Team 163 | Cymru ASN lookup over night. 164 | - Added timeout argument to IPWhois(). This is the default timeout in seconds 165 | for socket connections. 166 | - Fixed decoding issue in IPWhois.get_whois(). 167 | 168 | 0.1.3 (2013-09-11) 169 | ------------------ 170 | 171 | - Added exception handling with query retry support for socket errors, 172 | timeouts, connection resets. 173 | - Moved ASN queries to their own functions (IPWhois.get_asn_dns() and 174 | IPWhois.get_asn_whois()) 175 | - Moved whois query to its own function (IPWhois.get_whois()) 176 | - Country codes are now forced as upper case in the return dictionary. 177 | 178 | 0.1.2 (2013-09-10) 179 | ------------------ 180 | 181 | - Fixed file path for get_countries(). 182 | - Fixed variable names that conflicted with builtins. 183 | - Added content to README. 184 | - Moved CHANGES.txt to CHANGES.rst and added to setup.py. 185 | - Download URL now points to GitHub master tarball. 186 | 187 | 0.1.1 (2013-09-09) 188 | ------------------ 189 | 190 | - Fixed README issue. 191 | 192 | 0.1.0 (2013-09-06) 193 | ------------------ 194 | 195 | - Initial release. -------------------------------------------------------------------------------- /CLI.rst: -------------------------------------------------------------------------------- 1 | === 2 | CLI 3 | === 4 | 5 | ipwhois_cli.py and ipwhois_utils_cli.py are command line interfaces for the 6 | ipwhois library. When using pip to install ipwhois, the CLI scripts are 7 | installed to your Python environment Scripts directory. 8 | 9 | - ipwhois_cli.py has full ipwhois.py functionality. 10 | - ipwhois_utils_cli.py has full utils.py functionality. 11 | - The others (net.py, rdap.py, whois.py, nir.py, asn.py) will be included in a 12 | future release. 13 | 14 | ipwhois_cli.py 15 | ============== 16 | 17 | Usage 18 | ----- 19 | 20 | ipwhois_cli.py [-h] [--whois] [--exclude_nir] [--json] [--hr] 21 | [--show_name] [--colorize] [--timeout TIMEOUT] 22 | [--proxy_http "PROXY_HTTP"] 23 | [--proxy_https "PROXY_HTTPS"] 24 | [--inc_raw] [--retry_count RETRY_COUNT] 25 | [--asn_methods "ASN_METHODS"] 26 | [--extra_org_map "EXTRA_ORG_MAP"] 27 | [--skip_asn_description] [--depth COLOR_DEPTH] 28 | [--excluded_entities "EXCLUDED_ENTITIES"] [--bootstrap] 29 | [--rate_limit_timeout RATE_LIMIT_TIMEOUT] 30 | [--get_referral] [--extra_blacklist "EXTRA_BLACKLIST"] 31 | [--ignore_referral_errors] [--field_list "FIELD_LIST"] 32 | [--nir_field_list "NIR_FIELD_LIST"] --addr "IP" 33 | 34 | ipwhois CLI interface 35 | 36 | optional arguments: 37 | -h, --help show this help message and exit 38 | --whois Retrieve whois data via legacy Whois (port 43) instead 39 | of RDAP (default). 40 | --exclude_nir Disable NIR whois lookups (JPNIC, KRNIC). This is the 41 | opposite of the ipwhois inc_nir, in order to enable 42 | inc_nir by default in the CLI. 43 | --json Output results in JSON format. 44 | 45 | Output options: 46 | --hr If set, returns results with human readable key 47 | translations. 48 | --show_name If this and --hr are set, the key name is shown in 49 | parentheses afterits short value 50 | --colorize If set, colorizes the output using ANSI. Should work 51 | in most platform consoles. 52 | 53 | IPWhois settings: 54 | --timeout TIMEOUT The default timeout for socket connections in seconds. 55 | --proxy_http PROXY_HTTP 56 | The proxy HTTP address passed to request.ProxyHandler. 57 | User auth can be passed like 58 | "http://user:pass@192.168.0.1:80" 59 | --proxy_https PROXY_HTTPS 60 | The proxy HTTPS address passed to 61 | request.ProxyHandler. User auth can be passed like 62 | "https://user:pass@192.168.0.1:443" 63 | 64 | Common settings (RDAP & Legacy Whois): 65 | --inc_raw Include the raw whois results in the output. 66 | --retry_count RETRY_COUNT 67 | The number of times to retry in case socket errors, 68 | timeouts, connection resets, etc. are encountered. 69 | --asn_methods ASN_METHODS 70 | List of ASN lookup types to attempt, in order. 71 | Defaults to all ['dns', 'whois', 'http']. 72 | --extra_org_map EXTRA_ORG_MAP 73 | Dictionary mapping org handles to RIRs. This is for 74 | limited cases where ARIN REST (ASN fallback HTTP 75 | lookup) does not show an RIR as the org handle e.g., 76 | DNIC (which is now the built in ORG_MAP) e.g., 77 | {\"DNIC\": \"arin\"}. Valid RIR values are (note the 78 | case-sensitive - this is meant to match the REST 79 | result): 'ARIN', 'RIPE', 'apnic', 'lacnic', 'afrinic' 80 | --skip_asn_description 81 | Don't run an additional query when pulling ASN 82 | information via dns (to get the ASN description). This 83 | is the opposite of the ipwhois get_asn_description 84 | argument, in order to enable get_asn_description by 85 | default in the CLI. 86 | 87 | RDAP settings: 88 | --depth COLOR_DEPTH If not --whois, how many levels deep to run RDAP 89 | queries when additional referenced objects are found. 90 | --excluded_entities EXCLUDED_ENTITIES 91 | If not --whois, a comma delimited list of entity 92 | handles to not perform lookups. 93 | --bootstrap If not --whois, performs lookups via ARIN bootstrap 94 | rather than lookups based on ASN data. ASN lookups are 95 | not performed and no output for any of the asn* fields 96 | is provided. 97 | --rate_limit_timeout RATE_LIMIT_TIMEOUT 98 | If not --whois, the number of seconds to wait before 99 | retrying when a rate limit notice is returned via 100 | rdap+json. 101 | 102 | Legacy Whois settings: 103 | --get_referral If --whois, retrieve referral whois information, if 104 | available. 105 | --extra_blacklist EXTRA_BLACKLIST 106 | If --whois, A list of blacklisted whois servers in 107 | addition to the global BLACKLIST. 108 | --ignore_referral_errors 109 | If --whois, ignore and continue when an exception is 110 | encountered on referral whois lookups. 111 | --field_list FIELD_LIST 112 | If --whois, a list of fields to parse: ['name', 113 | 'handle', 'description', 'country', 'state', 'city', 114 | 'address', 'postal_code', 'emails', 'created', 115 | 'updated'] 116 | 117 | NIR (National Internet Registry) settings: 118 | --nir_field_list NIR_FIELD_LIST 119 | If not --exclude_nir, a list of fields to parse: 120 | ['name', 'handle', 'country', 'address', 121 | 'postal_code', 'nameservers', 'created', 'updated', 122 | 'contact_admin', 'contact_tech'] 123 | 124 | Input (Required): 125 | --addr IP An IPv4 or IPv6 address as a string. 126 | 127 | Usage Examples 128 | -------------- 129 | 130 | Basic usage 131 | ^^^^^^^^^^^ 132 | 133 | :: 134 | 135 | ipwhois_cli.py --addr 74.125.225.229 --hr --show_name --colorize --depth 1 136 | 137 | ipwhois_utils_cli.py 138 | ==================== 139 | 140 | Usage 141 | ----- 142 | 143 | ipwhois_utils_cli.py [-h] [--ipv4_lstrip_zeros IPADDRESS] 144 | [--calculate_cidr IPADDRESS IPADDRESS] 145 | [--get_countries] [--get_country COUNTRYCODE] 146 | [--ipv4_is_defined IPADDRESS] 147 | [--ipv6_is_defined IPADDRESS] 148 | [--unique_everseen ITERABLE] 149 | [--unique_addresses FILEPATH] [--colorize] 150 | 151 | ipwhois utilities CLI interface 152 | 153 | optional arguments: 154 | -h, --help show this help message and exit 155 | --ipv4_lstrip_zeros IPADDRESS 156 | Strip leading zeros in each octet of an IPv4 address. 157 | --calculate_cidr IPADDRESSRANGE 158 | Calculate a CIDR range(s) from a start and end IP 159 | address. Separate start and end address arguments by 160 | space. 161 | --get_countries Output a dictionary containing ISO_3166-1 country 162 | codes to names. 163 | --get_country COUNTRYCODE 164 | Output the ISO_3166-1 name for a country code. 165 | --ipv4_is_defined IPADDRESS 166 | Check if an IPv4 address is defined (in a reserved 167 | address range). 168 | --ipv6_is_defined IPADDRESS 169 | Check if an IPv6 address is defined (in a reserved 170 | address range). 171 | --ipv4_generate_random TOTAL 172 | Generate random, unique IPv4 addresses that are not 173 | defined (can be looked up using ipwhois). 174 | --ipv6_generate_random TOTAL 175 | Generate random, unique IPv6 addresses that are not 176 | defined (can be looked up using ipwhois). 177 | --unique_everseen ITERABLE 178 | List unique elements from input iterable, preserving 179 | the order. 180 | --unique_addresses FILEPATH 181 | Search an input file, extracting, counting, and 182 | summarizing IPv4/IPv6 addresses/networks. 183 | 184 | Output options: 185 | --colorize If set, colorizes the output using ANSI. Should work 186 | in most platform consoles. 187 | 188 | Usage Examples 189 | -------------- 190 | 191 | ipv4_lstrip_zeros 192 | ^^^^^^^^^^^^^^^^^ 193 | 194 | :: 195 | 196 | >>>> ipwhois_utils_cli.py --ipv4_lstrip_zeros 074.125.025.229 197 | 198 | 74.125.25.229 199 | 200 | calculate_cidr 201 | ^^^^^^^^^^^^^^ 202 | 203 | :: 204 | 205 | >>>> ipwhois_utils_cli.py --calculate_cidr 192.168.0.9 192.168.5.4 206 | 207 | Found 12 CIDR blocks for (192.168.0.9, 192.168.5.4): 208 | 192.168.0.9/32 209 | 192.168.0.10/31 210 | 192.168.0.12/30 211 | 192.168.0.16/28 212 | 192.168.0.32/27 213 | 192.168.0.64/26 214 | 192.168.0.128/25 215 | 192.168.1.0/24 216 | 192.168.2.0/23 217 | 192.168.4.0/24 218 | 192.168.5.0/30 219 | 192.168.5.4/32 220 | 221 | get_countries 222 | ^^^^^^^^^^^^^ 223 | 224 | :: 225 | 226 | >>>> ipwhois_utils_cli.py --get_countries 227 | 228 | Found 252 countries: 229 | AD: Andorra 230 | AE: United Arab Emirates 231 | AF: Afghanistan 232 | AG: Antigua and Barbuda 233 | AI: Anguilla 234 | AL: Albania 235 | AM: Armenia 236 | ... 237 | 238 | get_country 239 | ^^^^^^^^^^^ 240 | 241 | :: 242 | 243 | >>>> ipwhois_utils_cli.py --get_country US 244 | 245 | Match found for country code (US): 246 | United States 247 | 248 | ipv4_is_defined 249 | ^^^^^^^^^^^^^^^ 250 | 251 | :: 252 | 253 | >>>> ipwhois_utils_cli.py --ipv4_is_defined 192.168.0.1 254 | 255 | 192.168.0.1 is defined: 256 | Name: Private-Use Networks 257 | RFC: RFC 1918 258 | 259 | ipv6_is_defined 260 | ^^^^^^^^^^^^^^^ 261 | 262 | :: 263 | 264 | >>>> ipwhois_utils_cli.py --ipv6_is_defined fc00:: 265 | 266 | fc00:: is defined: 267 | Name: Unique Local Unicast 268 | RFC: RFC 4193 269 | 270 | ipv4_generate_random 271 | ^^^^^^^^^^^^^^^^^^^^ 272 | 273 | :: 274 | 275 | >>>> ipwhois_utils_cli.py --ipv4_generate_random 5 276 | 277 | 119.224.47.74 278 | 128.106.183.195 279 | 54.97.0.158 280 | 52.206.105.37 281 | 126.180.201.81 282 | 283 | ipv6_generate_random 284 | ^^^^^^^^^^^^^^^^^^^^ 285 | 286 | :: 287 | 288 | >>>> ipwhois_utils_cli.py --ipv6_generate_random 5 289 | 290 | 3e8c:dc93:49c8:57fd:31dd:2963:6332:426e 291 | 2e3d:fd84:b57b:9282:91e6:5d4d:18d5:34f1 292 | 21d4:9d25:7dd6:e28b:77d7:7ce9:f85f:b34f 293 | 3659:2b9:12ed:1eac:fd40:5756:3753:6d2d 294 | 2e05:6d47:83fd:5de8:c6cb:85cb:912:fdb1 295 | 296 | unique_everseen 297 | ^^^^^^^^^^^^^^^ 298 | 299 | :: 300 | 301 | >>>> ipwhois_utils_cli.py --unique_everseen [4,2,6,4,6,2] 302 | 303 | Unique everseen: 304 | [4, 2, 6] 305 | 306 | unique_addresses 307 | ^^^^^^^^^^^^^^^^ 308 | 309 | :: 310 | 311 | >>>> ipwhois_utils_cli.py --unique_addresses /tmp/some.file 312 | 313 | Found 477 unique addresses: 314 | 74.125.225.229: Count: 5, Ports: {'22': 1} 315 | 2001:4860::/32: Count: 4, Ports: {'443': 1, '80': 2} 316 | 2001:4860:4860::8888: Count: 3, Ports: {} 317 | ... 318 | 319 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | .. note:: 6 | 7 | If you are looking for items to contribute, start by looking at current 8 | open `issues `_ and search the 9 | source code for "TODO" items. 10 | 11 | **************** 12 | Issue submission 13 | **************** 14 | 15 | | Issues are tracked on GitHub: 16 | | https://github.com/secynic/ipwhois/issues 17 | 18 | 19 | Follow the guidelines detailed in the appropriate section below. As a general 20 | rule of thumb, provide as much information as possible when submitting issues. 21 | 22 | Bug reports 23 | =========== 24 | 25 | - Title should be a short, descriptive summary of the bug 26 | - Include the Python and ipwhois versions affected 27 | - Provide a context (with code example) in the description of your issue. What 28 | are you attempting to do? 29 | - Include the full obfuscated output. Make sure to set DEBUG logging: 30 | :: 31 | 32 | import logging 33 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 34 | '[%(funcName)s()] %(message)s') 35 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 36 | 37 | - Include sources of information with links or screenshots 38 | - Do you have a suggestion on how to fix the bug? 39 | 40 | Feature Requests 41 | ================ 42 | 43 | - Title should be a short, descriptive summary of the feature requested 44 | - Provide use case examples 45 | - Include sources of information with links or screenshots 46 | - Do you have a suggestion on how to implement the feature? 47 | 48 | Testing 49 | ======= 50 | 51 | You may have noticed that CI tests are taking longer to complete. 52 | This is due to the enabling of online lookup tests (network tests in the 53 | ipwhois/tests/online directory). 54 | 55 | When running local tests, you may include these tests by adding the 56 | --include=online flag to your nosetests command. 57 | 58 | Example:: 59 | 60 | nosetests -v -w ipwhois --include=online --exclude=stress --with-coverage 61 | --cover-package=ipwhois 62 | 63 | Questions 64 | ========= 65 | 66 | I am happy to answer any questions and provide assistance where possible. 67 | Please be clear and concise. Provide examples when possible. Check the 68 | ipwhois `documentation `_ and the 69 | `issue tracker `_ before asking a 70 | question. 71 | 72 | Questions can be submitted as issues. Past questions can be searched by 73 | filtering the label "question". 74 | 75 | You can also message me on IRC. I am usually idle on freenode in the 76 | `Python channels `_ 77 | 78 | ************* 79 | Pull Requests 80 | ************* 81 | 82 | What to include 83 | =============== 84 | 85 | Aside from the core code changes, it is helpful to provide the following 86 | (where applicable): 87 | 88 | - Unit tests 89 | - Examples 90 | - Sphinx configuration changes in /docs 91 | - Requirements (python2.txt, python3.txt, docs/requirements.txt) 92 | 93 | GitFlow Model 94 | ============= 95 | 96 | This library follows the GitFlow model. As a contributor, this is simply 97 | accomplished by the following steps: 98 | 99 | 1. Create an issue (if there isn't one already) 100 | 2. Branch from dev (not master), try to name your branch to reference the issue 101 | (e.g., issue_123_feature, issue_123_bugfix). 102 | 3. Merge pull requests to dev (not master). Hotfix merges to master will 103 | only be allowed under extreme/time sensitive circumstances. 104 | 105 | Guidelines 106 | ========== 107 | 108 | - Title should be a short, descriptive summary of the changes 109 | - Follow `PEP 8 `_ where possible. 110 | - Follow the `Google docstring style guide 111 | `_ for 112 | comments 113 | - Must be compatible with Python 2.7 and 3.4+ 114 | - Break out reusable code to functions 115 | - Make your code easy to read and comment where necessary 116 | - Reference the GitHub issue number in the description (e.g., Issue #01) 117 | - When running nosetests, make sure to add the following arguments: 118 | :: 119 | 120 | --verbosity=3 --nologcapture --include=online --cover-erase 121 | 122 | If you would like to exclude the aggressive online stress tests, add to the 123 | above: 124 | :: 125 | 126 | --exclude stress 127 | 128 | -------------------------------------------------------------------------------- /EXPERIMENTAL.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Experimental Functions 3 | ====================== 4 | 5 | .. caution:: 6 | 7 | Functions in experimental.py contain new functionality that has not yet 8 | been widely tested. Bulk lookup support contained here can result in 9 | significant system/network resource utilization. Additionally, abuse of 10 | this functionality may get you banned by the various services queried by 11 | this library. Use at your own discretion. 12 | 13 | Bulk ASN Lookups 14 | ================ 15 | 16 | The function for retrieving ASN information for multiple IP addresses from 17 | Cymru via port 43/tcp (WHOIS). 18 | 19 | `ipwhois.experimental.get_bulk_asn_whois() 20 | `_ 22 | 23 | .. _get_bulk_asn_whois-input: 24 | 25 | Input 26 | ----- 27 | 28 | Arguments supported: 29 | 30 | +--------------------+--------+-----------------------------------------------+ 31 | | **Key** |**Type**| **Description** | 32 | +--------------------+--------+-----------------------------------------------+ 33 | | addresses | list | List of IP address strings to lookup. | 34 | +--------------------+--------+-----------------------------------------------+ 35 | | retry_count | int | The number of times to retry in case socket | 36 | | | | errors, timeouts, connection resets, etc. are | 37 | | | | encountered. Defaults to 3. | 38 | +--------------------+--------+-----------------------------------------------+ 39 | | timeout | int | The default timeout for socket connections in | 40 | | | | seconds. Defaults to 120. | 41 | +--------------------+--------+-----------------------------------------------+ 42 | 43 | .. _get_bulk_asn_whois-output: 44 | 45 | Output 46 | ------ 47 | 48 | Outputs a string of the raw ASN bulk data, new line separated. The first line 49 | is obsolete. 50 | 51 | .. _get_bulk_asn_whois-examples: 52 | 53 | Usage Examples 54 | -------------- 55 | 56 | Basic usage 57 | ^^^^^^^^^^^ 58 | 59 | .. GET_BULK_ASN_WHOIS_OUTPUT_BASIC START 60 | 61 | :: 62 | 63 | >>>> from ipwhois.experimental import get_bulk_asn_whois 64 | >>>> from pprint import pprint 65 | 66 | >>>> ip_list = ['74.125.225.229', '2001:4860:4860::8888', '62.239.237.1', '2a00:2381:ffff::1', '210.107.73.73', '2001:240:10c:1::ca20:9d1d', '200.57.141.161', '2801:10:c000::', '196.11.240.215', '2001:43f8:7b0::', '133.1.2.5', '115.1.2.3'] 67 | >>>> results = get_bulk_asn_whois(addresses=ip_list) 68 | >>>> pprint(results.split('\n')) 69 | 70 | [ 71 | "Bulk mode; whois.cymru.com [2024-10-15 05:46:42 +0000]", 72 | "15169 | 74.125.225.229 | 74.125.225.0/24 | US | arin | 2007-03-13 | GOOGLE, US", 73 | "15169 | 2001:4860:4860::8888 | 2001:4860::/32 | US | arin | 2005-03-14 | GOOGLE, US", 74 | "2856 | 62.239.237.1 | 62.239.0.0/16 | GB | ripencc | 2001-01-02 | BT-UK-AS BTnet UK Regional network, GB", 75 | "2856 | 2a00:2381:ffff::1 | 2a00:2380::/25 | GB | ripencc | 2007-08-29 | BT-UK-AS BTnet UK Regional network, GB", 76 | "3786 | 210.107.73.73 | 210.107.0.0/17 | KR | apnic | 1997-08-29 | LGDACOM LG DACOM Corporation, KR", 77 | "2497 | 2001:240:10c:1::ca20:9d1d | 2001:240::/32 | JP | apnic | 2000-03-08 | IIJ Internet Initiative Japan Inc., JP", 78 | "19373 | 200.57.141.161 | 200.57.128.0/20 | MX | lacnic | 2000-12-04 | Triara.com S.A. de C.V., MX", 79 | "264653 | 2801:10:c000:: | 2801:10:c000::/48 | CO | lacnic | 2013-10-29 | Universidad Catolica de Oriente, CO", 80 | "12091 | 196.11.240.215 | 196.11.240.0/24 | ZA | afrinic | 1994-07-21 | MTNNS-1, ZA", 81 | "37578 | 2001:43f8:7b0:: | 2001:43f8:7b0::/48 | KE | afrinic | 2013-03-22 | Tespok, KE", 82 | "4730 | 133.1.2.5 | 133.1.0.0/16 | JP | apnic | 1997-03-01 | ODINS Osaka University, JP", 83 | "4766 | 115.1.2.3 | 115.0.0.0/12 | KR | apnic | 2008-07-01 | KIXS-AS-KR Korea Telecom, KR", 84 | "" 85 | ] 86 | 87 | .. GET_BULK_ASN_WHOIS_OUTPUT_BASIC END 88 | 89 | Bulk RDAP Lookups 90 | ================= 91 | 92 | The function for bulk retrieving and parsing whois information for a list of 93 | IP addresses via HTTP (RDAP). This bulk lookup method uses bulk ASN Whois 94 | lookups first to retrieve the ASN for each IP. It then optimizes RDAP queries 95 | to achieve the fastest overall time, accounting for rate-limiting RIRs. 96 | 97 | `ipwhois.experimental.bulk_lookup_rdap() 98 | `_ 100 | 101 | .. _bulk_lookup_rdap-input: 102 | 103 | Input 104 | ----- 105 | 106 | Arguments supported: 107 | 108 | +--------------------+--------+-----------------------------------------------+ 109 | | **Key** |**Type**| **Description** | 110 | +--------------------+--------+-----------------------------------------------+ 111 | | addresses | list | List of IP address strings to lookup. | 112 | +--------------------+--------+-----------------------------------------------+ 113 | | inc_raw | bool | Whether to include the raw whois results in | 114 | | | | the returned dictionary. Defaults to False. | 115 | +--------------------+--------+-----------------------------------------------+ 116 | | retry_count | int | The number of times to retry in case socket | 117 | | | | errors, timeouts, connection resets, etc. are | 118 | | | | encountered. Defaults to 3. | 119 | +--------------------+--------+-----------------------------------------------+ 120 | | depth | int | How many levels deep to run queries when | 121 | | | | additional referenced objects are found. | 122 | | | | Defaults to 0. | 123 | +--------------------+--------+-----------------------------------------------+ 124 | | excluded_entities | list | Entity handles to not perform lookups. | 125 | | | | Defaults to None. | 126 | +--------------------+--------+-----------------------------------------------+ 127 | | rate_limit_timeout | int | The number of seconds to wait before retrying | 128 | | | | when a rate limit notice isreturned via | 129 | | | | rdap+json. Defaults to 60. | 130 | +--------------------+--------+-----------------------------------------------+ 131 | | socket_timeout | int | The default timeout for socket connections in | 132 | | | | seconds. Defaults to 10. | 133 | +--------------------+--------+-----------------------------------------------+ 134 | | asn_timeout | int | The default timeout for bulk ASN lookups in | 135 | | | | seconds. Defaults to 240. | 136 | +--------------------+--------+-----------------------------------------------+ 137 | | proxy_openers | list | List of urllib.request.OpenerDirector proxy | 138 | | | | openers for single/rotating proxy support. | 139 | | | | Defaults to None. | 140 | +--------------------+--------+-----------------------------------------------+ 141 | 142 | .. _bulk_lookup_rdap-output: 143 | 144 | Output 145 | ------ 146 | 147 | The output namedtuple from ipwhois.experimental.bulk_lookup_rdap(). 148 | 149 | +------------------+--------+-------------------------------------------------+ 150 | | **Key** |**Type**| **Description** | 151 | +------------------+--------+-------------------------------------------------+ 152 | | results | dict | IP address keys with the values as dictionaries | 153 | | | | returned by `IPWhois.lookup_rdap() | 154 | | | | `_ | 156 | +------------------+--------+-------------------------------------------------+ 157 | | stats | dict | Stats for the lookup containing the keys | 158 | | | | identified in :ref:`bulk_lookup_rdap-stats` | 159 | +------------------+--------+-------------------------------------------------+ 160 | 161 | .. _bulk_lookup_rdap-stats: 162 | 163 | Stats Dictionary 164 | ^^^^^^^^^^^^^^^^ 165 | 166 | The stats dictionary returned by ipwhois.experimental.bulk_lookup_rdap() 167 | 168 | :: 169 | 170 | { 171 | 'ip_input_total' (int) - The total number of addresses 172 | originally provided for lookup via the addresses argument. 173 | 'ip_unique_total' (int) - The total number of unique addresses 174 | found in the addresses argument. 175 | 'ip_lookup_total' (int) - The total number of addresses that 176 | lookups were attempted for, excluding any that failed ASN 177 | registry checks. 178 | 'ip_failed_total' (int) - The total number of addresses that 179 | lookups failed for. Excludes any that failed initially, but 180 | succeeded after further retries. 181 | 'lacnic' (dict) - 182 | { 183 | 'failed' (list) - The addresses that failed to lookup. 184 | Excludes any that failed initially, but succeeded after 185 | further retries. 186 | 'rate_limited' (list) - The addresses that encountered 187 | rate-limiting. Unless an address is also in 'failed', 188 | it eventually succeeded. 189 | 'total' (int) - The total number of addresses belonging to 190 | this RIR that lookups were attempted for. 191 | } 192 | 'ripencc' (dict) - Same as 'lacnic' above. 193 | 'apnic' (dict) - Same as 'lacnic' above. 194 | 'afrinic' (dict) - Same as 'lacnic' above. 195 | 'arin' (dict) - Same as 'lacnic' above. 196 | 'unallocated_addresses' (list) - The addresses that are 197 | unallocated/failed ASN lookups. These can be addresses that 198 | are not listed for one of the 5 RIRs (other). No attempt 199 | was made to perform an RDAP lookup for these. 200 | } 201 | 202 | .. _bulk_lookup_rdap-examples: 203 | 204 | Usage Examples 205 | -------------- 206 | 207 | Basic usage 208 | ^^^^^^^^^^^ 209 | 210 | .. BULK_LOOKUP_RDAP_OUTPUT_BASIC START 211 | 212 | :: 213 | 214 | >>>> from ipwhois.experimental import bulk_lookup_rdap 215 | >>>> from pprint import pprint 216 | 217 | >>>> ip_list = ['74.125.225.229', '2001:4860:4860::8888', '62.239.237.1', '2a00:2381:ffff::1', '210.107.73.73', '2001:240:10c:1::ca20:9d1d', '200.57.141.161', '2801:10:c000::', '196.11.240.215', '2001:43f8:7b0::', '133.1.2.5', '115.1.2.3'] 218 | >>>> results, stats = bulk_lookup_rdap(addresses=ip_list) 219 | >>>> pprint(stats) 220 | 221 | { 222 | "afrinic": { 223 | "failed": [], 224 | "rate_limited": [], 225 | "total": 2 226 | }, 227 | "apnic": { 228 | "failed": [], 229 | "rate_limited": [], 230 | "total": 4 231 | }, 232 | "arin": { 233 | "failed": [], 234 | "rate_limited": [], 235 | "total": 2 236 | }, 237 | "ip_failed_total": 0, 238 | "ip_input_total": 12, 239 | "ip_lookup_total": 12, 240 | "ip_unique_total": 12, 241 | "lacnic": { 242 | "failed": [], 243 | "rate_limited": [], 244 | "total": 2 245 | }, 246 | "ripencc": { 247 | "failed": [], 248 | "rate_limited": [], 249 | "total": 2 250 | }, 251 | "unallocated_addresses": [] 252 | } 253 | 254 | .. BULK_LOOKUP_RDAP_OUTPUT_BASIC END 255 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2024 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, this 8 | 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" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt *.rst 2 | recursive-include ipwhois *.xml *.csv 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | ipwhois 3 | ======= 4 | 5 | .. image:: https://coveralls.io/repos/github/secynic/ipwhois/badge.svg?branch= 6 | master 7 | :target: https://coveralls.io/github/secynic/ipwhois?branch=master 8 | .. image:: https://img.shields.io/github/issues-raw/secynic/ipwhois 9 | :target: https://github.com/secynic/ipwhois/issues 10 | .. image:: https://codeclimate.com/github/secynic/ipwhois/badges/issue_count.svg 11 | :target: https://codeclimate.com/github/secynic/ipwhois 12 | .. image:: https://img.shields.io/badge/license-BSD%202--Clause-blue.svg 13 | :target: https://github.com/secynic/ipwhois/tree/master/LICENSE.txt 14 | .. image:: https://img.shields.io/badge/python-2.7%2C%203.4+-blue.svg 15 | :target: https://docs.python.org 16 | .. image:: https://img.shields.io/badge/docs-latest-green.svg?style=flat 17 | :target: https://ipwhois.readthedocs.io/en/latest 18 | .. image:: https://img.shields.io/badge/docs-dev-yellow.svg?style=flat 19 | :target: https://ipwhois.readthedocs.io/en/dev 20 | 21 | Summary 22 | ======= 23 | 24 | ipwhois is a Python package focused on retrieving and parsing whois data 25 | for IPv4 and IPv6 addresses. 26 | 27 | .. note:: 28 | 29 | If you are experiencing latency issues, it is likely related to rate 30 | limiting. Rate limiting is based on your source IP, which may be a problem 31 | with multiple users behind the same proxy. Additionally, LACNIC implements 32 | aggressive rate limiting. Experimental bulk query support is new as of 33 | v1.0.0. 34 | 35 | Features 36 | ======== 37 | 38 | * Parses a majority of whois fields in to a standard dictionary 39 | * IPv4 and IPv6 support 40 | * Supports RDAP queries (recommended method, see: 41 | https://tools.ietf.org/html/rfc7483) 42 | * Proxy support for RDAP queries 43 | * Supports legacy whois protocol queries 44 | * Referral whois support for legacy whois protocol 45 | * Recursive network parsing for IPs with parent/children networks listed 46 | * National Internet Registry support for JPNIC and KRNIC 47 | * Supports IP to ASN and ASN origin queries 48 | * Python 2.7 and 3.4+ supported 49 | * Useful set of utilities 50 | * Experimental bulk query support 51 | * BSD license 52 | * Human readable field translations 53 | * Full CLI for IPWhois with optional ANSI colored console output. 54 | 55 | Links 56 | ===== 57 | 58 | Documentation 59 | ------------- 60 | 61 | GitHub latest 62 | ^^^^^^^^^^^^^ 63 | 64 | https://ipwhois.readthedocs.io/en/latest 65 | 66 | GitHub dev 67 | ^^^^^^^^^^ 68 | 69 | https://ipwhois.readthedocs.io/en/dev 70 | 71 | Examples 72 | -------- 73 | 74 | https://github.com/secynic/ipwhois/tree/master/ipwhois/examples 75 | 76 | Github 77 | ------ 78 | 79 | https://github.com/secynic/ipwhois 80 | 81 | Pypi 82 | ---- 83 | 84 | https://pypi.org/project/ipwhois 85 | 86 | Changes 87 | ------- 88 | 89 | https://ipwhois.readthedocs.io/en/latest/CHANGES.html 90 | 91 | Upgrade Notes 92 | ------------- 93 | 94 | https://ipwhois.readthedocs.io/en/latest/UPGRADING.html 95 | 96 | Dependencies 97 | ============ 98 | 99 | Python 2.7:: 100 | 101 | dnspython 102 | ipaddr 103 | defusedxml 104 | 105 | Python 3.4+:: 106 | 107 | dnspython 108 | defusedxml 109 | 110 | Installing 111 | ========== 112 | 113 | Latest release from PyPi:: 114 | 115 | pip install --upgrade ipwhois 116 | 117 | GitHub - Stable:: 118 | 119 | pip install -e git+https://github.com/secynic/ipwhois@master#egg=ipwhois 120 | 121 | GitHub - Dev:: 122 | 123 | pip install -e git+https://github.com/secynic/ipwhois@dev#egg=ipwhois 124 | 125 | Firewall Ports 126 | ============== 127 | 128 | ipwhois needs some outbound firewall ports opened from your host/server. 129 | 130 | :ASN (DNS): 53/tcp 131 | :ASN (Whois): 43/tcp 132 | :ASN (HTTP): 133 | 80/tcp 134 | 135 | 443/tcp (Pending) 136 | :RDAP (HTTP): 137 | 80/tcp 138 | 139 | 443/tcp (Pending) 140 | :NIR (HTTP): 141 | 80/tcp 142 | 143 | 443/tcp (KRNIC) 144 | :Legacy Whois: 43/tcp 145 | :Get Host: 43/tcp 146 | 147 | API 148 | === 149 | 150 | IPWhois (main class) 151 | -------------------- 152 | 153 | ipwhois.IPWhois is the base class for wrapping RDAP and Legacy Whois lookups. 154 | Instantiate this object, then call one of the lookup functions: 155 | 156 | `RDAP (HTTP) - IPWhois.lookup_rdap() <#rdap-http>`_ 157 | OR 158 | `Legacy Whois - IPWhois.lookup_whois() <#legacy-whois>`_ 159 | 160 | Input 161 | ^^^^^ 162 | 163 | +--------------------+--------+-----------------------------------------------+ 164 | | **Key** |**Type**| **Description** | 165 | +--------------------+--------+-----------------------------------------------+ 166 | | address | str | An IPv4 or IPv6 address as a string, integer, | 167 | | | | IPv4Address, or IPv6Address. | 168 | +--------------------+--------+-----------------------------------------------+ 169 | | timeout | int | The default timeout for socket connections | 170 | | | | in seconds. Defaults to 5. | 171 | +--------------------+--------+-----------------------------------------------+ 172 | | proxy_opener | object | The urllib.request.OpenerDirector request for | 173 | | | | proxy support or None. | 174 | +--------------------+--------+-----------------------------------------------+ 175 | 176 | RDAP (HTTP) 177 | ----------- 178 | 179 | IPWhois.lookup_rdap() is the recommended lookup method. RDAP provides a 180 | far better data structure than legacy whois and REST lookups (previous 181 | implementation). RDAP queries allow for parsing of contact information and 182 | details for users, organizations, and groups. RDAP also provides more detailed 183 | network information. 184 | 185 | RDAP documentation: 186 | 187 | https://ipwhois.readthedocs.io/en/latest/RDAP.html 188 | 189 | Legacy Whois 190 | ------------ 191 | 192 | .. note:: 193 | 194 | Legacy Whois output is different from RDAP. See the below JSON outputs for 195 | a comparison: 196 | 197 | Legacy Whois: 198 | https://ipwhois.readthedocs.io/en/latest/WHOIS.html#basic-usage 199 | 200 | RDAP: 201 | https://ipwhois.readthedocs.io/en/latest/RDAP.html#basic-usage 202 | 203 | Legacy Whois documentation: 204 | 205 | https://ipwhois.readthedocs.io/en/latest/WHOIS.html 206 | 207 | National Internet Registries 208 | ---------------------------- 209 | 210 | This library now supports NIR lookups for JPNIC and KRNIC. Previously, Whois 211 | and RDAP data for Japan and South Korea was restricted. NIR lookups scrape 212 | these national registries directly for the data restricted from regional 213 | internet registries. NIR queries are enabled by default via the inc_nir 214 | argument in the IPWhois.lookup_*() functions. 215 | 216 | https://ipwhois.readthedocs.io/en/latest/NIR.html 217 | 218 | Autonomous System Numbers 219 | ------------------------- 220 | 221 | This library now supports ASN origin lookups via Whois and HTTP. 222 | 223 | IP ASN functionality was moved to its own parser API (IPASN). 224 | 225 | There is no CLI for these yet. 226 | 227 | https://ipwhois.readthedocs.io/en/latest/ASN.html 228 | 229 | Utilities 230 | --------- 231 | 232 | Utilities documentation: 233 | 234 | https://ipwhois.readthedocs.io/en/latest/UTILS.html 235 | 236 | Scripts 237 | ------- 238 | 239 | CLI documentation: 240 | 241 | https://ipwhois.readthedocs.io/en/latest/CLI.html 242 | 243 | Experimental Functions 244 | ---------------------- 245 | 246 | .. caution:: 247 | 248 | Functions in experimental.py contain new functionality that has not yet 249 | been widely tested. Bulk lookup support contained here can result in 250 | significant system/network resource utilization. Additionally, abuse of 251 | this functionality may get you banned by the various services queried by 252 | this library. Use at your own discretion. 253 | 254 | Experimental functions documentation: 255 | 256 | https://ipwhois.readthedocs.io/en/latest/EXPERIMENTAL.html 257 | 258 | Contributing 259 | ============ 260 | 261 | https://ipwhois.readthedocs.io/en/latest/CONTRIBUTING.html 262 | 263 | IP Reputation Support 264 | ===================== 265 | 266 | This feature is under consideration. Take a look at TekDefense's Automater: 267 | 268 | `TekDefense-Automater `_ 269 | 270 | Domain Support 271 | ============== 272 | 273 | There are no plans for domain whois support in this project. 274 | 275 | Look at Sven Slootweg's 276 | `python-whois `_ for a library with 277 | domain support. 278 | 279 | Special Thanks 280 | ============== 281 | 282 | Thank you JetBrains for the `PyCharm `_ 283 | open source support! 284 | 285 | Thank you Chris Wells (`@cdubz `_) for your 286 | extensive testing on the experimental functions! 287 | 288 | Last but not least, thank you to all the issue submitters and contributors. 289 | -------------------------------------------------------------------------------- /UPGRADING.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Upgrade Notes 3 | ============= 4 | 5 | Version upgrade notes, warnings, and critical changes will be displayed here. 6 | This does not supplement the changelog, but serves to provide information on 7 | any changes that may affect user experience when upgrading to a new release. 8 | 9 | This page is new as of version 1.0.0. Any information on older versions is 10 | likely missing or incomplete. 11 | 12 | ****** 13 | v1.3.0 14 | ****** 15 | 16 | - Upgrade dnspython 17 | - Added requirement for defusedxml for security 18 | - Added support for Python up to 3.12 19 | - Remove reserved IANA 198.97.38.0/24 20 | - Fix for ASN queries against RADB for RIPE source 21 | - Temporary (move to config later) hardcoding of user agent for ASN origin 22 | lookup to avoid 403 23 | - Updated to HTTPS where applicable 24 | - Fix for local domain searches with ASN 25 | - Added get_recursive argument (-r) for IPWhois.lookup_whois() and 26 | ipwhois.net.Net.get_whois() 27 | 28 | ****** 29 | v1.2.0 30 | ****** 31 | 32 | - Removed deprecated functions: asn.IPASN._parse_fields_http, 33 | asn.IPASN._parse_fields_dns, asn.IPASN._parse_fields_whois, 34 | asn.ASNOrigin._parse_fields, asn.ASNOrigin._get_nets_radb, 35 | net.Net.lookup_asn, whois.Whois._parse_fields, whois.Whois._get_nets_arin 36 | whois.Whois._get_nets_lacnic, whois.Whois._get_nets_other, 37 | nir.NIRWhois._parse_fields, nir.NIRWhois._get_nets_jpnic 38 | nir.NIRWhois._get_nets_krnic, nir.NIRWhois._get_contact 39 | - Removed deprecated asn_alts parameter 40 | - Removed deprecated allow_permutations parameter 41 | - Added new argument root_ent_check to IPWhois.lookup_rdap and 42 | RDAP.lookup. Set this to False to revert to old functionality - missing data, 43 | but less queries. If you leave this set to default of True, you will notice 44 | more queries and potentially more rate-limiting. 45 | - Added support for Python 3.8 46 | - Pinned requirements 47 | 48 | ****** 49 | v1.1.0 50 | ****** 51 | 52 | - Exceptions now inherit a new BaseIpwhoisException rather than Exception 53 | - Removed support for Python 2.6/3.3, added support for 3.7 54 | - Removed the disallow_permutations argument from ipwhois_cli.py. Use 55 | ans_methods instead. 56 | - Fixed deprecation warnings in core code 57 | 58 | ****** 59 | v1.0.0 60 | ****** 61 | 62 | - Removed deprecated IPWhois.lookup() - This was moved to 63 | IPWhois.lookup_whois() 64 | - HTTPS (port 443) requirement added for KRNIC lookups. 65 | - Experimental bulk functions added: experimental.get_bulk_asn_whois and 66 | experimental.bulk_lookup_rdap. 67 | - Added new return key asn_description to net.Net.get_asn_whois, 68 | experimental.get_bulk_asn_whois, and hr.py. New argument get_asn_description. 69 | - The IPWhois argument allow_permutations and the lookup argument asn_alts 70 | have been deprecated in favor of new argument asn_methods. 71 | - Deprecated unnecessary protected class functions, changed to public in 72 | asn.py, nir.py, and whois.py (#184): asn.IPASN._parse_fields_dns, 73 | asn.IPASN._parse_fields_whois, asn.IPASN._parse_fields_http, 74 | asn.ASNOrigin._parse_fields, asn.ASNOrigin._get_nets_radb, 75 | nir.NIRWhois._parse_fields, nir.NIRWhois._get_nets_jpnic, 76 | nir.NIRWhois._get_nets_krnic, nir.NIRWhois._get_contact, 77 | whois.Whois._parse_fields, whois.Whois._get_nets_arin, 78 | whois.Whois._get_nets_lacnic, whois.Whois._get_nets_other 79 | - New IP generators added: utils.ipv4_generate_random and 80 | utils.ipv6_generate_random 81 | - net.Net.get_host(), utils.ipv4_is_defined(), and utils.ipv6_is_defined now 82 | return namedtuple instead of tuple. 83 | - net.Net.get_asn_dns now returns a list rather than a str 84 | 85 | ******* 86 | v0.14.0 87 | ******* 88 | 89 | - NIR (National Internet Registry) lookups are enabled by default. This is 90 | currently only performed for JPNIC and KRNIC addresses. To disable, 91 | set inc_nir=False in your IPWhois.lookup_*() query. 92 | - The 'nets' -> 'emails' key in IPWhois.lookup_whois() was changed from a 93 | '\\n' separated string to a list. 94 | 95 | ******* 96 | v0.11.0 97 | ******* 98 | 99 | - The new RDAP return format was introduced and split off from the legacy 100 | whois return format. Using RDAP lookup (IPWhois.lookup_rdap()) is now the 101 | recommended method to maximize indexable values. RDAP return data is 102 | different in nearly every way from the legacy whois data. For information on 103 | raw RDAP responses, please see the RFC: https://tools.ietf.org/html/rfc7483 -------------------------------------------------------------------------------- /UTILS.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Utilities 3 | ========= 4 | 5 | Many useful utilities are provided for IP addresses outside of whois 6 | functionality. The following utilities are used throughout the ipwhois library 7 | for validation and parsing. 8 | 9 | Country Codes 10 | ============= 11 | 12 | The legacy country code listing (iso_3166-1_list_en.xml) is no longer 13 | available as a free export from iso.org. Support has been added for 14 | iso_3166-1.csv, which is now the default. 15 | 16 | Use Legacy XML File:: 17 | 18 | >>>> from ipwhois.utils import get_countries 19 | >>>> countries = get_countries(is_legacy_xml=True) 20 | 21 | Human Readable Fields 22 | ===================== 23 | 24 | Human readable translations are available for all result fields (RDAP and 25 | Legacy Whois). Translations are currently limited to the short name (_short), 26 | the name (_name), and the description (_description). 27 | 28 | See the ipwhois CLI (ipwhois_utils_cli.py) for an example. 29 | 30 | Import the human readable translation dictionaries 31 | 32 | :: 33 | 34 | >>>> from ipwhois.hr import (HR_ASN, HR_ASN_ORIGIN, HR_RDAP_COMMON, 35 | HR_RDAP, HR_WHOIS, HR_WHOIS_NIR) 36 | 37 | Usage Examples 38 | ============== 39 | 40 | IPv4 Strip Zeros 41 | ---------------- 42 | Strip leading zeros in each octet of an IPv4 address string. 43 | 44 | :: 45 | 46 | >>>> from ipwhois.utils import ipv4_lstrip_zeros 47 | >>>> print(ipv4_lstrip_zeros('074.125.025.229')) 48 | 49 | 74.125.25.229 50 | 51 | CIDR Calculation 52 | ---------------- 53 | Get a list of CIDR range(s) from a start and end IP address. 54 | 55 | :: 56 | 57 | >>>> from ipwhois.utils import calculate_cidr 58 | >>>> print(calculate_cidr('192.168.0.9', '192.168.5.4')) 59 | 60 | ['192.168.0.9/32', '192.168.0.10/31', '192.168.0.12/30', '192.168.0.16/28', 61 | '192.168.0.32/27', '192.168.0.64/26', '192.168.0.128/25', '192.168.1.0/24', 62 | '192.168.2.0/23', '192.168.4.0/24', '192.168.5.0/30', '192.168.5.4/32'] 63 | 64 | Check if IP is reserved/defined 65 | ------------------------------- 66 | Check if an IPv4 or IPv6 address is in a reserved/defined pool. 67 | 68 | :: 69 | 70 | >>>> from ipwhois.utils import (ipv4_is_defined, ipv6_is_defined) 71 | >>>> print(ipv4_is_defined('192.168.0.1')) 72 | 73 | (True, 'Private-Use Networks', 'RFC 1918') 74 | 75 | >>>> print(ipv6_is_defined('fe80::')) 76 | 77 | (True, 'Link-Local', 'RFC 4291, Section 2.5.6') 78 | 79 | Country Code Mapping 80 | -------------------- 81 | Retrieve a dictionary mapping ISO 3166-1 country codes to country names. 82 | 83 | :: 84 | 85 | >>>> from ipwhois import IPWhois 86 | >>>> from ipwhois.utils import get_countries 87 | 88 | >>>> countries = get_countries() 89 | >>>> obj = IPWhois('74.125.225.229') 90 | >>>> results = obj.lookup_whois(False) 91 | >>>> print(countries[results['nets'][0]['country']]) 92 | 93 | United States 94 | 95 | Iterable to unique elements (order preserved) 96 | --------------------------------------------- 97 | List unique elements, preserving the order. This was taken from the itertools 98 | recipes. 99 | 100 | :: 101 | 102 | >>>> from ipwhois.utils import unique_everseen 103 | >>>> print(list(unique_everseen(['b', 'a', 'b', 'a', 'c', 'a', 'b', 'c'))) 104 | 105 | ['b', 'a', 'c'] 106 | 107 | Parse IPs/ports from text/file 108 | ------------------------------ 109 | Search an input string and/or file, extracting and counting IPv4/IPv6 110 | addresses/networks. Summarizes ports with sub-counts. 111 | 112 | :: 113 | 114 | >>>> from ipwhois.utils import unique_addresses 115 | >>>> from pprint import pprint 116 | 117 | >>>> input_data = ( 118 | 'You can have IPs like 74.125.225.229, or 2001:4860:4860::8888' 119 | 'Put a port at the end 74.125.225.229:80 or for IPv6: ' 120 | '[2001:4860:4860::8888]:443 or even networks like ' 121 | '74.125.0.0/16 and 2001:4860::/32.' 122 | ) 123 | 124 | >>>> results = unique_addresses(data=input_data, file_path=None) 125 | >>>> pprint(results) 126 | 127 | {'2001:4860:4860::8888': {'count': 2, 'ports': {'443': 1}}, 128 | '2001:4860::/32': {'count': 1, 'ports': {}}, 129 | '74.125.0.0/16': {'count': 1, 'ports': {}}, 130 | '74.125.225.229': {'count': 2, 'ports': {'80': 1}}} 131 | 132 | Generate random IP addresses 133 | ---------------------------- 134 | Generate random, unique IPv4/IPv6 addresses that are not defined (can be 135 | looked up using ipwhois). 136 | 137 | :: 138 | 139 | >>>> from ipwhois.utils import ipv4_generate_random 140 | >>>> for address in ipv4_generate_random(10): 141 | >>>> print(address) 142 | 143 | 71.58.89.10 144 | 17.206.180.200 145 | 156.94.166.94 146 | 36.92.169.70 147 | 52.214.0.208 148 | 174.254.156.179 149 | 33.184.228.52 150 | 17.58.3.61 151 | 101.151.158.16 152 | 61.162.38.154 153 | 154 | >>>> from ipwhois.utils import ipv6_generate_random 155 | >>>> for address in ipv6_generate_random(10): 156 | >>>> print(address) 157 | 158 | 218e:a9ad:aae4:431c:ff16:eb94:f063:47f7 159 | 24ba:3185:a26f:fd30:5756:16d5:b4ab:771b 160 | 38ad:f797:360a:d98e:4f3b:b1c8:5811:8425 161 | 2c0e:9add:6b48:96c4:d22:2674:8067:2de9 162 | 3b72:414b:c387:4650:c4a6:eed3:21a8:ba9b 163 | 3d24:4053:dd81:d269:2cdc:91c9:b0f8:830e 164 | 32a4:8ef8:807:1bf0:e866:c8d7:d69e:2a52 165 | 2a2b:eb87:d368:89ee:6861:555:32c6:d552 166 | 2ee6:5445:f1ff:b1c6:d68f:3ee1:1e31:fe34 167 | 2c6b:393f:ae7:a0f7:1c2:2e19:bab1:af9c 168 | 169 | -------------------------------------------------------------------------------- /ipwhois/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2024 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 | 25 | from .exceptions import * 26 | from .net import Net 27 | from .ipwhois import IPWhois 28 | 29 | __version__ = '1.3.0' 30 | -------------------------------------------------------------------------------- /ipwhois/data/iso_3166-1.csv: -------------------------------------------------------------------------------- 1 | AD,Andorra, 2 | AE,United Arab Emirates, 3 | AF,Afghanistan, 4 | AG,Antigua and Barbuda, 5 | AI,Anguilla, 6 | AL,Albania, 7 | AM,Armenia, 8 | AN,Netherlands Antilles, 9 | AO,Angola, 10 | AP,"Asia/Pacific Region", 11 | AQ,Antarctica, 12 | AR,Argentina, 13 | AS,American Samoa, 14 | AT,Austria, 15 | AU,Australia, 16 | AW,Aruba, 17 | AX,Aland Islands, 18 | AZ,Azerbaijan, 19 | BA,Bosnia and Herzegovina, 20 | BB,Barbados, 21 | BD,Bangladesh, 22 | BE,Belgium, 23 | BF,Burkina Faso, 24 | BG,Bulgaria, 25 | BH,Bahrain, 26 | BI,Burundi, 27 | BJ,Benin, 28 | BL,Saint Bartelemey, 29 | BM,Bermuda, 30 | BN,Brunei Darussalam, 31 | BO,Bolivia, 32 | BQ,"Bonaire, Saint Eustatius and Saba", 33 | BR,Brazil, 34 | BS,Bahamas, 35 | BT,Bhutan, 36 | BV,Bouvet Island, 37 | BW,Botswana, 38 | BY,Belarus, 39 | BZ,Belize, 40 | CA,Canada, 41 | CC,Cocos (Keeling) Islands, 42 | CD,"Congo, The Democratic Republic of the", 43 | CF,Central African Republic, 44 | CG,Congo, 45 | CH,Switzerland, 46 | CI,Cote d'Ivoire, 47 | CK,Cook Islands, 48 | CL,Chile, 49 | CM,Cameroon, 50 | CN,China, 51 | CO,Colombia, 52 | CR,Costa Rica, 53 | CU,Cuba, 54 | CV,Cape Verde, 55 | CW,Curacao, 56 | CX,Christmas Island, 57 | CY,Cyprus, 58 | CZ,Czech Republic, 59 | DE,Germany, 60 | DJ,Djibouti, 61 | DK,Denmark, 62 | DM,Dominica, 63 | DO,Dominican Republic, 64 | DZ,Algeria, 65 | EC,Ecuador, 66 | EE,Estonia, 67 | EG,Egypt, 68 | EH,Western Sahara, 69 | ER,Eritrea, 70 | ES,Spain, 71 | ET,Ethiopia, 72 | EU,Europe, 73 | FI,Finland, 74 | FJ,Fiji, 75 | FK,Falkland Islands (Malvinas), 76 | FM,"Micronesia, Federated States of", 77 | FO,Faroe Islands, 78 | FR,France, 79 | GA,Gabon, 80 | GB,United Kingdom, 81 | GD,Grenada, 82 | GE,Georgia, 83 | GF,French Guiana, 84 | GG,Guernsey, 85 | GH,Ghana, 86 | GI,Gibraltar, 87 | GL,Greenland, 88 | GM,Gambia, 89 | GN,Guinea, 90 | GP,Guadeloupe, 91 | GQ,Equatorial Guinea, 92 | GR,Greece, 93 | GS,South Georgia and the South Sandwich Islands, 94 | GT,Guatemala, 95 | GU,Guam, 96 | GW,Guinea-Bissau, 97 | GY,Guyana, 98 | HK,Hong Kong, 99 | HM,Heard Island and McDonald Islands, 100 | HN,Honduras, 101 | HR,Croatia, 102 | HT,Haiti, 103 | HU,Hungary, 104 | ID,Indonesia, 105 | IE,Ireland, 106 | IL,Israel, 107 | IM,Isle of Man, 108 | IN,India, 109 | IO,British Indian Ocean Territory, 110 | IQ,Iraq, 111 | IR,"Iran, Islamic Republic of", 112 | IS,Iceland, 113 | IT,Italy, 114 | JE,Jersey, 115 | JM,Jamaica, 116 | JO,Jordan, 117 | JP,Japan, 118 | KE,Kenya, 119 | KG,Kyrgyzstan, 120 | KH,Cambodia, 121 | KI,Kiribati, 122 | KM,Comoros, 123 | KN,Saint Kitts and Nevis, 124 | KP,"Korea, Democratic People's Republic of", 125 | KR,"Korea, Republic of", 126 | KW,Kuwait, 127 | KY,Cayman Islands, 128 | KZ,Kazakhstan, 129 | LA,Lao People's Democratic Republic, 130 | LB,Lebanon, 131 | LC,Saint Lucia, 132 | LI,Liechtenstein, 133 | LK,Sri Lanka, 134 | LR,Liberia, 135 | LS,Lesotho, 136 | LT,Lithuania, 137 | LU,Luxembourg, 138 | LV,Latvia, 139 | LY,Libyan Arab Jamahiriya, 140 | MA,Morocco, 141 | MC,Monaco, 142 | MD,"Moldova, Republic of", 143 | ME,Montenegro, 144 | MF,Saint Martin, 145 | MG,Madagascar, 146 | MH,Marshall Islands, 147 | MK,Macedonia, 148 | ML,Mali, 149 | MM,Myanmar, 150 | MN,Mongolia, 151 | MO,Macao, 152 | MP,Northern Mariana Islands, 153 | MQ,Martinique, 154 | MR,Mauritania, 155 | MS,Montserrat, 156 | MT,Malta, 157 | MU,Mauritius, 158 | MV,Maldives, 159 | MW,Malawi, 160 | MX,Mexico, 161 | MY,Malaysia, 162 | MZ,Mozambique, 163 | NA,Namibia, 164 | NC,New Caledonia, 165 | NE,Niger, 166 | NF,Norfolk Island, 167 | NG,Nigeria, 168 | NI,Nicaragua, 169 | NL,Netherlands, 170 | NO,Norway, 171 | NP,Nepal, 172 | NR,Nauru, 173 | NU,Niue, 174 | NZ,New Zealand, 175 | OM,Oman, 176 | PA,Panama, 177 | PE,Peru, 178 | PF,French Polynesia, 179 | PG,Papua New Guinea, 180 | PH,Philippines, 181 | PK,Pakistan, 182 | PL,Poland, 183 | PM,Saint Pierre and Miquelon, 184 | PN,Pitcairn, 185 | PR,Puerto Rico, 186 | PS,Palestinian Territory, 187 | PT,Portugal, 188 | PW,Palau, 189 | PY,Paraguay, 190 | QA,Qatar, 191 | RE,Reunion, 192 | RO,Romania, 193 | RS,Serbia, 194 | RU,Russian Federation, 195 | RW,Rwanda, 196 | SA,Saudi Arabia, 197 | SB,Solomon Islands, 198 | SC,Seychelles, 199 | SD,Sudan, 200 | SE,Sweden, 201 | SG,Singapore, 202 | SH,Saint Helena, 203 | SI,Slovenia, 204 | SJ,Svalbard and Jan Mayen, 205 | SK,Slovakia, 206 | SL,Sierra Leone, 207 | SM,San Marino, 208 | SN,Senegal, 209 | SO,Somalia, 210 | SR,Suriname, 211 | SS,South Sudan, 212 | ST,Sao Tome and Principe, 213 | SV,El Salvador, 214 | SX,Sint Maarten, 215 | SY,Syrian Arab Republic, 216 | SZ,Swaziland, 217 | TC,Turks and Caicos Islands, 218 | TD,Chad, 219 | TF,French Southern Territories, 220 | TG,Togo, 221 | TH,Thailand, 222 | TJ,Tajikistan, 223 | TK,Tokelau, 224 | TL,Timor-Leste, 225 | TM,Turkmenistan, 226 | TN,Tunisia, 227 | TO,Tonga, 228 | TR,Turkey, 229 | TT,Trinidad and Tobago, 230 | TV,Tuvalu, 231 | TW,Taiwan, 232 | TZ,"Tanzania, United Republic of", 233 | UA,Ukraine, 234 | UG,Uganda, 235 | UM,United States Minor Outlying Islands, 236 | US,United States, 237 | UY,Uruguay, 238 | UZ,Uzbekistan, 239 | VA,Holy See (Vatican City State), 240 | VC,Saint Vincent and the Grenadines, 241 | VE,Venezuela, 242 | VG,"Virgin Islands, British", 243 | VI,"Virgin Islands, U.S.", 244 | VN,Vietnam, 245 | VU,Vanuatu, 246 | WF,Wallis and Futuna, 247 | WS,Samoa, 248 | YE,Yemen, 249 | YT,Mayotte, 250 | ZA,South Africa, 251 | ZM,Zambia, 252 | ZW,Zimbabwe, -------------------------------------------------------------------------------- /ipwhois/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = ../../ipwhois-docs 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ipwhois.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ipwhois.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/ipwhois" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ipwhois" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /ipwhois/docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ipwhois.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ipwhois.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /ipwhois/docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | dnspython 4 | defusedxml 5 | -------------------------------------------------------------------------------- /ipwhois/docs/source/ASN.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../ASN.rst -------------------------------------------------------------------------------- /ipwhois/docs/source/CHANGES.old.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../CHANGES.old.rst -------------------------------------------------------------------------------- /ipwhois/docs/source/CHANGES.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../CHANGES.rst -------------------------------------------------------------------------------- /ipwhois/docs/source/CLI.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../CLI.rst -------------------------------------------------------------------------------- /ipwhois/docs/source/CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../CONTRIBUTING.rst -------------------------------------------------------------------------------- /ipwhois/docs/source/EXPERIMENTAL.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../EXPERIMENTAL.rst -------------------------------------------------------------------------------- /ipwhois/docs/source/LICENSE.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | .. include:: ../../../LICENSE.txt -------------------------------------------------------------------------------- /ipwhois/docs/source/NIR.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../NIR.rst -------------------------------------------------------------------------------- /ipwhois/docs/source/RDAP.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../RDAP.rst -------------------------------------------------------------------------------- /ipwhois/docs/source/README.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../README.rst -------------------------------------------------------------------------------- /ipwhois/docs/source/UPGRADING.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../UPGRADING.rst -------------------------------------------------------------------------------- /ipwhois/docs/source/UTILS.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../UTILS.rst -------------------------------------------------------------------------------- /ipwhois/docs/source/WHOIS.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../WHOIS.rst -------------------------------------------------------------------------------- /ipwhois/docs/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {% block menu %} 4 | {{ super() }} 5 | Index 6 | Module Index 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /ipwhois/docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # ipwhois documentation build configuration file, created by 5 | # sphinx-quickstart on Mon Jul 28 19:55:48 2014. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # Taken from sphinx_rtd_theme docs 17 | import os 18 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 19 | if not on_rtd: 20 | import sphinx_rtd_theme 21 | html_theme = 'sphinx_rtd_theme' 22 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 23 | 24 | # If extensions (or modules to document with autodoc) are in another directory, 25 | # add these directories to sys.path here. If the directory is relative to the 26 | # documentation root, use os.path.abspath to make it absolute, like shown here. 27 | #sys.path.insert(0, os.path.abspath('.')) 28 | 29 | # -- General configuration ------------------------------------------------ 30 | 31 | # If your documentation needs a minimal Sphinx version, state it here. 32 | #needs_sphinx = '1.0' 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | 38 | extensions = [ 39 | 'sphinx.ext.autodoc', 40 | 'sphinx.ext.doctest', 41 | 'sphinx.ext.coverage', 42 | 'sphinx.ext.viewcode', 43 | 'sphinx.ext.napoleon' 44 | ] 45 | 46 | napoleon_google_docstring = True 47 | napoleon_numpy_docstring = False 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ['_templates'] 51 | 52 | # The suffix of source filenames. 53 | source_suffix = '.rst' 54 | 55 | # The encoding of source files. 56 | #source_encoding = 'utf-8-sig' 57 | 58 | # The master toctree document. 59 | master_doc = 'index' 60 | 61 | # General information about the project. 62 | project = 'ipwhois' 63 | copyright = '2013-2024, Philip Hane' 64 | 65 | # The version info for the project you're documenting, acts as replacement for 66 | # |version| and |release|, also used in various other places throughout the 67 | # built documents. 68 | # 69 | # The short X.Y version. 70 | version = '1.3.0' 71 | # The full version, including alpha/beta/rc tags. 72 | release = '1.3.0' 73 | 74 | # The language for content autogenerated by Sphinx. Refer to documentation 75 | # for a list of supported languages. 76 | #language = None 77 | 78 | # There are two options for replacing |today|: either, you set today to some 79 | # non-false value, then it is used: 80 | #today = '' 81 | # Else, today_fmt is used as the format for a strftime call. 82 | #today_fmt = '%B %d, %Y' 83 | 84 | # List of patterns, relative to source directory, that match files and 85 | # directories to ignore when looking for source files. 86 | exclude_patterns = [] 87 | 88 | # The reST default role (used for this markup: `text`) to use for all 89 | # documents. 90 | #default_role = None 91 | 92 | # If true, '()' will be appended to :func: etc. cross-reference text. 93 | #add_function_parentheses = True 94 | 95 | # If true, the current module name will be prepended to all description 96 | # unit titles (such as .. function::). 97 | #add_module_names = True 98 | 99 | # If true, sectionauthor and moduleauthor directives will be shown in the 100 | # output. They are ignored by default. 101 | #show_authors = False 102 | 103 | # The name of the Pygments (syntax highlighting) style to use. 104 | pygments_style = 'sphinx' 105 | 106 | # A list of ignored prefixes for module index sorting. 107 | #modindex_common_prefix = [] 108 | 109 | # If true, keep warnings as "system message" paragraphs in the built documents. 110 | #keep_warnings = False 111 | 112 | 113 | # -- Options for HTML output ---------------------------------------------- 114 | 115 | # The theme to use for HTML and HTML Help pages. See the documentation for 116 | # a list of builtin themes. 117 | #html_theme = 'sphinx_rtd_theme' 118 | 119 | # Theme options are theme-specific and customize the look and feel of a theme 120 | # further. For a list of options available for each theme, see the 121 | # documentation. 122 | #html_theme_options = {} 123 | 124 | # Add any paths that contain custom themes here, relative to this directory. 125 | #html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 126 | 127 | # The name for this set of Sphinx documents. If None, it defaults to 128 | # " v documentation". 129 | #html_title = None 130 | 131 | # A shorter title for the navigation bar. Default is the same as html_title. 132 | #html_short_title = None 133 | 134 | # The name of an image file (relative to this directory) to place at the top 135 | # of the sidebar. 136 | #html_logo = None 137 | 138 | # The name of an image file (within the static path) to use as favicon of the 139 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 140 | # pixels large. 141 | #html_favicon = None 142 | 143 | # Add any paths that contain custom static files (such as style sheets) here, 144 | # relative to this directory. They are copied after the builtin static files, 145 | # so a file named "default.css" will overwrite the builtin "default.css". 146 | #html_static_path = ['_static'] 147 | 148 | # Add any extra paths that contain custom files (such as robots.txt or 149 | # .htaccess) here, relative to this directory. These files are copied 150 | # directly to the root of the documentation. 151 | #html_extra_path = [] 152 | 153 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 154 | # using the given strftime format. 155 | #html_last_updated_fmt = '%b %d, %Y' 156 | 157 | # If true, SmartyPants will be used to convert quotes and dashes to 158 | # typographically correct entities. 159 | #html_use_smartypants = True 160 | 161 | # Custom sidebar templates, maps document names to template names. 162 | #html_sidebars = {} 163 | 164 | # Additional templates that should be rendered to pages, maps page names to 165 | # template names. 166 | #html_additional_pages = {} 167 | 168 | # If false, no module index is generated. 169 | #html_domain_indices = True 170 | 171 | # If false, no index is generated. 172 | #html_use_index = True 173 | 174 | # If true, the index is split into individual pages for each letter. 175 | #html_split_index = False 176 | 177 | # If true, links to the reST sources are added to the pages. 178 | html_show_sourcelink = False 179 | 180 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 181 | html_show_sphinx = True 182 | 183 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 184 | html_show_copyright = True 185 | 186 | # If true, an OpenSearch description file will be output, and all pages will 187 | # contain a tag referring to it. The value of this option must be the 188 | # base URL from which the finished HTML is served. 189 | #html_use_opensearch = '' 190 | 191 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 192 | #html_file_suffix = None 193 | 194 | # Output file base name for HTML help builder. 195 | htmlhelp_basename = 'ipwhoisdoc' 196 | 197 | 198 | # -- Options for LaTeX output --------------------------------------------- 199 | 200 | latex_elements = { 201 | # The paper size ('letterpaper' or 'a4paper'). 202 | #'papersize': 'letterpaper', 203 | 204 | # The font size ('10pt', '11pt' or '12pt'). 205 | #'pointsize': '10pt', 206 | 207 | # Additional stuff for the LaTeX preamble. 208 | #'preamble': '', 209 | } 210 | 211 | # Grouping the document tree into LaTeX files. List of tuples 212 | # (source start file, target name, title, 213 | # author, documentclass [howto, manual, or own class]). 214 | latex_documents = [ 215 | ('index', 'ipwhois.tex', 'ipwhois Documentation', 216 | 'secynic', 'manual'), 217 | ] 218 | 219 | # The name of an image file (relative to this directory) to place at the top of 220 | # the title page. 221 | #latex_logo = None 222 | 223 | # For "manual" documents, if this is true, then toplevel headings are parts, 224 | # not chapters. 225 | #latex_use_parts = False 226 | 227 | # If true, show page references after internal links. 228 | #latex_show_pagerefs = False 229 | 230 | # If true, show URL addresses after external links. 231 | #latex_show_urls = False 232 | 233 | # Documents to append as an appendix to all manuals. 234 | #latex_appendices = [] 235 | 236 | # If false, no module index is generated. 237 | #latex_domain_indices = True 238 | 239 | 240 | # -- Options for manual page output --------------------------------------- 241 | 242 | # One entry per manual page. List of tuples 243 | # (source start file, name, description, authors, manual section). 244 | man_pages = [ 245 | ('index', 'ipwhois', 'ipwhois Documentation', 246 | ['secynic'], 1) 247 | ] 248 | 249 | # If true, show URL addresses after external links. 250 | #man_show_urls = False 251 | 252 | 253 | # -- Options for Texinfo output ------------------------------------------- 254 | 255 | # Grouping the document tree into Texinfo files. List of tuples 256 | # (source start file, target name, title, author, 257 | # dir menu entry, description, category) 258 | texinfo_documents = [ 259 | ('index', 'ipwhois', 'ipwhois Documentation', 260 | 'secynic', 'ipwhois', 'ipwhois is a Python package focused on retrieving ' 261 | 'and parsing whois data for IPv4 and IPv6 addresses.', 262 | 'Miscellaneous'), 263 | ] 264 | 265 | # Documents to append as an appendix to all manuals. 266 | #texinfo_appendices = [] 267 | 268 | # If false, no module index is generated. 269 | #texinfo_domain_indices = True 270 | 271 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 272 | #texinfo_show_urls = 'footnote' 273 | 274 | # If true, do not generate a @detailmenu in the "Top" node's menu. 275 | #texinfo_no_detailmenu = False 276 | -------------------------------------------------------------------------------- /ipwhois/docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | ipwhois 3 | ======= 4 | 5 | ipwhois is a Python package focused on retrieving and parsing whois data 6 | for IPv4 and IPv6 addresses. 7 | 8 | .. toctree:: 9 | :caption: Project Info 10 | :maxdepth: 2 11 | 12 | Readme 13 | Contributing 14 | License 15 | Changes 16 | Changes (Archive) 17 | Upgrade Notes 18 | 19 | .. toctree:: 20 | :caption: API Documentation 21 | :maxdepth: 2 22 | 23 | RDAP (Recommended) 24 | Legacy Whois 25 | NIR (National Internet Registry) 26 | ASN (Autonomous System Number) 27 | Utilities 28 | CLI 29 | Experimental Functions 30 | 31 | .. toctree:: 32 | :caption: Code 33 | 34 | ipwhois 35 | -------------------------------------------------------------------------------- /ipwhois/docs/source/ipwhois.rst: -------------------------------------------------------------------------------- 1 | Library Structure 2 | ================= 3 | 4 | .. automodule:: ipwhois 5 | :members: 6 | :private-members: 7 | 8 | .. automodule:: ipwhois.ipwhois 9 | :members: 10 | :private-members: 11 | 12 | .. automodule:: ipwhois.net 13 | :members: 14 | :private-members: 15 | 16 | .. automodule:: ipwhois.rdap 17 | :members: 18 | :private-members: 19 | 20 | .. automodule:: ipwhois.whois 21 | :members: 22 | :private-members: 23 | 24 | .. automodule:: ipwhois.nir 25 | :members: 26 | :private-members: 27 | 28 | .. automodule:: ipwhois.asn 29 | :members: 30 | :private-members: 31 | 32 | .. automodule:: ipwhois.utils 33 | :members: 34 | :private-members: 35 | 36 | .. automodule:: ipwhois.exceptions 37 | :members: 38 | :private-members: 39 | 40 | .. automodule:: ipwhois.hr 41 | :members: 42 | :private-members: 43 | 44 | .. automodule:: ipwhois.experimental 45 | :members: 46 | :private-members: 47 | -------------------------------------------------------------------------------- /ipwhois/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secynic/ipwhois/b8c79c4e902467ccac0d841ad6ef820b1627e357/ipwhois/examples/__init__.py -------------------------------------------------------------------------------- /ipwhois/examples/elastic_search/README.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | ipwhois: ElasticSearch example 3 | ============================== 4 | 5 | Basic example Python CLI showing how to use ipwhois (RDAP) data with 6 | ElasticSearch and Kibana. This example uses some other libararies to obtain 7 | geo information for various country and address fields, formatted for mapping 8 | in Kibana. 9 | 10 | One purpose for using this example would be to locally track, cache, and 11 | report on whois data. 12 | 13 | I do not re-distribute the GeoLite2 database. For geolite2 data, download the 14 | GeoLite2 database GeoLite2-City.mmdb and place in the data directory: 15 | https://dev.maxmind.com/geoip/geoip2/geolite2/ 16 | 17 | .. note:: 18 | 19 | I have not updated the kibana.json since the ES 5.5.1 example. --kimport 20 | and --kexport will not work with it for ES 6.x. 21 | 22 | Dependencies 23 | ============ 24 | 25 | Tested using:: 26 | 27 | ElasticSearch 6.6.0 28 | Kibana 6.6.0 29 | 30 | Python 2.7, 3.4+ (requirements.txt):: 31 | 32 | ipwhois 33 | elasticsearch 34 | geoip2 35 | geopy 36 | 37 | 38 | Usage 39 | ===== 40 | 41 | elastic_search.py [-h] [--create] [--delete] [--insert "IP"] [--update] 42 | [--expires INTEGER] [--depth INTEGER] 43 | [--kexport "FILEPATH"] [--kimport "FILEPATH"] 44 | [--host "HOST"] [--port PORT] 45 | 46 | Populate ElasticSearch with ipwhois data. Index: ipwhois Doc Types: base, 47 | entity 48 | 49 | arguments: 50 | -h, --help show this help message and exit 51 | --create Create the ipwhois ElasticSearch index. 52 | --delete Delete the ipwhois ElasticSearch index. 53 | --insert IP An IPv4 or IPv6 address as a string. 54 | --update Update entries rather than inserting new. 55 | --expires INTEGER Only insert/update/query an IP address if it is older 56 | than EXPIRES (days). Default: 7. 57 | --depth INTEGER How many levels deep to run queries when additional 58 | referenced objects (IP entities) are found. Default: 59 | 1. 60 | --kexport FILEPATH Export the ipwhois Kibana configuration (Index: 61 | .kibana) to a jsonfile (FILEPATH). 62 | --kimport FILEPATH Import the ipwhois default Kibana configuration 63 | (Index: .kibana) from a json file (FILEPATH). 64 | --host HOST The ElasticSearch host to connect to. Default: 65 | "localhost". 66 | --port PORT The ElasticSearch port to connect to. Default: 9200. 67 | 68 | Usage Examples 69 | ============== 70 | 71 | Create the ipwhois ElasticSearch index 72 | -------------------------------------- 73 | 74 | :: 75 | 76 | elastic_search.py --create 77 | 78 | Delete the ipwhois ElasticSearch index 79 | -------------------------------------- 80 | 81 | :: 82 | 83 | elastic_search.py --delete 84 | 85 | Query an IP address and enter the data 86 | -------------------------------------- 87 | 88 | :: 89 | 90 | elastic_search.py --insert "74.125.225.229" 91 | 92 | Update data for an IP address if the last entry is older than 7 days 93 | -------------------------------------------------------------------- 94 | 95 | :: 96 | 97 | elastic_search.py --insert "74.125.225.229" --update --expires 7 98 | 99 | Update data for an IP address regardless of age 100 | ----------------------------------------------- 101 | 102 | :: 103 | 104 | elastic_search.py --insert "74.125.225.229" --update --expires 0 105 | 106 | Query an IP address but don't query for sub-entities 107 | ---------------------------------------------------- 108 | 109 | :: 110 | 111 | elastic_search.py --insert "74.125.225.229" --depth 0 112 | 113 | Export Kibana config (dashboard, search, visualization, ipwhois) to json file 114 | ----------------------------------------------------------------------------- 115 | 116 | :: 117 | 118 | elastic_search.py --kexport "/tmp/ipwhois-kibana.json" 119 | 120 | Import Kibana config (dashboard, search, visualization, ipwhois) from json file 121 | ------------------------------------------------------------------------------- 122 | 123 | :: 124 | 125 | elastic_search.py --kimport "/tmp/ipwhois-kibana.json" 126 | 127 | Create ipwhois index on custom ElasticSearch host and port 128 | ---------------------------------------------------------- 129 | 130 | :: 131 | 132 | elastic_search.py --create --host "192.168.0.1" --port 1234 133 | 134 | Import Kibana Config 135 | ==================== 136 | 137 | There is a default Kibana configuration for ipwhois in the data directory. 138 | 139 | Replace EXAMPLES_DIR with the file path to the ipwhois examples directory: 140 | 141 | :: 142 | 143 | elastic_search.py --kimport "EXAMPLES_DIR/elastic_search/data/kibana.json" 144 | 145 | -------------------------------------------------------------------------------- /ipwhois/examples/elastic_search/data/geo_coord.json: -------------------------------------------------------------------------------- 1 | {"UY": {"longitude": -56, "latitude": -33}, "TH": {"longitude": 100, "latitude": 15}, "HU": {"longitude": 20, "latitude": 47}, "HT": {"longitude": -72.4167, "latitude": 19}, "HR": {"longitude": 15.5, "latitude": 45.1667}, "FM": {"longitude": 158.25, "latitude": 6.9167}, "WF": {"longitude": -176.2, "latitude": -13.3}, "HM": {"longitude": 72.5167, "latitude": -53.1}, "HN": {"longitude": -86.5, "latitude": 15}, "HK": {"longitude": 114.1667, "latitude": 22.25}, "FJ": {"longitude": 175, "latitude": -18}, "VN": {"longitude": 106, "latitude": 16}, "ZA": {"longitude": 24, "latitude": -29}, "TG": {"longitude": 1.1667, "latitude": 8}, "SK": {"longitude": 19.5, "latitude": 48.6667}, "UG": {"longitude": 32, "latitude": 1}, "GY": {"longitude": -59, "latitude": 5}, "VI": {"longitude": -64.8333, "latitude": 18.3333}, "GP": {"longitude": -61.5833, "latitude": 16.25}, "GQ": {"longitude": 10, "latitude": 2}, "GR": {"longitude": 22, "latitude": 39}, "GS": {"longitude": -37, "latitude": -54.5}, "GT": {"longitude": -90.25, "latitude": 15.5}, "OM": {"longitude": 57, "latitude": 21}, "VA": {"longitude": 12.45, "latitude": 41.9}, "GW": {"longitude": -15, "latitude": 12}, "GH": {"longitude": -2, "latitude": 8}, "GI": {"longitude": -5.3667, "latitude": 36.1833}, "GL": {"longitude": -40, "latitude": 72}, "GM": {"longitude": -16.5667, "latitude": 13.4667}, "GN": {"longitude": -10, "latitude": 11}, "GA": {"longitude": 11.75, "latitude": -1}, "GB": {"longitude": -2, "latitude": 54}, "GD": {"longitude": -61.6667, "latitude": 12.1167}, "GE": {"longitude": 43.5, "latitude": 42}, "GF": {"longitude": -53, "latitude": 4}, "BJ": {"longitude": 2.25, "latitude": 9.5}, "BI": {"longitude": 30, "latitude": -3.5}, "BH": {"longitude": 50.55, "latitude": 26}, "BO": {"longitude": -65, "latitude": -17}, "BN": {"longitude": 114.6667, "latitude": 4.5}, "BM": {"longitude": -64.75, "latitude": 32.3333}, "VU": {"longitude": 167, "latitude": -16}, "BB": {"longitude": -59.5333, "latitude": 13.1667}, "BA": {"longitude": 18, "latitude": 44}, "BG": {"longitude": 25, "latitude": 43}, "BF": {"longitude": -2, "latitude": 13}, "BE": {"longitude": 4, "latitude": 50.8333}, "GU": {"longitude": 144.7833, "latitude": 13.4667}, "BZ": {"longitude": -88.75, "latitude": 17.25}, "BY": {"longitude": 28, "latitude": 53}, "ZM": {"longitude": 30, "latitude": -15}, "BS": {"longitude": -76, "latitude": 24.25}, "BR": {"longitude": -55, "latitude": -10}, "BW": {"longitude": 24, "latitude": -22}, "BV": {"longitude": 3.4, "latitude": -54.4333}, "BT": {"longitude": 90.5, "latitude": 27.5}, "JP": {"longitude": 138, "latitude": 36}, "TK": {"longitude": -172, "latitude": -9}, "ST": {"longitude": 7, "latitude": 1}, "VG": {"longitude": -64.5, "latitude": 18.5}, "YT": {"longitude": 45.1667, "latitude": -12.8333}, "ZW": {"longitude": 30, "latitude": -20}, "JO": {"longitude": 36, "latitude": 31}, "JM": {"longitude": -77.5, "latitude": 18.25}, "QA": {"longitude": 51.25, "latitude": 25.5}, "AN": {"longitude": -68.75, "latitude": 12.25}, "AO": {"longitude": 18.5, "latitude": -12.5}, "AL": {"longitude": 20, "latitude": 41}, "AM": {"longitude": 45, "latitude": 40}, "AI": {"longitude": -63.1667, "latitude": 18.25}, "AF": {"longitude": 65, "latitude": 33}, "AG": {"longitude": -61.8, "latitude": 17.05}, "AD": {"longitude": 1.5, "latitude": 42.5}, "AE": {"longitude": 54, "latitude": 24}, "SV": {"longitude": -88.9167, "latitude": 13.8333}, "AZ": {"longitude": 47.5, "latitude": 40.5}, "AW": {"longitude": -69.9667, "latitude": 12.5}, "AT": {"longitude": 13.3333, "latitude": 47.3333}, "AU": {"longitude": 133, "latitude": -27}, "AR": {"longitude": -64, "latitude": -34}, "AS": {"longitude": -170, "latitude": -14.3333}, "AP": {"longitude": 105, "latitude": 35}, "AQ": {"longitude": 0, "latitude": -90}, "IT": {"longitude": 12.8333, "latitude": 42.8333}, "IR": {"longitude": 53, "latitude": 32}, "IS": {"longitude": -18, "latitude": 65}, "IQ": {"longitude": 44, "latitude": 33}, "PT": {"longitude": -8, "latitude": 39.5}, "PW": {"longitude": 134.5, "latitude": 7.5}, "PS": {"longitude": 35.25, "latitude": 32}, "PR": {"longitude": -66.5, "latitude": 18.25}, "PM": {"longitude": -56.3333, "latitude": 46.8333}, "PL": {"longitude": 20, "latitude": 52}, "ID": {"longitude": 120, "latitude": -5}, "IE": {"longitude": -8, "latitude": 53}, "PH": {"longitude": 122, "latitude": 13}, "PK": {"longitude": 70, "latitude": 30}, "PE": {"longitude": -76, "latitude": -10}, "IO": {"longitude": 71.5, "latitude": -6}, "PG": {"longitude": 147, "latitude": -6}, "PF": {"longitude": -140, "latitude": -15}, "PA": {"longitude": -80, "latitude": 9}, "IN": {"longitude": 77, "latitude": 20}, "US": {"longitude": -97, "latitude": 38}, "DE": {"longitude": 9, "latitude": 51}, "VE": {"longitude": -66, "latitude": 8}, "TW": {"longitude": 121, "latitude": 23.5}, "DK": {"longitude": 10, "latitude": 56}, "DJ": {"longitude": 43, "latitude": 11.5}, "DM": {"longitude": -61.3333, "latitude": 15.4167}, "DO": {"longitude": -70.6667, "latitude": 19}, "DZ": {"longitude": 3, "latitude": 28}, "LI": {"longitude": 9.5333, "latitude": 47.1667}, "SD": {"longitude": 30, "latitude": 15}, "LK": {"longitude": 81, "latitude": 7}, "SR": {"longitude": -56, "latitude": 4}, "TV": {"longitude": 178, "latitude": -8}, "LA": {"longitude": 105, "latitude": 18}, "LC": {"longitude": -61.1333, "latitude": 13.8833}, "LB": {"longitude": 35.8333, "latitude": 33.8333}, "SY": {"longitude": 38, "latitude": 35}, "SZ": {"longitude": 31.5, "latitude": -26.5}, "LY": {"longitude": 17, "latitude": 25}, "SE": {"longitude": 15, "latitude": 62}, "SG": {"longitude": 103.8, "latitude": 1.3667}, "SA": {"longitude": 45, "latitude": 25}, "SB": {"longitude": 159, "latitude": -8}, "SC": {"longitude": 55.6667, "latitude": -4.5833}, "SL": {"longitude": -11.5, "latitude": 8.5}, "SM": {"longitude": 12.4167, "latitude": 43.7667}, "LS": {"longitude": 28.5, "latitude": -29.5}, "LR": {"longitude": -9.5, "latitude": 6.5}, "LU": {"longitude": 6, "latitude": 49.75}, "LT": {"longitude": 24, "latitude": 55}, "SJ": {"longitude": 20, "latitude": 78}, "LV": {"longitude": 25, "latitude": 57}, "CD": {"longitude": 25, "latitude": 0}, "VC": {"longitude": -61.2, "latitude": 13.25}, "CF": {"longitude": 21, "latitude": 7}, "CG": {"longitude": 15, "latitude": -1}, "CA": {"longitude": -100, "latitude": 54}, "CC": {"longitude": 96.8333, "latitude": -12.5}, "CL": {"longitude": -71, "latitude": -30}, "CM": {"longitude": 12, "latitude": 6}, "CN": {"longitude": 105, "latitude": 35}, "CO": {"longitude": -72, "latitude": 4}, "CH": {"longitude": 8, "latitude": 47}, "CI": {"longitude": -5, "latitude": 8}, "CK": {"longitude": -159.7667, "latitude": -21.2333}, "CU": {"longitude": -80, "latitude": 21.5}, "CV": {"longitude": -24, "latitude": 16}, "TR": {"longitude": 35, "latitude": 39}, "CR": {"longitude": -84, "latitude": 10}, "PY": {"longitude": -58, "latitude": -23}, "BD": {"longitude": 90, "latitude": 24}, "CX": {"longitude": 105.6667, "latitude": -10.5}, "CY": {"longitude": 33, "latitude": 35}, "CZ": {"longitude": 15.5, "latitude": 49.75}, "TT": {"longitude": -61, "latitude": 11}, "KM": {"longitude": 44.25, "latitude": -12.1667}, "KN": {"longitude": -62.75, "latitude": 17.3333}, "SO": {"longitude": 49, "latitude": 10}, "KH": {"longitude": 105, "latitude": 13}, "KI": {"longitude": 173, "latitude": 1.4167}, "RS": {"longitude": 21, "latitude": 44}, "KE": {"longitude": 38, "latitude": 1}, "KG": {"longitude": 75, "latitude": 41}, "RW": {"longitude": 30, "latitude": -2}, "RU": {"longitude": 100, "latitude": 60}, "SI": {"longitude": 15, "latitude": 46}, "RO": {"longitude": 25, "latitude": 46}, "KY": {"longitude": -80.5, "latitude": 19.5}, "KZ": {"longitude": 68, "latitude": 48}, "KW": {"longitude": 47.6581, "latitude": 29.3375}, "KP": {"longitude": 127, "latitude": 40}, "KR": {"longitude": 127.5, "latitude": 37}, "TZ": {"longitude": 35, "latitude": -6}, "WS": {"longitude": -172.3333, "latitude": -13.5833}, "TN": {"longitude": 9, "latitude": 34}, "TM": {"longitude": 60, "latitude": 40}, "NG": {"longitude": 8, "latitude": 10}, "NF": {"longitude": 167.95, "latitude": -29.0333}, "NE": {"longitude": 8, "latitude": 16}, "NC": {"longitude": 165.5, "latitude": -21.5}, "RE": {"longitude": 55.6, "latitude": -21.1}, "NA": {"longitude": 17, "latitude": -22}, "NO": {"longitude": 10, "latitude": 62}, "NL": {"longitude": 5.75, "latitude": 52.5}, "FR": {"longitude": 2, "latitude": 46}, "NI": {"longitude": -85, "latitude": 13}, "FO": {"longitude": -7, "latitude": 62}, "NU": {"longitude": -169.8667, "latitude": -19.0333}, "UA": {"longitude": 32, "latitude": 49}, "FK": {"longitude": -59, "latitude": -51.75}, "NR": {"longitude": 166.9167, "latitude": -0.5333}, "FI": {"longitude": 26, "latitude": 62}, "NP": {"longitude": 84, "latitude": 28}, "SN": {"longitude": -14, "latitude": 14}, "NZ": {"longitude": 174, "latitude": -41}, "UM": {"longitude": 166.6, "latitude": 19.2833}, "EC": {"longitude": -77.5, "latitude": -2}, "EG": {"longitude": 30, "latitude": 27}, "EE": {"longitude": 26, "latitude": 59}, "EH": {"longitude": -13, "latitude": 24.5}, "TF": {"longitude": 67, "latitude": -43}, "ER": {"longitude": 39, "latitude": 15}, "ES": {"longitude": -4, "latitude": 40}, "YE": {"longitude": 48, "latitude": 15}, "ET": {"longitude": 38, "latitude": 8}, "EU": {"longitude": 8, "latitude": 47}, "TO": {"longitude": -175, "latitude": -20}, "IL": {"longitude": 34.75, "latitude": 31.5}, "MK": {"longitude": 22, "latitude": 41.8333}, "MH": {"longitude": 168, "latitude": 9}, "TJ": {"longitude": 71, "latitude": 39}, "MN": {"longitude": 105, "latitude": 46}, "MO": {"longitude": 113.55, "latitude": 22.1667}, "ML": {"longitude": -4, "latitude": 17}, "MM": {"longitude": 98, "latitude": 22}, "MC": {"longitude": 7.4, "latitude": 43.7333}, "MA": {"longitude": -5, "latitude": 32}, "MG": {"longitude": 47, "latitude": -20}, "MD": {"longitude": 29, "latitude": 47}, "ME": {"longitude": 19.4, "latitude": 42.5}, "MZ": {"longitude": 35, "latitude": -18.25}, "UZ": {"longitude": 64, "latitude": 41}, "MX": {"longitude": -102, "latitude": 23}, "MY": {"longitude": 112.5, "latitude": 2.5}, "TD": {"longitude": 19, "latitude": 15}, "TC": {"longitude": -71.5833, "latitude": 21.75}, "SH": {"longitude": -5.7, "latitude": -15.9333}, "MR": {"longitude": -12, "latitude": 20}, "MS": {"longitude": -62.2, "latitude": 16.75}, "MP": {"longitude": 145.75, "latitude": 15.2}, "MQ": {"longitude": -61, "latitude": 14.6667}, "MV": {"longitude": 73, "latitude": 3.25}, "MW": {"longitude": 34, "latitude": -13.5}, "MT": {"longitude": 14.5833, "latitude": 35.8333}, "MU": {"longitude": 57.55, "latitude": -20.2833}} -------------------------------------------------------------------------------- /ipwhois/examples/elastic_search/requirements.txt: -------------------------------------------------------------------------------- 1 | ipwhois 2 | elasticsearch 3 | geoip2 4 | geopy 5 | -------------------------------------------------------------------------------- /ipwhois/examples/redis_cache/README.rst: -------------------------------------------------------------------------------- 1 | ================================== 2 | ipwhois: Caching via Redis example 3 | ================================== 4 | 5 | Example Python CLI showing how to cache ipwhois (RDAP) data with 6 | Redis. The intent here is to reduce duplicate queries, configurable by 7 | cache timeouts. In this example, get (--get) just pretty prints (pprint) to 8 | stdout. You could easily convert this example for non-cli use by using the 9 | class embedded in this file (IPWhoisRedisCache). 10 | 11 | Dependencies 12 | ============ 13 | 14 | Tested using:: 15 | 16 | Redis 3.2.1 17 | 18 | Python 2.7, 3.4+ (requirements.txt):: 19 | 20 | ipwhois 21 | redis 22 | 23 | Usage 24 | ===== 25 | 26 | redis_cache.py [-h] [--addr "IP"] [--set] [--get] [--expires EXPIRES] 27 | [--depth DEPTH] [--host "HOST"] [--port PORT] 28 | [--db DATABASE] 29 | 30 | Cache ipwhois data with Redis. 31 | 32 | arguments: 33 | -h, --help show this help message and exit 34 | --addr IP An IPv4 or IPv6 address as a string. 35 | --set Update the cache with RDAP results for addr if new or 36 | if the existing entry is older than expires. This will 37 | run first if combined with get. 38 | --get Get the cached RDAP results for addr. Can be combined 39 | with set, and will run after set. 40 | --expires EXPIRES Updates the cache if the --addr cache data is older 41 | than EXPIRES (in days). Default: 7. Setting to 0 42 | forces an update. 43 | --depth DEPTH How many levels deep to run queries when additional 44 | referenced objects (IP entities) are found. Default: 45 | 1. 46 | --host HOST The Redis host. Default: localhost. 47 | --port PORT The Redis port. Default: 6379. 48 | --db DATABASE The Redis database. Default: 0. 49 | 50 | Usage Examples 51 | ============== 52 | 53 | Update cache for an IP address if empty or expired, and get 54 | ----------------------------------------------------------- 55 | 56 | :: 57 | 58 | redis_cache.py --addr "74.125.225.229" --set --get 59 | 60 | Get cache results for an IP address 61 | ----------------------------------- 62 | 63 | :: 64 | 65 | redis_cache.py --addr "74.125.225.229" --get 66 | 67 | Update cache for an IP address if empty or older than one day 68 | ------------------------------------------------------------- 69 | 70 | :: 71 | 72 | redis_cache.py --addr "74.125.225.229" --set --expires 1 73 | 74 | Force update cache for an IP address 75 | ------------------------------------ 76 | 77 | :: 78 | 79 | redis_cache.py --addr "74.125.225.229" --set --expires 0 80 | 81 | Set but don't query for sub-entities 82 | ------------------------------------ 83 | 84 | :: 85 | 86 | redis_cache.py --addr "74.125.225.229" --set --depth 0 87 | 88 | Override the default Redis host, port, and db 89 | --------------------------------------------- 90 | 91 | :: 92 | 93 | redis_cache.py --addr "74.125.225.229" --set --host "192.168.0.1" 94 | --port 1234 --db 1 95 | 96 | -------------------------------------------------------------------------------- /ipwhois/examples/redis_cache/redis_cache.py: -------------------------------------------------------------------------------- 1 | # Basic example showing how to cache ipwhois RDAP results with Redis. 2 | 3 | import argparse 4 | import redis 5 | from os import path 6 | from ipwhois import IPWhois 7 | from datetime import datetime, timedelta 8 | import pickle 9 | from pprint import pprint 10 | 11 | # Setup the arg parser. 12 | parser = argparse.ArgumentParser( 13 | description='ipwhois RDAP result caching with Redis.' 14 | ) 15 | parser.add_argument( 16 | '--addr', 17 | type=str, 18 | nargs=1, 19 | metavar='"IP"', 20 | help='An IPv4 or IPv6 address as a string.', 21 | required=True 22 | ) 23 | parser.add_argument( 24 | '--set', 25 | action='store_true', 26 | help='Update the cache with RDAP results for addr if new or if the ' 27 | 'existing entry is older than expires.' 28 | ) 29 | parser.add_argument( 30 | '--get', 31 | action='store_true', 32 | help='Get the cached RDAP results for addr. Can be combined with set.' 33 | ) 34 | parser.add_argument( 35 | '--expires', 36 | type=int, 37 | default=7, 38 | metavar='EXPIRES', 39 | help=('Updates the cache if the --addr cache data is older than ' 40 | 'EXPIRES (in days). Default: 7. Setting to 0 forces ' 41 | 'an update.') 42 | ) 43 | parser.add_argument( 44 | '--depth', 45 | type=int, 46 | default=1, 47 | metavar='DEPTH', 48 | help='How many levels deep to run queries when additional ' 49 | 'referenced objects (IP entities) are found. Default: 1.' 50 | ) 51 | parser.add_argument( 52 | '--host', 53 | type=str, 54 | nargs=1, 55 | metavar='"HOST"', 56 | default='localhost', 57 | help='The Redis host to connect to. Default: localhost.' 58 | ) 59 | parser.add_argument( 60 | '--port', 61 | type=int, 62 | metavar='PORT', 63 | default=6379, 64 | help='The Redis port to connect to. Default: 6379.' 65 | ) 66 | parser.add_argument( 67 | '--db', 68 | type=int, 69 | metavar='DATABASE', 70 | default=0, 71 | help='The Redis database to connect to. Default: 0.' 72 | ) 73 | 74 | # Get the args 75 | args = parser.parse_args() 76 | 77 | # Get the current working directory. 78 | CUR_DIR = path.dirname(__file__) 79 | 80 | 81 | class IPWhoisRedisCache: 82 | 83 | def __init__(self, connection_pool=None, addr=''): 84 | 85 | if not isinstance(connection_pool, redis.ConnectionPool): 86 | raise ValueError('connection_pool must be an instance of ' 87 | 'redis.ConnectionPool') 88 | 89 | self.conn = redis.Redis(connection_pool=connection_pool) 90 | self.addr = addr 91 | 92 | def get_ipwhois(self, depth=1): 93 | 94 | # Perform the RDAP lookup for self.addr retrieving all 95 | # entities up to depth. 96 | obj = IPWhois(self.addr) 97 | ret = obj.lookup_rdap(depth=depth) 98 | 99 | # Set the updated timestamp for cache expiration. 100 | ret['updated'] = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') 101 | 102 | return ret 103 | 104 | def get(self): 105 | 106 | ret = self.conn.get(self.addr) 107 | return pickle.loads(ret) if ret else ret 108 | 109 | def set(self, expires=7, depth=1): 110 | 111 | existing_data = self.get() 112 | existing_updated = None 113 | 114 | if existing_data is not None: 115 | existing_updated = datetime.strptime(existing_data['updated'], 116 | '%Y-%m-%dT%H:%M:%SZ') 117 | 118 | if (expires == 0 or existing_data is None or existing_updated < ( 119 | datetime.utcnow() - timedelta(days=expires))): 120 | 121 | result = self.get_ipwhois(depth) 122 | 123 | self.conn.set(self.addr, pickle.dumps(result)) 124 | return True 125 | 126 | else: 127 | 128 | return False 129 | 130 | # Redis connection init 131 | pool = redis.ConnectionPool(host=args.host, port=args.port, db=args.db) 132 | cache = IPWhoisRedisCache(pool, args.addr[0]) 133 | 134 | if args.set: 135 | set_result = cache.set(args.expires, args.depth) 136 | if set_result: 137 | print('Redis cache updated.') 138 | else: 139 | print('Redis cache has not expired.') 140 | 141 | if args.get: 142 | pprint(cache.get()) 143 | 144 | if not args.set and not args.get: 145 | print('Nothing done. --set and/or --get required.') 146 | -------------------------------------------------------------------------------- /ipwhois/examples/redis_cache/requirements.txt: -------------------------------------------------------------------------------- 1 | ipwhois 2 | redis 3 | -------------------------------------------------------------------------------- /ipwhois/examples/redis_cache/requirements26.txt: -------------------------------------------------------------------------------- 1 | ipwhois 2 | redis 3 | argparse 4 | -------------------------------------------------------------------------------- /ipwhois/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2024 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 | 25 | 26 | class BaseIpwhoisException(Exception): 27 | """ 28 | Base exception for all the ipwhois custom ones. 29 | """ 30 | 31 | 32 | class NetError(BaseIpwhoisException): 33 | """ 34 | An Exception for when a parameter provided is not an instance of 35 | ipwhois.net.Net. 36 | """ 37 | 38 | 39 | class IPDefinedError(BaseIpwhoisException): 40 | """ 41 | An Exception for when the IP is defined (does not need to be resolved). 42 | """ 43 | 44 | 45 | class ASNLookupError(BaseIpwhoisException): 46 | """ 47 | An Exception for when the ASN lookup failed. 48 | """ 49 | 50 | 51 | class ASNRegistryError(BaseIpwhoisException): 52 | """ 53 | An Exception for when the ASN registry does not match one of the five 54 | expected values (arin, ripencc, apnic, lacnic, afrinic). 55 | """ 56 | 57 | 58 | class ASNParseError(BaseIpwhoisException): 59 | """ 60 | An Exception for when the ASN parsing failed. 61 | """ 62 | 63 | 64 | class ASNOriginLookupError(BaseIpwhoisException): 65 | """ 66 | An Exception for when the ASN origin lookup failed. 67 | """ 68 | 69 | 70 | class HostLookupError(BaseIpwhoisException): 71 | """ 72 | An Exception for when the host lookup failed. 73 | """ 74 | 75 | 76 | class BlacklistError(BaseIpwhoisException): 77 | """ 78 | An Exception for when the server is in a blacklist. 79 | """ 80 | 81 | 82 | class WhoisLookupError(BaseIpwhoisException): 83 | """ 84 | An Exception for when the whois lookup failed. 85 | """ 86 | 87 | 88 | class WhoisRateLimitError(BaseIpwhoisException): 89 | """ 90 | An Exception for when Whois queries exceed the NIC's request limit and have 91 | exhausted all retries. 92 | """ 93 | 94 | 95 | class HTTPLookupError(BaseIpwhoisException): 96 | """ 97 | An Exception for when the RDAP lookup failed. 98 | """ 99 | 100 | 101 | class HTTPRateLimitError(BaseIpwhoisException): 102 | """ 103 | An Exception for when HTTP queries exceed the NIC's request limit and have 104 | exhausted all retries. 105 | """ 106 | 107 | 108 | class InvalidEntityContactObject(BaseIpwhoisException): 109 | """ 110 | An Exception for when JSON output is not an RDAP entity contact information 111 | object: 112 | https://tools.ietf.org/html/rfc7483#section-5.4 113 | """ 114 | 115 | 116 | class InvalidNetworkObject(BaseIpwhoisException): 117 | """ 118 | An Exception for when JSON output is not an RDAP network object: 119 | https://tools.ietf.org/html/rfc7483#section-5.4 120 | """ 121 | 122 | 123 | class InvalidEntityObject(BaseIpwhoisException): 124 | """ 125 | An Exception for when JSON output is not an RDAP entity object: 126 | https://tools.ietf.org/html/rfc7483#section-5.1 127 | """ 128 | -------------------------------------------------------------------------------- /ipwhois/scripts/ipwhois_utils_cli.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2024 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 | 25 | # CLI python script interface for ipwhois.utils lookups. 26 | 27 | import argparse 28 | from collections import OrderedDict 29 | import json 30 | from ipwhois.utils import (ipv4_lstrip_zeros, calculate_cidr, get_countries, 31 | ipv4_is_defined, ipv6_is_defined, 32 | ipv4_generate_random, ipv6_generate_random, 33 | unique_everseen, unique_addresses) 34 | 35 | # CLI ANSI rendering 36 | ANSI = { 37 | 'end': '\033[0m', 38 | 'b': '\033[1m', 39 | 'ul': '\033[4m', 40 | 'red': '\033[31m', 41 | 'green': '\033[32m', 42 | 'yellow': '\033[33m', 43 | 'cyan': '\033[36m' 44 | } 45 | 46 | # Setup the arg parser. 47 | parser = argparse.ArgumentParser( 48 | description='ipwhois utilities CLI interface' 49 | ) 50 | parser.add_argument( 51 | '--ipv4_lstrip_zeros', 52 | type=str, 53 | nargs=1, 54 | metavar='"IP ADDRESS"', 55 | help='Strip leading zeros in each octet of an IPv4 address.' 56 | ) 57 | parser.add_argument( 58 | '--calculate_cidr', 59 | type=str, 60 | nargs=2, 61 | metavar='"IP ADDRESS"', 62 | help='Calculate a CIDR range(s) from a start and end IP address.' 63 | ) 64 | parser.add_argument( 65 | '--get_countries', 66 | action='store_true', 67 | help='Output a dictionary containing ISO_3166-1 country codes to names.' 68 | ) 69 | parser.add_argument( 70 | '--get_country', 71 | type=str, 72 | nargs=1, 73 | metavar='"COUNTRY CODE"', 74 | help='Output the ISO_3166-1 name for a country code.' 75 | ) 76 | parser.add_argument( 77 | '--ipv4_is_defined', 78 | type=str, 79 | nargs=1, 80 | metavar='"IP ADDRESS"', 81 | help='Check if an IPv4 address is defined (in a reserved address range).' 82 | ) 83 | parser.add_argument( 84 | '--ipv6_is_defined', 85 | type=str, 86 | nargs=1, 87 | metavar='"IP ADDRESS"', 88 | help='Check if an IPv6 address is defined (in a reserved address range).' 89 | ) 90 | parser.add_argument( 91 | '--ipv4_generate_random', 92 | type=int, 93 | nargs=1, 94 | metavar='TOTAL', 95 | help='Generate random, unique IPv4 addresses that are not defined (can be ' 96 | 'looked up using ipwhois).' 97 | ) 98 | parser.add_argument( 99 | '--ipv6_generate_random', 100 | type=int, 101 | nargs=1, 102 | metavar='TOTAL', 103 | help='Generate random, unique IPv6 addresses that are not defined (can be ' 104 | 'looked up using ipwhois).' 105 | ) 106 | parser.add_argument( 107 | '--unique_everseen', 108 | type=json.loads, 109 | nargs=1, 110 | metavar='"ITERABLE"', 111 | help='List unique elements from input iterable, preserving the order.' 112 | ) 113 | parser.add_argument( 114 | '--unique_addresses', 115 | type=str, 116 | nargs=1, 117 | metavar='"FILE PATH"', 118 | help='Search an input file, extracting, counting, and summarizing ' 119 | 'IPv4/IPv6 addresses/networks.' 120 | ) 121 | 122 | # Output options 123 | group = parser.add_argument_group('Output options') 124 | group.add_argument( 125 | '--colorize', 126 | action='store_true', 127 | help='If set, colorizes the output using ANSI. Should work in most ' 128 | 'platform consoles.' 129 | ) 130 | 131 | # Get the args 132 | script_args = parser.parse_args() 133 | 134 | if script_args.ipv4_lstrip_zeros: 135 | 136 | print(ipv4_lstrip_zeros(address=script_args.ipv4_lstrip_zeros[0])) 137 | 138 | elif script_args.calculate_cidr: 139 | 140 | try: 141 | 142 | result = calculate_cidr( 143 | start_address=script_args.calculate_cidr[0], 144 | end_address=script_args.calculate_cidr[1] 145 | ) 146 | 147 | print('{0}Found {1} CIDR blocks for ({2}, {3}){4}:\n{5}'.format( 148 | ANSI['green'] if script_args.colorize else '', 149 | len(result), 150 | script_args.calculate_cidr[0], 151 | script_args.calculate_cidr[1], 152 | ANSI['end'] if script_args.colorize else '', 153 | '\n'.join(result) 154 | )) 155 | 156 | except Exception as e: 157 | 158 | print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e))) 159 | 160 | elif script_args.get_countries: 161 | 162 | try: 163 | 164 | result = get_countries() 165 | 166 | print('{0}Found {1} countries{2}:\n{3}'.format( 167 | ANSI['green'] if script_args.colorize else '', 168 | len(result), 169 | ANSI['end'] if script_args.colorize else '', 170 | '\n'.join(['{0}: {1}'.format(k, v) for k, v in ( 171 | OrderedDict(sorted(result.items())).iteritems())]) 172 | )) 173 | 174 | except Exception as e: 175 | 176 | print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e))) 177 | 178 | elif script_args.get_country: 179 | 180 | try: 181 | 182 | countries = get_countries() 183 | result = countries[script_args.get_country[0].upper()] 184 | 185 | print('{0}Match found for country code ({1}){2}:\n{3}'.format( 186 | ANSI['green'] if script_args.colorize else '', 187 | script_args.get_country[0], 188 | ANSI['end'] if script_args.colorize else '', 189 | result 190 | )) 191 | 192 | except Exception as e: 193 | 194 | print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e))) 195 | 196 | elif script_args.ipv4_is_defined: 197 | 198 | try: 199 | 200 | result = ipv4_is_defined(address=script_args.ipv4_is_defined[0]) 201 | 202 | if result[0]: 203 | print('{0}{1} is defined{2}:\n{3}'.format( 204 | ANSI['green'] if script_args.colorize else '', 205 | script_args.ipv4_is_defined[0], 206 | ANSI['end'] if script_args.colorize else '', 207 | 'Name: {0}\nRFC: {1}'.format(result[1], result[2]) 208 | )) 209 | else: 210 | print('{0}{1} is not defined{2}'.format( 211 | ANSI['yellow'] if script_args.colorize else '', 212 | script_args.ipv4_is_defined[0], 213 | ANSI['end'] if script_args.colorize else '' 214 | )) 215 | 216 | except Exception as e: 217 | 218 | print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e))) 219 | 220 | elif script_args.ipv6_is_defined: 221 | 222 | try: 223 | 224 | result = ipv6_is_defined(address=script_args.ipv6_is_defined[0]) 225 | 226 | if result[0]: 227 | print('{0}{1} is defined{2}:\n{3}'.format( 228 | ANSI['green'] if script_args.colorize else '', 229 | script_args.ipv6_is_defined[0], 230 | ANSI['end'] if script_args.colorize else '', 231 | 'Name: {0}\nRFC: {1}'.format(result[1], result[2]) 232 | )) 233 | else: 234 | print('{0}{1} is not defined{2}'.format( 235 | ANSI['yellow'] if script_args.colorize else '', 236 | script_args.ipv6_is_defined[0], 237 | ANSI['end'] if script_args.colorize else '' 238 | )) 239 | 240 | except Exception as e: 241 | 242 | print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e))) 243 | 244 | elif script_args.ipv4_generate_random: 245 | 246 | try: 247 | 248 | result = ipv4_generate_random(total=script_args.ipv4_generate_random[0]) 249 | 250 | for random_ip in result: 251 | 252 | print(random_ip) 253 | 254 | except Exception as e: 255 | 256 | print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e))) 257 | 258 | elif script_args.ipv6_generate_random: 259 | 260 | try: 261 | 262 | result = ipv6_generate_random(total=script_args.ipv6_generate_random[0]) 263 | 264 | for random_ip in result: 265 | 266 | print(random_ip) 267 | 268 | except Exception as e: 269 | 270 | print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e))) 271 | 272 | elif script_args.unique_everseen: 273 | 274 | try: 275 | 276 | result = list(unique_everseen(iterable=script_args.unique_everseen[0])) 277 | 278 | print('{0}Unique everseen{1}:\n{2}'.format( 279 | ANSI['green'] if script_args.colorize else '', 280 | ANSI['end'] if script_args.colorize else '', 281 | result 282 | )) 283 | 284 | except Exception as e: 285 | 286 | print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e))) 287 | 288 | elif script_args.unique_addresses: 289 | 290 | try: 291 | 292 | result = unique_addresses(file_path=script_args.unique_addresses[0]) 293 | 294 | tmp = [] 295 | for k, v in sorted(result.items(), key=lambda kv: int(kv[1]['count']), 296 | reverse=True): 297 | tmp.append('{0}{1}{2}: Count: {3}, Ports: {4}'.format( 298 | ANSI['b'] if script_args.colorize else '', 299 | k, 300 | ANSI['end'] if script_args.colorize else '', 301 | v['count'], 302 | json.dumps(v['ports']) 303 | )) 304 | 305 | print('{0}Found {1} unique addresses{2}:\n{3}'.format( 306 | ANSI['green'] if script_args.colorize else '', 307 | len(result), 308 | ANSI['end'] if script_args.colorize else '', 309 | '\n'.join(tmp) 310 | )) 311 | 312 | except Exception as e: 313 | 314 | print('{0}Error{1}: {2}'.format(ANSI['red'], ANSI['end'], str(e))) 315 | -------------------------------------------------------------------------------- /ipwhois/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestCommon(unittest.TestCase): 5 | longMessage = False 6 | 7 | if not hasattr(unittest.TestCase, 'assertIsInstance'): 8 | def assertIsInstance(self, obj, cls, msg=None): 9 | if not isinstance(obj, cls): 10 | self.fail(self._formatMessage( 11 | msg, 12 | '{0} is not an instance of {1}'.format(obj, cls) 13 | )) 14 | -------------------------------------------------------------------------------- /ipwhois/tests/entity.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "eventAction": "last changed", 5 | "eventDate": "2015-09-01T14:03:11-04:00" 6 | }, 7 | { 8 | "eventAction": "registration", 9 | "eventDate": "2000-11-30T13:54:08-05:00" 10 | } 11 | ], 12 | "handle": "ZG39-ARIN", 13 | "links": [ 14 | { 15 | "href": "https://rdap.arin.net/registry/entity/ZG39-ARIN", 16 | "rel": "self", 17 | "type": "application/rdap+json", 18 | "value": "https://rdap.arin.net/registry/entity/ZG39-ARIN" 19 | }, 20 | { 21 | "href": "http://whois.arin.net/rest/poc/ZG39-ARIN", 22 | "rel": "alternate", 23 | "type": "application/xml", 24 | "value": "https://rdap.arin.net/registry/entity/ZG39-ARIN" 25 | } 26 | ], 27 | "notices": [ 28 | { 29 | "description": [ 30 | "By using the ARIN RDAP/Whois service, you are agreeing to the RDAP/Whois Terms of Use" 31 | ], 32 | "links": [ 33 | { 34 | "href": "https://www.arin.net/whois_tou.html", 35 | "rel": "about", 36 | "type": "text/html", 37 | "value": "https://rdap.arin.net/registry/entity/ZG39-ARIN" 38 | } 39 | ], 40 | "title": "Terms of Service" 41 | } 42 | ], 43 | "objectClassName": "entity", 44 | "port43": "whois.arin.net", 45 | "rdapConformance": [ 46 | "rdap_level_0" 47 | ], 48 | "status": [ 49 | "validated" 50 | ], 51 | "vcardArray": [ 52 | "vcard", 53 | [ 54 | [ 55 | "version", 56 | {}, 57 | "text", 58 | 4 59 | ], 60 | [ 61 | "adr", 62 | { 63 | "label": "1600 Amphitheatre Parkway\nMountain View\nCA\n94043\nUNITED STATES" 64 | }, 65 | "text", 66 | [ 67 | "", 68 | "", 69 | "", 70 | "", 71 | "", 72 | "", 73 | "" 74 | ] 75 | ], 76 | [ 77 | "fn", 78 | {}, 79 | "text", 80 | "Google Inc" 81 | ], 82 | [ 83 | "org", 84 | {}, 85 | "text", 86 | "Google Inc" 87 | ], 88 | [ 89 | "kind", 90 | {}, 91 | "text", 92 | "group" 93 | ], 94 | [ 95 | "email", 96 | {}, 97 | "text", 98 | "arin-contact@google.com" 99 | ], 100 | [ 101 | "tel", 102 | { 103 | "type": [ 104 | "work", 105 | "voice" 106 | ] 107 | }, 108 | "text", 109 | "+1-650-253-0000" 110 | ] 111 | ] 112 | ] 113 | } -------------------------------------------------------------------------------- /ipwhois/tests/jpnic.json: -------------------------------------------------------------------------------- 1 | {"133.1.2.5": {"nir": "jpnic", "response": "\n\n\n\nJPNIC Whois Gateway\n\n\n
\n[ JPNIC database provides information regarding IP address and ASN. Its use   ]\n[ is restricted to network administration purposes. For further information,  ]\n[ use 'whois -h whois.nic.ad.jp help'. To only display English output,        ]\n[ add '/e' at the end of command, e.g. 'whois -h whois.nic.ad.jp xxx/e'.      ]\n\nNetwork Information:            \na. [Network Number]             133.1.0.0/16\nb. [Network Name]               OSAKAU-NET\ng. [Organization]               Osaka University\nm. [Administrative Contact]     MY22537JP\nn. [Technical Contact]          MY22537JP\np. [Nameserver]                 a.osaka-u.ac.jp\np. [Nameserver]                 b.osaka-u.ac.jp\np. [Nameserver]                 dns-x.sinet.ad.jp\n[Assigned Date]                 \n[Return Date]                   \n[Last Update]                   2015/01/14 11:50:03(JST)\n                                \nLess Specific Info.\n----------\nNo match!!\n\nMore Specific Info.\n----------\nNo match!!\n\n
\n
\nBack to Whois Gateway top menu\n\n\n"}} -------------------------------------------------------------------------------- /ipwhois/tests/krnic.json: -------------------------------------------------------------------------------- 1 | {"115.1.2.3": {"nir": "krnic", "response": "\r\n\r\n
query : 115.1.2.3\n\n\n# KOREAN(UTF8)\n\n IPv4    \n,    .\n\n[    ]\nIPv4           : 115.0.0.0 - 115.23.255.255 (/12+/13)\n             :  \n           : KORNET\n               :     90\n           : 13606\n           : 20080703\n\n   \n
: IP \n : +82-2-500-6630\n : kornet_ip@kt.com\n
\n IPv4 , \n .\n--------------------------------------------------------------------------------\n\n\n[ ]\nIPv4 : 115.1.2.0 - 115.1.2.63 (/26)\n : () \n : CUSTOMER\n : \n : 486-800\n : 20150317\n\n \n
: IP \n : +82-2-500-6630\n : kornet_ip@kt.com\n
\n\n# ENGLISH\n\nKRNIC is not an ISP but a National Internet Registry similar to APNIC.\n\n[ Network Information ]\nIPv4 Address : 115.0.0.0 - 115.23.255.255 (/12+/13)\nOrganization Name : Korea Telecom\nService Name : KORNET\nAddress : Gyeonggi-do Bundang-gu, Seongnam-si Buljeong-ro 90\nZip Code : 13606\nRegistration Date : 20080703\n\n See the Contact Info\n
Name : IP Manager\nPhone : +82-2-500-6630\nE-Mail : kornet_ip@kt.com\n
\n--------------------------------------------------------------------------------\n\nMore specific assignment information is as follows.\n\n[ Network Information ]\nIPv4 Address : 115.1.2.0 - 115.1.2.63 (/26)\nOrganization Name : KT\nNetwork Type : CUSTOMER\nAddress : Yeoncheon-Eup Yeoncheon-Gun Gyeonggi-Do\nZip Code : 486-800\nRegistration Date : 20150317\n\n See the Contact Info\n
Name : IP Manager\nPhone : +82-2-500-6630\nE-Mail : kornet_ip@kt.com\n
\n\n- KISA/KRNIC WHOIS Service -\n\n\n\n
\r\n\r\n\r\n\r\n"}} -------------------------------------------------------------------------------- /ipwhois/tests/online/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secynic/ipwhois/b8c79c4e902467ccac0d841ad6ef820b1627e357/ipwhois/tests/online/__init__.py -------------------------------------------------------------------------------- /ipwhois/tests/online/test_asn.py: -------------------------------------------------------------------------------- 1 | import json 2 | import io 3 | from os import path 4 | import logging 5 | from ipwhois.tests import TestCommon 6 | from ipwhois.exceptions import (ASNOriginLookupError, ASNRegistryError) 7 | from ipwhois.net import Net 8 | from ipwhois.asn import (IPASN, ASNOrigin) 9 | 10 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 11 | '[%(funcName)s()] %(message)s') 12 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 13 | log = logging.getLogger(__name__) 14 | 15 | 16 | class TestIPASN(TestCommon): 17 | 18 | def test_lookup(self): 19 | 20 | net = Net('74.125.225.229') 21 | ipasn = IPASN(net) 22 | 23 | try: 24 | self.assertIsInstance(ipasn.lookup(inc_raw=True), dict) 25 | except ASNRegistryError: 26 | pass 27 | except AssertionError as e: 28 | raise e 29 | except Exception as e: 30 | self.fail('Unexpected exception raised: {0}'.format(e)) 31 | 32 | self.assertRaises(ValueError, ipasn.lookup, **dict( 33 | asn_methods=['asd'])) 34 | 35 | ipasn.lookup(asn_methods=['dns', 'whois', 'http']) 36 | ipasn.lookup(asn_methods=['http']) 37 | 38 | net = Net(address='74.125.225.229', timeout=0) 39 | ipasn = IPASN(net) 40 | self.assertRaises(ASNRegistryError, ipasn.lookup) 41 | 42 | net = Net(address='74.125.225.229', timeout=0) 43 | ipasn = IPASN(net) 44 | self.assertRaises(ASNRegistryError, ipasn.lookup, **dict( 45 | asn_methods=['http'])) 46 | 47 | 48 | class TestASNOrigin(TestCommon): 49 | 50 | def test_lookup(self): 51 | 52 | data_dir = path.abspath(path.join(path.dirname(__file__), '..')) 53 | 54 | with io.open(str(data_dir) + '/asn.json', 'r') as \ 55 | data_file: 56 | data = json.load(data_file) 57 | 58 | # IP doesn't matter here 59 | net = Net('74.125.225.229') 60 | 61 | for key, val in data.items(): 62 | 63 | log.debug('Testing: {0} - {1}'.format(key, val['asn'])) 64 | 65 | obj = ASNOrigin(net) 66 | try: 67 | 68 | self.assertIsInstance( 69 | obj.lookup( 70 | asn=val['asn'] 71 | ), 72 | dict 73 | ) 74 | 75 | except ASNOriginLookupError: 76 | 77 | pass 78 | 79 | except AssertionError as e: 80 | 81 | raise e 82 | 83 | except Exception as e: 84 | 85 | self.fail('Unexpected exception raised: {0}'.format(e)) 86 | 87 | net = Net(address='74.125.225.229', timeout=0) 88 | asnorigin = ASNOrigin(net) 89 | self.assertRaises(ASNOriginLookupError, asnorigin.lookup, **dict( 90 | asn='15169', 91 | asn_methods=['http'])) 92 | 93 | self.assertRaises(ValueError, asnorigin.lookup, **dict( 94 | asn='15169', 95 | asn_methods=['asd'])) 96 | 97 | net = Net(address='74.125.225.229') 98 | asnorigin = ASNOrigin(net) 99 | asnorigin.lookup(asn='15169', asn_methods=['whois']) 100 | asnorigin.lookup(asn='15169', asn_methods=['http']) 101 | -------------------------------------------------------------------------------- /ipwhois/tests/online/test_experimental.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from ipwhois.tests import TestCommon 3 | from ipwhois.exceptions import (ASNLookupError) 4 | from ipwhois.experimental import (get_bulk_asn_whois, bulk_lookup_rdap) 5 | 6 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 7 | '[%(funcName)s()] %(message)s') 8 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 9 | log = logging.getLogger(__name__) 10 | 11 | 12 | class TestExperimental(TestCommon): 13 | 14 | def test_get_bulk_asn_whois(self): 15 | 16 | ips = [ 17 | '74.125.225.229', # ARIN 18 | '2001:4860:4860::8888', 19 | '62.239.237.1', # RIPE 20 | '2a00:2381:ffff::1', 21 | '210.107.73.73', # APNIC 22 | '2001:240:10c:1::ca20:9d1d', 23 | '200.57.141.161', # LACNIC 24 | '2801:10:c000::', 25 | '196.11.240.215', # AFRINIC 26 | '2001:43f8:7b0::', 27 | '133.1.2.5', # JPNIC 28 | '115.1.2.3' # KRNIC 29 | ] 30 | 31 | try: 32 | self.assertIsInstance(get_bulk_asn_whois(addresses=ips), str) 33 | except ASNLookupError: 34 | pass 35 | except AssertionError as e: 36 | raise e 37 | except Exception as e: 38 | self.fail('Unexpected exception raised: {0}'.format(e)) 39 | 40 | def test_bulk_lookup_rdap(self): 41 | 42 | try: 43 | from urllib.request import (OpenerDirector, 44 | ProxyHandler, 45 | build_opener) 46 | except ImportError: 47 | from urllib2 import (OpenerDirector, 48 | ProxyHandler, 49 | build_opener) 50 | 51 | handler = ProxyHandler() 52 | opener = build_opener(handler) 53 | bulk_lookup_rdap(addresses=['74.125.225.229'], proxy_openers=[opener]) 54 | 55 | ips = [ 56 | '74.125.225.229', # ARIN 57 | '2001:4860:4860::8888', 58 | '62.239.237.1', # RIPE 59 | '2a00:2381:ffff::1', 60 | '210.107.73.73', # APNIC 61 | '2001:240:10c:1::ca20:9d1d', 62 | '200.57.141.161', # LACNIC 63 | '2801:10:c000::', 64 | '196.11.240.215', # AFRINIC 65 | '2001:43f8:7b0::', 66 | '133.1.2.5', # JPNIC 67 | '115.1.2.3' # KRNIC 68 | ] 69 | 70 | expected_stats = {'ip_input_total': 12, 'ip_unique_total': 12, 71 | 'ip_lookup_total': 12, 'ip_failed_total': 0, 72 | 'lacnic': {'failed': [], 'rate_limited': [], 'total': 2}, 73 | 'ripencc': {'failed': [], 'rate_limited': [], 'total': 2}, 74 | 'apnic': {'failed': [], 'rate_limited': [], 'total': 4}, 75 | 'afrinic': {'failed': [], 'rate_limited': [], 'total': 2}, 76 | 'arin': {'failed': [], 'rate_limited': [], 'total': 2}, 77 | 'unallocated_addresses': []} 78 | 79 | try: 80 | result = bulk_lookup_rdap(addresses=ips) 81 | self.assertIsInstance(result, tuple) 82 | 83 | results, stats = result 84 | self.assertEqual(stats, expected_stats) 85 | self.assertEqual(len(results), 12) 86 | 87 | except ASNLookupError: 88 | pass 89 | except AssertionError as e: 90 | raise e 91 | except Exception as e: 92 | self.fail('Unexpected exception raised: {0}'.format(e)) 93 | -------------------------------------------------------------------------------- /ipwhois/tests/online/test_ipwhois.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from ipwhois.tests import TestCommon 3 | from ipwhois.exceptions import (ASNLookupError, ASNRegistryError, 4 | WhoisLookupError, HTTPLookupError, 5 | BlacklistError) 6 | from ipwhois.ipwhois import IPWhois 7 | 8 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 9 | '[%(funcName)s()] %(message)s') 10 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | class TestIPWhois(TestCommon): 15 | 16 | def test_lookup_whois(self): 17 | 18 | ips = [ 19 | '74.125.225.229', # ARIN 20 | '2001:4860:4860::8888', 21 | '62.239.237.1', # RIPE 22 | '2a00:2381:ffff::1', 23 | '210.107.73.73', # APNIC 24 | '2001:240:10c:1::ca20:9d1d', 25 | '200.57.141.161', # LACNIC 26 | '2801:10:c000::', 27 | '196.11.240.215', # AFRINIC 28 | '2001:43f8:7b0::', 29 | '133.1.2.5', # JPNIC 30 | '115.1.2.3' # KRNIC 31 | ] 32 | 33 | for ip in ips: 34 | 35 | timeout = 5 36 | retry_count = 3 37 | 38 | # JPNIC doesn't like my testing 39 | if ip in ('133.1.2.5', '2001:240:10c:1::ca20:9d1d'): 40 | 41 | timeout = 15 42 | retry_count = 10 43 | 44 | log.debug('Testing: {0}'.format(ip)) 45 | result = IPWhois(address=ip, timeout=timeout) 46 | 47 | try: 48 | # TODO: keep until deprecated lookup is removed, for coverage 49 | self.assertIsInstance(result.lookup_whois( 50 | retry_count=retry_count), dict) 51 | except (ASNLookupError, ASNRegistryError, WhoisLookupError, 52 | HTTPLookupError): 53 | pass 54 | except AssertionError as e: 55 | raise e 56 | except Exception as e: 57 | self.fail('Unexpected exception raised: {0}'.format(e)) 58 | 59 | rwhois_ips = [ 60 | '38.113.116.218' # COGNETCO 61 | ] 62 | 63 | for ip in rwhois_ips: 64 | 65 | result = IPWhois(ip) 66 | try: 67 | self.assertIsInstance(result.lookup_whois( 68 | get_referral=True, 69 | ignore_referral_errors=True, 70 | inc_raw=True), dict) 71 | except (ASNLookupError, ASNRegistryError, WhoisLookupError): 72 | pass 73 | except AssertionError as e: 74 | raise e 75 | except Exception as e: 76 | self.fail('Unexpected exception raised: {0}'.format(e)) 77 | 78 | for ip in rwhois_ips: 79 | 80 | result = IPWhois(ip) 81 | try: 82 | self.assertIsInstance(result.lookup_whois( 83 | get_referral=True, 84 | ignore_referral_errors=True, 85 | inc_raw=True, 86 | extra_blacklist=['rwhois.cogentco.com']), dict) 87 | except (ASNLookupError, ASNRegistryError, WhoisLookupError): 88 | pass 89 | except AssertionError as e: 90 | raise e 91 | except Exception as e: 92 | self.fail('Unexpected exception raised: {0}'.format(e)) 93 | 94 | try: 95 | self.assertIsInstance(result.lookup_whois( 96 | get_referral=True, 97 | ignore_referral_errors=False, 98 | inc_raw=True, 99 | extra_blacklist=['rwhois.cogentco.com']), dict) 100 | except (ASNLookupError, ASNRegistryError, WhoisLookupError, 101 | BlacklistError): 102 | pass 103 | except AssertionError as e: 104 | raise e 105 | except Exception as e: 106 | self.fail('Unexpected exception raised: {0}'.format(e)) 107 | 108 | break 109 | 110 | for ip in rwhois_ips: 111 | 112 | result = IPWhois(ip) 113 | try: 114 | self.assertIsInstance(result.lookup_whois( 115 | get_referral=True, 116 | ignore_referral_errors=False, 117 | inc_raw=False), dict) 118 | except (ASNLookupError, ASNRegistryError, WhoisLookupError): 119 | pass 120 | except AssertionError as e: 121 | raise e 122 | except Exception as e: 123 | self.fail('Unexpected exception raised: {0}'.format(e)) 124 | 125 | break 126 | 127 | def test_lookup_rdap(self): 128 | try: 129 | from urllib.request import ProxyHandler, build_opener 130 | except ImportError: 131 | from urllib2 import ProxyHandler, build_opener 132 | 133 | ips = [ 134 | '74.125.225.229', # ARIN 135 | '2001:4860:4860::8888', 136 | '62.239.237.1', # RIPE 137 | '2a00:2381:ffff::1', 138 | '210.107.73.73', # APNIC 139 | '2001:240:10c:1::ca20:9d1d', 140 | '200.57.141.161', # LACNIC 141 | '2801:10:c000::', 142 | '196.11.240.215', # AFRINIC 143 | '2001:43f8:7b0::', 144 | '133.1.2.5', # JPNIC 145 | '115.1.2.3' # KRNIC 146 | ] 147 | 148 | for ip in ips: 149 | 150 | timeout = 5 151 | retry_count = 3 152 | 153 | # JPNIC doesn't like my testing 154 | if ip in ('133.1.2.5', '2001:240:10c:1::ca20:9d1d'): 155 | timeout = 15 156 | retry_count = 10 157 | 158 | log.debug('Testing: {0}'.format(ip)) 159 | result = IPWhois(address=ip, timeout=timeout) 160 | 161 | try: 162 | self.assertIsInstance(result.lookup_rdap( 163 | retry_count=retry_count), dict) 164 | except (ASNLookupError, ASNRegistryError, WhoisLookupError, 165 | HTTPLookupError): 166 | pass 167 | except AssertionError as e: 168 | raise e 169 | except Exception as e: 170 | self.fail('Unexpected exception raised: {0}'.format(e)) 171 | 172 | handler = ProxyHandler({'http': 'http://0.0.0.0:80/'}) 173 | opener = build_opener(handler) 174 | result = IPWhois(address='74.125.225.229', timeout=0, 175 | proxy_opener=opener) 176 | self.assertRaises(ASNRegistryError, result.lookup_rdap) 177 | -------------------------------------------------------------------------------- /ipwhois/tests/online/test_net.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from ipwhois.tests import TestCommon 3 | from ipwhois.exceptions import (ASNLookupError, ASNRegistryError, 4 | BlacklistError, WhoisLookupError, 5 | HTTPLookupError, HostLookupError) 6 | from ipwhois.asn import IPASN 7 | from ipwhois.net import Net 8 | 9 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 10 | '[%(funcName)s()] %(message)s') 11 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 12 | log = logging.getLogger(__name__) 13 | 14 | 15 | class TestNet(TestCommon): 16 | 17 | def test_lookup_asn(self): 18 | 19 | net = Net('74.125.225.229') 20 | ipasn = IPASN(net) 21 | 22 | try: 23 | self.assertIsInstance(ipasn.lookup(), dict) 24 | except HTTPLookupError: 25 | pass 26 | except AssertionError as e: 27 | raise e 28 | except Exception as e: 29 | self.fail('Unexpected exception raised: {0}'.format(e)) 30 | 31 | def test_get_asn_dns(self): 32 | result = Net('74.125.225.229') 33 | try: 34 | self.assertIsInstance(result.get_asn_dns(), list) 35 | except ASNLookupError: 36 | pass 37 | except AssertionError as e: 38 | raise e 39 | except Exception as e: 40 | self.fail('Unexpected exception raised: {0}'.format(e)) 41 | 42 | result.dns_zone = 'a' 43 | self.assertRaises(ASNLookupError, result.get_asn_dns) 44 | 45 | def test_get_asn_verbose_dns(self): 46 | result = Net('74.125.225.229') 47 | try: 48 | self.assertIsInstance(result.get_asn_verbose_dns(asn='15169'), str) 49 | except ASNLookupError: 50 | pass 51 | except AssertionError as e: 52 | raise e 53 | except Exception as e: 54 | self.fail('Unexpected exception raised: {0}'.format(e)) 55 | 56 | self.assertRaises(ASNLookupError, result.get_asn_verbose_dns, **dict( 57 | asn='a' 58 | )) 59 | 60 | def test_get_asn_whois(self): 61 | result = Net('74.125.225.229') 62 | try: 63 | self.assertIsInstance(result.get_asn_whois(), str) 64 | except ASNLookupError: 65 | pass 66 | except AssertionError as e: 67 | raise e 68 | except Exception as e: 69 | self.fail('Unexpected exception raised: {0}'.format(e)) 70 | 71 | def test_get_asn_http(self): 72 | result = Net('74.125.225.229') 73 | try: 74 | self.assertIsInstance(result.get_asn_http(), dict) 75 | except ASNLookupError: 76 | pass 77 | except AssertionError as e: 78 | raise e 79 | except Exception as e: 80 | self.fail('Unexpected exception raised: {0}'.format(e)) 81 | 82 | def test_get_whois(self): 83 | result = Net('74.125.225.229') 84 | try: 85 | self.assertIsInstance(result.get_whois(), str) 86 | except WhoisLookupError: 87 | pass 88 | except AssertionError as e: 89 | raise e 90 | except Exception as e: 91 | self.fail('Unexpected exception raised: {0}'.format(e)) 92 | 93 | self.assertRaises(WhoisLookupError, result.get_whois, **dict( 94 | retry_count=0, server='arin.net')) 95 | 96 | self.assertRaises(BlacklistError, result.get_whois, **dict( 97 | server='whois.arin.net', extra_blacklist=['whois.arin.net'])) 98 | 99 | result = Net(address='74.125.225.229', timeout=0) 100 | self.assertRaises(WhoisLookupError, result.get_whois, **dict( 101 | retry_count=1)) 102 | 103 | def test_get_asn_origin_whois(self): 104 | # IP doesn't matter here 105 | result = Net('74.125.225.229') 106 | 107 | try: 108 | self.assertIsInstance(result.get_asn_origin_whois( 109 | asn='AS15169'), str) 110 | except WhoisLookupError: 111 | pass 112 | except AssertionError as e: 113 | raise e 114 | except Exception as e: 115 | self.fail('Unexpected exception raised: {0}'.format(e)) 116 | 117 | self.assertRaises(WhoisLookupError, result.get_asn_origin_whois, 118 | **dict(asn='AS15169', retry_count=0, 119 | server='radb.net')) 120 | 121 | # IP doesn't matter here 122 | result = Net(address='74.125.225.229', timeout=0) 123 | self.assertRaises(WhoisLookupError, result.get_asn_origin_whois, 124 | **dict(asn='AS15169', retry_count=1)) 125 | 126 | def test_get_http_json(self): 127 | from ipwhois.rdap import RIR_RDAP 128 | result = Net('74.125.225.229') 129 | try: 130 | self.assertIsInstance(result.get_http_json( 131 | RIR_RDAP['arin']['ip_url'].format('74.125.225.229')), dict) 132 | except HTTPLookupError: 133 | pass 134 | except AssertionError as e: 135 | raise e 136 | except Exception as e: 137 | self.fail('Unexpected exception raised: {0}'.format(e)) 138 | 139 | self.assertRaises(HTTPLookupError, result.get_http_json, **dict( 140 | url='http://255.255.255.255', retry_count=1)) 141 | 142 | result = Net(address='74.125.225.229', timeout=0) 143 | url = RIR_RDAP['arin']['ip_url'].format('74.125.225.229') 144 | self.assertRaises(HTTPLookupError, result.get_http_json, **dict( 145 | url=url, retry_count=0)) 146 | 147 | def test_get_host(self): 148 | ips = [ 149 | '74.125.225.229', # ARIN 150 | '2001:4860:4860::8888' 151 | ] 152 | 153 | for ip in ips: 154 | result = Net(ip) 155 | try: 156 | self.assertIsInstance(result.get_host(0), tuple) 157 | except HostLookupError: 158 | pass 159 | except AssertionError as e: 160 | raise e 161 | except Exception as e: 162 | self.fail('Unexpected exception raised: {0}'.format(e)) 163 | 164 | result = Net('74.125.225.229', 0) 165 | self.assertRaises(HostLookupError, result.get_host, **dict( 166 | retry_count=1)) 167 | 168 | def test_get_http_raw(self): 169 | from ipwhois.nir import NIR_WHOIS 170 | 171 | # GET 172 | result = Net('133.1.2.5') 173 | try: 174 | self.assertIsInstance(result.get_http_raw( 175 | NIR_WHOIS['jpnic']['url'].format('133.1.2.5')), str) 176 | except HTTPLookupError: 177 | pass 178 | except AssertionError as e: 179 | raise e 180 | except Exception as e: 181 | self.fail('Unexpected exception raised: {0}'.format(e)) 182 | 183 | # POST 184 | result = Net('115.1.2.3') 185 | try: 186 | self.assertIsInstance(result.get_http_raw( 187 | url=NIR_WHOIS['krnic']['url'].format('115.1.2.3'), 188 | request_type=NIR_WHOIS['krnic']['request_type'], 189 | form_data={ 190 | NIR_WHOIS['krnic']['form_data_ip_field']: '115.1.2.3' 191 | } 192 | ), str) 193 | except HTTPLookupError: 194 | pass 195 | except AssertionError as e: 196 | raise e 197 | except Exception as e: 198 | self.fail('Unexpected exception raised: {0}'.format(e)) 199 | 200 | self.assertRaises(HTTPLookupError, result.get_http_raw, **dict( 201 | url='http://255.255.255.255', retry_count=1)) 202 | 203 | result = Net(address='133.1.2.5', timeout=0) 204 | url = NIR_WHOIS['jpnic']['url'].format('133.1.2.5') 205 | self.assertRaises(HTTPLookupError, result.get_http_raw, **dict( 206 | url=url, retry_count=0)) 207 | -------------------------------------------------------------------------------- /ipwhois/tests/online/test_nir.py: -------------------------------------------------------------------------------- 1 | import json 2 | import io 3 | from os import path 4 | import logging 5 | from ipwhois.exceptions import HTTPLookupError 6 | from ipwhois.tests import TestCommon 7 | from ipwhois.net import Net 8 | from ipwhois.nir import NIRWhois 9 | 10 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 11 | '[%(funcName)s()] %(message)s') 12 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 13 | log = logging.getLogger(__name__) 14 | 15 | 16 | class TestNIRWhois(TestCommon): 17 | 18 | def test_lookup(self): 19 | 20 | data_dir = path.abspath(path.join(path.dirname(__file__), '..')) 21 | 22 | with io.open(str(data_dir) + '/jpnic.json', 'r') as data_jpnic: 23 | data = json.load(data_jpnic) 24 | 25 | with io.open(str(data_dir) + '/krnic.json', 'r') as data_krnic: 26 | data.update(json.load(data_krnic)) 27 | 28 | for key, val in data.items(): 29 | 30 | log.debug('Testing: {0}'.format(key)) 31 | net = Net(key) 32 | obj = NIRWhois(net) 33 | 34 | try: 35 | 36 | self.assertIsInstance( 37 | obj.lookup( 38 | nir=val['nir'], 39 | ), 40 | dict 41 | ) 42 | 43 | except HTTPLookupError: 44 | 45 | pass 46 | 47 | except AssertionError as e: 48 | 49 | raise e 50 | 51 | except Exception as e: 52 | 53 | self.fail('Unexpected exception raised: {0}'.format(e)) 54 | -------------------------------------------------------------------------------- /ipwhois/tests/online/test_rdap.py: -------------------------------------------------------------------------------- 1 | import json 2 | import io 3 | from os import path 4 | import logging 5 | from ipwhois.tests import TestCommon 6 | from ipwhois.exceptions import (HTTPLookupError, HTTPRateLimitError) 7 | from ipwhois.rdap import (RDAP, Net) 8 | 9 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 10 | '[%(funcName)s()] %(message)s') 11 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 12 | log = logging.getLogger(__name__) 13 | 14 | 15 | class TestRDAP(TestCommon): 16 | 17 | def test_lookup(self): 18 | 19 | data_dir = path.abspath(path.join(path.dirname(__file__), '..')) 20 | 21 | with io.open(str(data_dir) + '/rdap.json', 'r') as data_file: 22 | data = json.load(data_file) 23 | 24 | for key, val in data.items(): 25 | 26 | log.debug('Testing: {0}'.format(key)) 27 | net = Net(key) 28 | obj = RDAP(net) 29 | 30 | try: 31 | 32 | self.assertIsInstance(obj.lookup(asn_data=val['asn_data'], 33 | depth=1), dict) 34 | 35 | except (HTTPLookupError, HTTPRateLimitError): 36 | 37 | pass 38 | 39 | except AssertionError as e: 40 | 41 | raise e 42 | 43 | except Exception as e: 44 | 45 | self.fail('Unexpected exception raised: {0}'.format(e)) 46 | 47 | for key, val in data.items(): 48 | 49 | log.debug('Testing bootstrap and raw: {0}'.format(key)) 50 | net = Net(key) 51 | obj = RDAP(net) 52 | 53 | try: 54 | 55 | self.assertIsInstance(obj.lookup(asn_data=val['asn_data'], 56 | depth=3, 57 | bootstrap=True, 58 | inc_raw=True), dict) 59 | 60 | except (HTTPLookupError, HTTPRateLimitError): 61 | 62 | pass 63 | 64 | except AssertionError as e: 65 | 66 | raise e 67 | 68 | except Exception as e: 69 | 70 | self.fail('Unexpected exception raised: {0}'.format(e)) 71 | -------------------------------------------------------------------------------- /ipwhois/tests/online/test_whois.py: -------------------------------------------------------------------------------- 1 | import json 2 | import io 3 | from os import path 4 | import logging 5 | from ipwhois.tests import TestCommon 6 | from ipwhois.exceptions import WhoisLookupError 7 | from ipwhois.net import Net 8 | from ipwhois.whois import Whois 9 | 10 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 11 | '[%(funcName)s()] %(message)s') 12 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 13 | log = logging.getLogger(__name__) 14 | 15 | 16 | class TestWhois(TestCommon): 17 | 18 | def test_lookup(self): 19 | 20 | data_dir = path.abspath(path.join(path.dirname(__file__), '..')) 21 | 22 | with io.open(str(data_dir) + '/whois.json', 'r') as data_file: 23 | data = json.load(data_file) 24 | 25 | for key, val in data.items(): 26 | 27 | log.debug('Testing: {0}'.format(key)) 28 | net = Net(key) 29 | obj = Whois(net) 30 | 31 | try: 32 | 33 | self.assertIsInstance( 34 | obj.lookup( 35 | asn_data=val['asn_data'], 36 | get_referral=True, 37 | inc_raw=True, 38 | ignore_referral_errors=True 39 | ), 40 | dict 41 | ) 42 | 43 | except WhoisLookupError: 44 | 45 | pass 46 | 47 | except AssertionError as e: 48 | 49 | raise e 50 | 51 | except Exception as e: 52 | 53 | self.fail('Unexpected exception raised: {0}'.format(e)) 54 | -------------------------------------------------------------------------------- /ipwhois/tests/stress/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secynic/ipwhois/b8c79c4e902467ccac0d841ad6ef820b1627e357/ipwhois/tests/stress/__init__.py -------------------------------------------------------------------------------- /ipwhois/tests/stress/test_experimental.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from ipwhois.tests import TestCommon 3 | from ipwhois.exceptions import (ASNLookupError) 4 | from ipwhois.experimental import (get_bulk_asn_whois, bulk_lookup_rdap) 5 | from ipwhois.utils import (ipv4_generate_random, ipv6_generate_random) 6 | 7 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 8 | '[%(funcName)s()] %(message)s') 9 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 10 | log = logging.getLogger(__name__) 11 | 12 | 13 | class TestExperimental(TestCommon): 14 | 15 | def test_get_bulk_asn_whois(self): 16 | 17 | ips = (list(ipv4_generate_random(500)) + 18 | list(ipv6_generate_random(500))) 19 | try: 20 | self.assertIsInstance(get_bulk_asn_whois(addresses=ips), str) 21 | except ASNLookupError: 22 | pass 23 | except AssertionError as e: 24 | raise e 25 | except Exception as e: 26 | self.fail('Unexpected exception raised: {0}'.format(e)) 27 | 28 | def test_bulk_lookup_rdap(self): 29 | 30 | ips = (list(ipv4_generate_random(250)) + 31 | list(ipv6_generate_random(250))) 32 | try: 33 | self.assertIsInstance(bulk_lookup_rdap(addresses=ips), tuple) 34 | except ASNLookupError: 35 | pass 36 | except AssertionError as e: 37 | raise e 38 | except Exception as e: 39 | self.fail('Unexpected exception raised: {0}'.format(e)) 40 | -------------------------------------------------------------------------------- /ipwhois/tests/stress/test_net.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from ipwhois.tests import TestCommon 3 | from ipwhois.exceptions import (HTTPLookupError, HTTPRateLimitError) 4 | from ipwhois.net import Net 5 | 6 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 7 | '[%(funcName)s()] %(message)s') 8 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 9 | log = logging.getLogger(__name__) 10 | 11 | 12 | class TestNet(TestCommon): 13 | 14 | def test_get_http_json(self): 15 | from ipwhois.rdap import RIR_RDAP 16 | 17 | # Test for HTTPRateLimitError for up to 20 requests. Exits when raised. 18 | url = RIR_RDAP['lacnic']['ip_url'].format('200.57.141.161') 19 | result = Net('200.57.141.161') 20 | count = 20 21 | http_lookup_errors = 0 22 | while count > 0: 23 | log.debug('Attempts left: {0}'.format(str(count))) 24 | count -= 1 25 | try: 26 | self.assertRaises(HTTPRateLimitError, result.get_http_json, 27 | **dict(url=url, retry_count=0)) 28 | break 29 | 30 | except AssertionError as e: 31 | if count == 0: 32 | raise e 33 | else: 34 | pass 35 | 36 | except HTTPLookupError as e: 37 | http_lookup_errors += 1 38 | if http_lookup_errors == 5: 39 | raise Exception('HTTPLookupError has been raised 5 times. ' 40 | 'Likely cause is socket connection ' 41 | 'timeouts. Quitting test to avoid an ' 42 | 'endless loop.') 43 | continue 44 | -------------------------------------------------------------------------------- /ipwhois/tests/test_asn.py: -------------------------------------------------------------------------------- 1 | import json 2 | import io 3 | from os import path 4 | import logging 5 | from ipwhois.tests import TestCommon 6 | from ipwhois.exceptions import (ASNRegistryError, ASNLookupError, 7 | ASNParseError) 8 | from ipwhois.net import Net 9 | from ipwhois.asn import (IPASN, ASNOrigin, ASN_ORIGIN_WHOIS, ASN_ORIGIN_HTTP, 10 | NetError) 11 | 12 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 13 | '[%(funcName)s()] %(message)s') 14 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 15 | log = logging.getLogger(__name__) 16 | 17 | 18 | class TestIPASN(TestCommon): 19 | 20 | def test_IPASN(self): 21 | 22 | self.assertRaises(NetError, IPASN, 'a') 23 | 24 | def test_parse_fields_dns(self): 25 | 26 | data = '"15169 | 74.125.225.0/24 | US | arin | 2007-03-13"' 27 | net = Net('74.125.225.229') 28 | ipasn = IPASN(net) 29 | try: 30 | self.assertIsInstance(ipasn.parse_fields_dns(data), dict) 31 | except AssertionError as e: 32 | raise e 33 | except Exception as e: 34 | self.fail('Unexpected exception raised: {0}'.format(e)) 35 | 36 | data = '"15169 | 74.125.225.0/24 | US | random | 2007-03-13"' 37 | self.assertRaises(ASNRegistryError, ipasn.parse_fields_dns, data) 38 | 39 | data = '' 40 | self.assertRaises(ASNParseError, ipasn.parse_fields_dns, data) 41 | 42 | def test_parse_fields_verbose_dns(self): 43 | 44 | data = '"15169 | US | arin | 2007-03-13 | GOOGLE - Google Inc., US"' 45 | net = Net('74.125.225.229') 46 | ipasn = IPASN(net) 47 | try: 48 | self.assertIsInstance(ipasn.parse_fields_verbose_dns(data), dict) 49 | except AssertionError as e: 50 | raise e 51 | except Exception as e: 52 | self.fail('Unexpected exception raised: {0}'.format(e)) 53 | 54 | data = '"15169 | US | random | 2007-03-13 | GOOGLE - Google Inc., US"' 55 | self.assertRaises(ASNRegistryError, ipasn.parse_fields_verbose_dns, 56 | data) 57 | 58 | data = '' 59 | self.assertRaises(ASNParseError, ipasn.parse_fields_verbose_dns, data) 60 | 61 | def test_parse_fields_whois(self): 62 | 63 | data = ('15169 | 74.125.225.229 | 74.125.225.0/24 | US | arin' 64 | ' | 2007-03-13 | GOOGLE - Google Inc., US') 65 | net = Net('74.125.225.229') 66 | ipasn = IPASN(net) 67 | try: 68 | self.assertIsInstance(ipasn.parse_fields_whois(data), dict) 69 | except AssertionError as e: 70 | raise e 71 | except Exception as e: 72 | self.fail('Unexpected exception raised: {0}'.format(e)) 73 | 74 | data = ('15169 | 74.125.225.229 | 74.125.225.0/24 | US | rdm' 75 | ' | 2007-03-13 | GOOGLE - Google Inc., US') 76 | self.assertRaises(ASNRegistryError, ipasn.parse_fields_whois, data) 77 | 78 | data = '15169 | 74.125.225.229 | 74.125.225.0/24 | US' 79 | self.assertRaises(ASNParseError, ipasn.parse_fields_whois, data) 80 | 81 | def test_parse_fields_http(self): 82 | 83 | data = { 84 | 'nets': { 85 | 'net': { 86 | 'orgRef': { 87 | '@handle': 'APNIC' 88 | } 89 | } 90 | } 91 | } 92 | net = Net('1.2.3.4') 93 | ipasn = IPASN(net) 94 | try: 95 | self.assertIsInstance(ipasn.parse_fields_http(response=data), 96 | dict) 97 | except AssertionError as e: 98 | raise e 99 | except Exception as e: 100 | self.fail('Unexpected exception raised: {0}'.format(e)) 101 | 102 | data['nets']['net']['orgRef']['@handle'] = 'RIPE' 103 | try: 104 | self.assertIsInstance(ipasn.parse_fields_http(response=data), 105 | dict) 106 | except AssertionError as e: 107 | raise e 108 | except Exception as e: 109 | self.fail('Unexpected exception raised: {0}'.format(e)) 110 | 111 | data['nets']['net']['orgRef']['@handle'] = 'DNIC' 112 | try: 113 | self.assertIsInstance(ipasn.parse_fields_http(response=data), 114 | dict) 115 | except AssertionError as e: 116 | raise e 117 | except Exception as e: 118 | self.fail('Unexpected exception raised: {0}'.format(e)) 119 | 120 | data['nets']['net']['orgRef']['@handle'] = 'INVALID' 121 | try: 122 | self.assertRaises(ASNRegistryError, ipasn.parse_fields_http, 123 | response=data) 124 | except AssertionError as e: 125 | raise e 126 | except Exception as e: 127 | self.fail('Unexpected exception raised: {0}'.format(e)) 128 | 129 | data = '' 130 | try: 131 | self.assertRaises(ASNRegistryError, ipasn.parse_fields_http, 132 | response=data) 133 | except AssertionError as e: 134 | raise e 135 | except Exception as e: 136 | self.fail('Unexpected exception raised: {0}'.format(e)) 137 | 138 | def test_lookup(self): 139 | data_dir = path.dirname(__file__) 140 | 141 | with io.open(str(data_dir) + '/asn.json', 'r') as \ 142 | data_file: 143 | data = json.load(data_file) 144 | 145 | for key, val in data.items(): 146 | 147 | log.debug('Testing: {0}'.format(key)) 148 | net = Net(key) 149 | obj = IPASN(net) 150 | 151 | try: 152 | 153 | self.assertIsInstance(obj.lookup(), dict) 154 | 155 | except AssertionError as e: 156 | 157 | raise e 158 | 159 | except Exception as e: 160 | 161 | self.fail('Unexpected exception raised: {0}'.format(e)) 162 | 163 | 164 | class TestASNOrigin(TestCommon): 165 | 166 | def test__ASNOrigin(self): 167 | 168 | self.assertRaises(NetError, ASNOrigin, 'a') 169 | 170 | def test__ASNOriginLookup(self): 171 | 172 | data_dir = path.dirname(__file__) 173 | 174 | with io.open(str(data_dir) + '/asn.json', 'r') as \ 175 | data_file: 176 | data = json.load(data_file) 177 | 178 | for key, val in data.items(): 179 | 180 | log.debug('Testing: {0}'.format(key)) 181 | net = Net(key) 182 | obj = ASNOrigin(net) 183 | 184 | try: 185 | 186 | self.assertIsInstance(obj.lookup(asn=val['asn'], 187 | inc_raw=True, 188 | response=val['response']), 189 | dict) 190 | 191 | except AssertionError as e: 192 | 193 | raise e 194 | 195 | except Exception as e: 196 | 197 | self.fail('Unexpected exception raised: {0}'.format(e)) 198 | 199 | def test_parse_fields(self): 200 | 201 | net = Net('74.125.225.229') 202 | obj = ASNOrigin(net) 203 | 204 | # No exception raised, but should provide code coverage for if regex 205 | # groups are messed up. 206 | tmp_dict = ASN_ORIGIN_WHOIS['radb']['fields'] 207 | tmp_dict['route'] = r'(route):[^\S\n]+(?P.+?)\n' 208 | obj.parse_fields( 209 | response="\nroute: 66.249.64.0/20\n", 210 | fields_dict=tmp_dict 211 | ) 212 | 213 | obj.parse_fields( 214 | response="\nchanged: noc@google.com 20110301\n", 215 | fields_dict=ASN_ORIGIN_WHOIS['radb']['fields'] 216 | ) 217 | 218 | multi_net_response = ( 219 | '\n\nroute: 66.249.64.0/20' 220 | '\ndescr: Google' 221 | '\norigin: AS15169' 222 | '\nnotify: noc@google.com' 223 | '\nmnt-by: MAINT-AS15169' 224 | '\nchanged: noc@google.com 20110301' 225 | '\nsource: RADB' 226 | '\n\nroute: 66.249.80.0/20' 227 | '\ndescr: Google' 228 | '\norigin: AS15169' 229 | '\nnotify: noc@google.com' 230 | '\nmnt-by: MAINT-AS15169' 231 | '\nchanged: noc@google.com 20110301' 232 | '\nsource: RADB' 233 | '\n\n' 234 | ) 235 | obj.parse_fields( 236 | response=multi_net_response, 237 | fields_dict=ASN_ORIGIN_WHOIS['radb']['fields'] 238 | ) 239 | 240 | def test__get_nets_radb(self): 241 | 242 | net = Net('74.125.225.229') 243 | obj = ASNOrigin(net) 244 | 245 | # No exception raised, but should provide code coverage for multiple 246 | # network scenarios and CIDR invalid IP ValueError. 247 | multi_net_response = ( 248 | '\n\nroute: 66.249.64.0/20' 249 | '\ndescr: Google' 250 | '\norigin: AS15169' 251 | '\nnotify: noc@google.com' 252 | '\nmnt-by: MAINT-AS15169' 253 | '\nchanged: noc@google.com 20110301' 254 | '\nsource: RADB' 255 | '\n\nroute: 66.249.80.0/20' 256 | '\ndescr: Google' 257 | '\norigin: AS15169' 258 | '\nnotify: noc@google.com' 259 | '\nmnt-by: MAINT-AS15169' 260 | '\nchanged: noc@google.com 20110301' 261 | '\nsource: RADB' 262 | '\n\n' 263 | ) 264 | obj.get_nets_radb(multi_net_response) 265 | 266 | self.assertEqual(obj.get_nets_radb(multi_net_response, is_http=True), 267 | [{'cidr': '66.249.64.0/20', 'description': None, 'maintainer': None, 'updated': None, 268 | 'source': None, 'start': 2, 'end': 29}, 269 | {'cidr': '66.249.80.0/20', 'description': None, 'maintainer': None, 'updated': None, 270 | 'source': None, 'start': 175, 'end': 202}]) 271 | 272 | net = Net('2001:43f8:7b0::') 273 | obj = ASNOrigin(net) 274 | 275 | multi_net_response = ( 276 | '\n\nroute6: 2001:43f8:7b0::/48' 277 | '\ndescr: KIXP Nairobi Management Network' 278 | '\norigin: AS37578' 279 | '\norg: ORG-TA38-AFRINIC' 280 | '\nmnt-by: TESPOK-MNT' 281 | '\nchanged: ***@isoc.org 20160721' 282 | '\nsource: AFRINIC' 283 | '\n\n' 284 | ) 285 | self.assertEqual( 286 | obj.get_nets_radb(multi_net_response), 287 | [{ 288 | 'updated': None, 289 | 'maintainer': None, 290 | 'description': None, 291 | 'start': 2, 292 | 'source': None, 293 | 'end': 36, 294 | 'cidr': '2001:43f8:7b0::/48' 295 | }] 296 | ) 297 | -------------------------------------------------------------------------------- /ipwhois/tests/test_experimental.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from ipwhois.tests import TestCommon 3 | from ipwhois.experimental import (get_bulk_asn_whois, bulk_lookup_rdap) 4 | 5 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 6 | '[%(funcName)s()] %(message)s') 7 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | class TestExperimental(TestCommon): 12 | 13 | def test_get_bulk_asn_whois(self): 14 | 15 | self.assertRaises(ValueError, get_bulk_asn_whois, **dict( 16 | addresses='1.2.3.4' 17 | )) 18 | 19 | def test_get_bulk_lookup_rdap(self): 20 | 21 | self.assertRaises(ValueError, bulk_lookup_rdap, **dict( 22 | addresses='1.2.3.4' 23 | )) 24 | -------------------------------------------------------------------------------- /ipwhois/tests/test_ipwhois.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from ipwhois.tests import TestCommon 3 | from ipwhois.ipwhois import IPWhois 4 | 5 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 6 | '[%(funcName)s()] %(message)s') 7 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | class TestIPWhois(TestCommon): 12 | 13 | def test_repr(self): 14 | 15 | # basic str test 16 | log.debug('Basic str test: {0}'.format('74.125.225.229')) 17 | obj = IPWhois('74.125.225.229') 18 | self.assertIsInstance(repr(obj), str) 19 | 20 | # add more specific tests 21 | -------------------------------------------------------------------------------- /ipwhois/tests/test_net.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | from ipwhois.tests import TestCommon 4 | from ipwhois.exceptions import (IPDefinedError, ASNLookupError, 5 | ASNRegistryError, WhoisLookupError, 6 | HTTPLookupError, HostLookupError) 7 | from ipwhois.net import Net 8 | 9 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 10 | '[%(funcName)s()] %(message)s') 11 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 12 | 13 | 14 | class TestNet(TestCommon): 15 | 16 | def test_ip_invalid(self): 17 | self.assertRaises(ValueError, Net, '192.168.0.256') 18 | self.assertRaises(ValueError, Net, 'fe::80::') 19 | 20 | def test_ip_defined(self): 21 | if sys.version_info >= (3, 3): 22 | from ipaddress import (IPv4Address, IPv6Address) 23 | else: 24 | from ipaddr import (IPv4Address, IPv6Address) 25 | 26 | self.assertRaises(IPDefinedError, Net, '192.168.0.1') 27 | self.assertRaises(IPDefinedError, Net, 'fe80::') 28 | self.assertRaises(IPDefinedError, Net, IPv4Address('192.168.0.1')) 29 | self.assertRaises(IPDefinedError, Net, IPv6Address('fe80::')) 30 | 31 | def test_ip_version(self): 32 | result = Net('74.125.225.229') 33 | self.assertEqual(result.version, 4) 34 | result = Net('2001:4860:4860::8888') 35 | self.assertEqual(result.version, 6) 36 | 37 | def test_timeout(self): 38 | result = Net('74.125.225.229') 39 | self.assertIsInstance(result.timeout, int) 40 | 41 | def test_proxy_opener(self): 42 | try: 43 | from urllib.request import (OpenerDirector, 44 | ProxyHandler, 45 | build_opener) 46 | except ImportError: 47 | from urllib2 import (OpenerDirector, 48 | ProxyHandler, 49 | build_opener) 50 | 51 | result = Net('74.125.225.229') 52 | self.assertIsInstance(result.opener, OpenerDirector) 53 | 54 | handler = ProxyHandler() 55 | opener = build_opener(handler) 56 | result = Net(address='74.125.225.229', proxy_opener=opener) 57 | self.assertIsInstance(result.opener, OpenerDirector) 58 | -------------------------------------------------------------------------------- /ipwhois/tests/test_nir.py: -------------------------------------------------------------------------------- 1 | import json 2 | import io 3 | from os import path 4 | import logging 5 | from ipwhois.exceptions import NetError 6 | from ipwhois.tests import TestCommon 7 | from ipwhois.net import Net 8 | from ipwhois.nir import (NIR_WHOIS, NIRWhois) 9 | 10 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 11 | '[%(funcName)s()] %(message)s') 12 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 13 | log = logging.getLogger(__name__) 14 | 15 | 16 | class TestNIRWhois(TestCommon): 17 | 18 | def test_lookup(self): 19 | 20 | data_dir = path.dirname(__file__) 21 | 22 | with io.open(str(data_dir) + '/jpnic.json', 'r') as data_jpnic: 23 | data = json.load(data_jpnic) 24 | 25 | with io.open(str(data_dir) + '/krnic.json', 'r') as data_krnic: 26 | data.update(json.load(data_krnic)) 27 | 28 | for key, val in data.items(): 29 | 30 | log.debug('Testing: {0}'.format(key)) 31 | net = Net(key) 32 | obj = NIRWhois(net) 33 | 34 | self.assertRaises(KeyError, obj.lookup, 35 | **dict(nir=val['nir'], response=None, 36 | is_offline=True) 37 | ) 38 | 39 | try: 40 | 41 | self.assertIsInstance(obj.lookup( 42 | nir=val['nir'], 43 | response=val['response'], 44 | is_offline=True, 45 | inc_raw=True), 46 | dict) 47 | 48 | self.assertIsInstance(obj.lookup( 49 | nir=val['nir'], 50 | response=val['response']), 51 | dict) 52 | 53 | except AssertionError as e: 54 | 55 | raise e 56 | 57 | except Exception as e: 58 | 59 | self.fail('Unexpected exception raised: {0}'.format(e)) 60 | 61 | self.assertRaises(NetError, NIRWhois, 'a') 62 | self.assertRaises(KeyError, obj.lookup) 63 | self.assertRaises(KeyError, obj.lookup, **dict(nir='a')) 64 | 65 | def test_parse_fields(self): 66 | 67 | net = Net('133.1.2.5') 68 | obj = NIRWhois(net) 69 | 70 | # No exception raised, but should provide code coverage for if regex 71 | # groups are messed up. 72 | tmp_dict = NIR_WHOIS['jpnic']['fields'] 73 | tmp_dict['name'] = r'(NetName):[^\S\n]+(?P.+?)\n' 74 | obj.parse_fields( 75 | response='\nNetName: TEST\n', 76 | fields_dict=tmp_dict, 77 | dt_format=NIR_WHOIS['jpnic']['dt_format'] 78 | ) 79 | 80 | obj.parse_fields( 81 | response='\nUpdated: 2012-02-24\n', 82 | fields_dict=NIR_WHOIS['jpnic']['fields'], 83 | dt_format=NIR_WHOIS['jpnic']['dt_format'] 84 | ) 85 | 86 | log.debug( 87 | 'Testing field parse error. This should be followed by a ' 88 | 'debug log.') 89 | obj.parse_fields( 90 | response='\nUpdated: 2012-02-244\n', 91 | fields_dict=NIR_WHOIS['jpnic']['fields'], 92 | dt_format=NIR_WHOIS['jpnic']['dt_format'] 93 | ) 94 | 95 | def test_get_nets_jpnic(self): 96 | 97 | net = Net('133.1.2.5') 98 | obj = NIRWhois(net) 99 | 100 | # No exception raised, but should provide code coverage for multiple 101 | # network scenarios and CIDR invalid IP ValueError. 102 | multi_net_response = ( 103 | 'a. [Network Number] asd>133.1.0.0/16' 104 | 'a. [Network Number] asd>133.1.0.0/24' 105 | ) 106 | obj.get_nets_jpnic(multi_net_response) 107 | 108 | self.assertFalse(obj.get_nets_jpnic( 109 | 'a. [Network Number] asd>asd/16' 110 | )) 111 | 112 | def test__get_nets_krnic(self): 113 | 114 | net = Net('115.1.2.3') 115 | obj = NIRWhois(net) 116 | 117 | # No exception raised, but should provide code coverage for multiple 118 | # network scenarios and CIDR invalid IP ValueError. 119 | multi_net_response = ( 120 | 'IPv4 Address : 115.0.0.0 - 115.23.255.255 (/12+/13)' 121 | 'IPv4 Address : 115.1.2.0 - 115.1.2.63 (/26)' 122 | ) 123 | obj.get_nets_krnic(multi_net_response) 124 | 125 | # ip_network ValueError 126 | self.assertFalse(obj.get_nets_krnic( 127 | 'IPv4 Address : asd - asd (/12+/13)' 128 | )) 129 | 130 | # Expected IP range regex not found, but some value found 131 | self.assertFalse(obj.get_nets_krnic( 132 | 'IPv4 Address : asd' 133 | )) 134 | 135 | def test_get_contact(self): 136 | 137 | net = Net('115.1.2.3') 138 | obj = NIRWhois(net) 139 | 140 | contact_response = ( 141 | 'Name : IP Manager' 142 | 'Phone : +82-2-500-6630' 143 | 'E-Mail : kornet_ip@kt.com' 144 | ) 145 | 146 | # No exception raised. 147 | obj.get_contact( 148 | response=contact_response, 149 | handle=None, 150 | nir='krnic', 151 | dt_format=NIR_WHOIS['krnic']['dt_format'] 152 | ) 153 | -------------------------------------------------------------------------------- /ipwhois/tests/test_rdap.py: -------------------------------------------------------------------------------- 1 | import json 2 | import io 3 | from os import path 4 | import logging 5 | from ipwhois.tests import TestCommon 6 | from ipwhois.rdap import (RDAP, _RDAPEntity, _RDAPContact, _RDAPNetwork, Net, 7 | InvalidEntityObject, InvalidEntityContactObject, 8 | InvalidNetworkObject, NetError) 9 | 10 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 11 | '[%(funcName)s()] %(message)s') 12 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 13 | log = logging.getLogger(__name__) 14 | 15 | 16 | class TestRDAP(TestCommon): 17 | 18 | def test_RDAP(self): 19 | 20 | self.assertRaises(NetError, RDAP, 'a') 21 | 22 | def test_lookup(self): 23 | 24 | data_dir = path.dirname(__file__) 25 | 26 | with io.open(str(data_dir) + '/rdap.json', 'r') as data_file: 27 | data = json.load(data_file) 28 | 29 | for key, val in data.items(): 30 | 31 | log.debug('Testing: {0}'.format(key)) 32 | net = Net(key) 33 | obj = RDAP(net) 34 | 35 | try: 36 | 37 | self.assertIsInstance(obj.lookup(response=val['response'], 38 | asn_data=val['asn_data'], 39 | depth=0), dict) 40 | 41 | except AssertionError as e: 42 | 43 | raise e 44 | 45 | except Exception as e: 46 | 47 | self.fail('Unexpected exception raised: {0}'.format(e)) 48 | 49 | self.assertRaises(NetError, RDAP, 'a') 50 | 51 | for key, val in data.items(): 52 | 53 | log.debug('Testing bootstrap and raw: {0}'.format(key)) 54 | net = Net(key) 55 | obj = RDAP(net) 56 | 57 | try: 58 | 59 | self.assertIsInstance(obj.lookup(response=val['response'], 60 | asn_data=val['asn_data'], 61 | depth=0, 62 | bootstrap=True, 63 | inc_raw=True), dict) 64 | 65 | except AssertionError as e: 66 | 67 | raise e 68 | 69 | except Exception as e: 70 | 71 | self.fail('Unexpected exception raised: {0}'.format(e)) 72 | 73 | break 74 | 75 | log.debug('Testing rdap.lookup response checks') 76 | net = Net('74.125.225.229') 77 | obj = RDAP(net) 78 | self.assertIsInstance(obj.lookup(response={ 79 | 'handle': 'test', 80 | 'ipVersion': 'v4', 81 | 'startAddress': '74.125.225.229', 82 | 'endAddress': '74.125.225.229' 83 | }, 84 | asn_data=val['asn_data'], 85 | depth=0, 86 | root_ent_check=False), dict) 87 | 88 | log.debug('Testing rdap.lookup entitiy checks') 89 | net = Net('74.125.225.229') 90 | obj = RDAP(net) 91 | entity = [{'handle': 'test', 'roles': [ 92 | 'administrative', 'technical'], 'entities': [ 93 | {'handle': 'GOGL', 'roles': ['administrative', 'technical']}]}] 94 | 95 | self.assertIsInstance(obj.lookup(response={ 96 | 'handle': 'test', 97 | 'ipVersion': 'v4', 98 | 'startAddress': '74.125.225.229', 99 | 'endAddress': '74.125.225.229', 100 | 'entities': entity 101 | }, 102 | asn_data=val['asn_data'], 103 | depth=0, 104 | root_ent_check=False), dict) 105 | 106 | self.assertIsInstance(obj.lookup(response={ 107 | 'handle': 'test', 108 | 'ipVersion': 'v4', 109 | 'startAddress': '74.125.225.229', 110 | 'endAddress': '74.125.225.229', 111 | 'entities': entity 112 | }, 113 | asn_data=val['asn_data'], 114 | depth=0, 115 | bootstrap=True, 116 | inc_raw=True, 117 | root_ent_check=False), dict) 118 | 119 | # No sub entities. This is for coverage, but won't error out. 120 | entity = [{'handle': 'test', 'roles': [ 121 | 'administrative', 'technical']}] 122 | 123 | self.assertIsInstance(obj.lookup(response={ 124 | 'handle': 'test', 125 | 'ipVersion': 'v4', 126 | 'startAddress': '74.125.225.229', 127 | 'endAddress': '74.125.225.229', 128 | 'entities': entity 129 | }, 130 | asn_data=val['asn_data'], 131 | depth=0, 132 | root_ent_check=False), dict) 133 | 134 | 135 | class TestRDAPContact(TestCommon): 136 | 137 | def test__RDAPContact(self): 138 | 139 | self.assertRaises(InvalidEntityContactObject, _RDAPContact, 'a') 140 | 141 | data_dir = path.dirname(__file__) 142 | 143 | with io.open(str(data_dir) + '/entity.json', 'r') as data_file: 144 | data = json.load(data_file) 145 | 146 | contact = _RDAPContact(data['vcardArray'][1]) 147 | contact.parse() 148 | 149 | self.assertRaises(IndexError, contact._parse_phone, []) 150 | self.assertRaises(IndexError, contact._parse_role, []) 151 | self.assertRaises(IndexError, contact._parse_title, []) 152 | 153 | 154 | class TestRDAPNetwork(TestCommon): 155 | 156 | def test__RDAPNetwork(self): 157 | 158 | self.assertRaises(InvalidNetworkObject, _RDAPNetwork, 'a') 159 | 160 | data_dir = path.dirname(__file__) 161 | 162 | with io.open(str(data_dir) + '/rdap.json', 'r') as data_file: 163 | data = json.load(data_file) 164 | 165 | for key, val in data.items(): 166 | network = _RDAPNetwork(val['response']) 167 | network.parse() 168 | 169 | tmp = val['response'] 170 | del tmp['startAddress'] 171 | network = _RDAPNetwork(tmp) 172 | self.assertRaises(InvalidNetworkObject, network.parse) 173 | 174 | network = _RDAPNetwork({}) 175 | self.assertRaises(InvalidNetworkObject, network.parse) 176 | 177 | break 178 | 179 | 180 | class TestRDAPEntity(TestCommon): 181 | 182 | def test__RDAPEntity(self): 183 | 184 | self.assertRaises(InvalidEntityObject, _RDAPEntity, 'abc') 185 | 186 | ent = _RDAPEntity({'abc': 'def'}) 187 | self.assertRaises(InvalidEntityObject, ent.parse) 188 | 189 | data_dir = path.dirname(__file__) 190 | 191 | with io.open(str(data_dir) + '/entity.json', 'r') as data_file: 192 | data = json.load(data_file) 193 | 194 | ent = _RDAPEntity(data) 195 | ent.parse() 196 | 197 | tmp = data 198 | del tmp['vcardArray'] 199 | ent = _RDAPEntity(tmp) 200 | ent.parse() 201 | 202 | tmp = data 203 | del tmp['notices'][0]['description'] 204 | ent = _RDAPEntity(tmp) 205 | ent.parse() 206 | -------------------------------------------------------------------------------- /ipwhois/tests/test_whois.py: -------------------------------------------------------------------------------- 1 | import json 2 | import io 3 | from os import path 4 | import logging 5 | from ipwhois.tests import TestCommon 6 | from ipwhois.net import Net 7 | from ipwhois.whois import (Whois, RIR_WHOIS, NetError) 8 | 9 | LOG_FORMAT = ('[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)s] ' 10 | '[%(funcName)s()] %(message)s') 11 | logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) 12 | log = logging.getLogger(__name__) 13 | 14 | 15 | class TestWhois(TestCommon): 16 | 17 | def test_Whois(self): 18 | 19 | self.assertRaises(NetError, Whois, 'a') 20 | 21 | def test_lookup(self): 22 | 23 | data_dir = path.dirname(__file__) 24 | 25 | with io.open(str(data_dir) + '/whois.json', 'r') as data_file: 26 | data = json.load(data_file) 27 | 28 | for key, val in data.items(): 29 | 30 | log.debug('Testing: {0}'.format(key)) 31 | net = Net(key) 32 | obj = Whois(net) 33 | 34 | try: 35 | 36 | self.assertIsInstance(obj.lookup(response=val['response'], 37 | asn_data=val['asn_data'], 38 | is_offline=True, 39 | inc_raw=True), 40 | dict) 41 | 42 | except AssertionError as e: 43 | 44 | raise e 45 | 46 | except Exception as e: 47 | 48 | self.fail('Unexpected exception raised: {0}'.format(e)) 49 | 50 | def test__parse_fields(self): 51 | 52 | net = Net('74.125.225.229') 53 | obj = Whois(net) 54 | 55 | # No exception raised, but should provide code coverage for if regex 56 | # groups are messed up. 57 | tmp_dict = RIR_WHOIS['arin']['fields'] 58 | tmp_dict['name'] = r'(NetName):[^\S\n]+(?P.+?)\n' 59 | obj.parse_fields( 60 | response="\nNetName: TEST\n", 61 | fields_dict=tmp_dict, 62 | dt_format=RIR_WHOIS['arin']['dt_format'] 63 | ) 64 | 65 | obj.parse_fields( 66 | response="\nUpdated: 2012-02-24\n", 67 | fields_dict=RIR_WHOIS['arin']['fields'], 68 | dt_format=RIR_WHOIS['arin']['dt_format'] 69 | ) 70 | 71 | log.debug('Testing field parse error. This should be followed by a ' 72 | 'debug log.') 73 | obj.parse_fields( 74 | response='\nUpdated: 2012-02-244\n', 75 | fields_dict=RIR_WHOIS['arin']['fields'], 76 | dt_format=RIR_WHOIS['arin']['dt_format'] 77 | ) 78 | 79 | def test_get_nets_arin(self): 80 | 81 | net = Net('74.125.225.229') 82 | obj = Whois(net) 83 | 84 | # No exception raised, but should provide code coverage for multiple 85 | # network scenarios and CIDR invalid IP ValueError. 86 | multi_net_response = ( 87 | '\n#\n\nNetRange: 74.125.0.0 - 74.125.255.255' 88 | '\nCIDR: 74.125.0.0/16\nNetName: TEST' 89 | '\nCIDR: 74.125.1.256/24\nNetName: TEST2' 90 | '\nNetRange: 74.125.1.0 - 74.125.1.0' 91 | '\n' 92 | ) 93 | obj.get_nets_arin(multi_net_response) 94 | 95 | def test_get_nets_lacnic(self): 96 | 97 | net = Net('200.57.141.161') 98 | obj = Whois(net) 99 | 100 | # No exception raised, but should provide code coverage for inetnum 101 | # invalid IP ValueError. 102 | multi_net_response = ( 103 | '\ninetnum: 200.57.256/19\r\n' 104 | '\n' 105 | ) 106 | obj.get_nets_lacnic(multi_net_response) 107 | 108 | def test_get_nets_other(self): 109 | 110 | net = Net('210.107.73.73') 111 | obj = Whois(net) 112 | 113 | # No exception raised, but should provide code coverage for inetnum 114 | # invalid IP ValueError. 115 | multi_net_response = ( 116 | '\ninetnum: 210.107.0.0 - 210.107.127.256\n' 117 | '\n' 118 | ) 119 | obj.get_nets_other(multi_net_response) 120 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /requirements/python2.txt: -------------------------------------------------------------------------------- 1 | dnspython 2 | ipaddr 3 | defusedxml 4 | -------------------------------------------------------------------------------- /requirements/python3.txt: -------------------------------------------------------------------------------- 1 | dnspython 2 | defusedxml 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = ipwhois 3 | version = 1.3.0 4 | author = Philip Hane 5 | author_email = secynic@gmail.com 6 | license = BSD 7 | description = Retrieve and parse whois data for IPv4 and IPv6 addresses. 8 | long_description = file: README.rst, CHANGES.rst 9 | long_description_content_type = text/x-rst 10 | keywords = 11 | Python, WHOIS, RWhois, Referral Whois, ASN, IP Address, IP, IPv4, IPv6, 12 | IETF, REST, Arin, Ripe, Apnic, Lacnic, Afrinic, NIC, National Information 13 | Center, RDAP, RIR, Regional Internet Registry, NIR, National Internet 14 | Registry, ASN origin, Origin 15 | url = https://github.com/secynic/ipwhois 16 | download_url = https://github.com/secynic/ipwhois/tarball/master 17 | classifiers = 18 | Development Status :: 5 - Production/Stable 19 | Intended Audience :: Developers 20 | Intended Audience :: Information Technology 21 | Intended Audience :: Science/Research 22 | License :: OSI Approved :: BSD License 23 | Operating System :: OS Independent 24 | Programming Language :: Python 25 | Programming Language :: Python :: 2 26 | Programming Language :: Python :: 2.7 27 | Programming Language :: Python :: 3 28 | Programming Language :: Python :: 3.4 29 | Programming Language :: Python :: 3.5 30 | Programming Language :: Python :: 3.6 31 | Programming Language :: Python :: 3.7 32 | Programming Language :: Python :: 3.8 33 | Programming Language :: Python :: 3.9 34 | Programming Language :: Python :: 3.10 35 | Programming Language :: Python :: 3.11 36 | Programming Language :: Python :: 3.12 37 | Topic :: Internet 38 | Topic :: Software Development 39 | 40 | [options] 41 | packages = ipwhois, ipwhois.scripts 42 | install_requires = 43 | dnspython 44 | ipaddr==2.2.0;python_version<"3.3" 45 | defusedxml 46 | 47 | [options.entry_points] 48 | console_scripts = 49 | ipwhois_cli = ipwhois.scripts.ipwhois_cli:main 50 | ipwhois_utils_cli = ipwhois.scripts.ipwhois_utils_cli:main 51 | 52 | [options.package_data] 53 | ipwhois = data/*.xml; data/*.csv 54 | 55 | [bdist_wheel] 56 | universal=1 --------------------------------------------------------------------------------