├── docs ├── api.rst ├── download.rst ├── index.rst ├── command_line.rst ├── cymruwhois.1 ├── Makefile ├── make.bat └── conf.py ├── tests ├── test_doctest.py ├── test_common_lookups.py └── test_mocked_socket.py ├── .gitignore ├── README.md ├── LICENSE.txt ├── setup.py └── cymruwhois.py /docs/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | --- 3 | 4 | .. autoclass:: cymruwhois.Client 5 | :members: lookup,lookupmany,lookupmany_dict 6 | :undoc-members: 7 | 8 | -------------------------------------------------------------------------------- /tests/test_doctest.py: -------------------------------------------------------------------------------- 1 | import doctest, cymruwhois 2 | 3 | def test_doctest(): 4 | fail, ok = doctest.testmod(cymruwhois) 5 | assert fail == 0 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | develop-eggs 12 | .installed.cfg 13 | 14 | # Installer logs 15 | pip-log.txt 16 | 17 | # Unit test / coverage reports 18 | .coverage 19 | .tox 20 | -------------------------------------------------------------------------------- /docs/download.rst: -------------------------------------------------------------------------------- 1 | Downloading 2 | =========== 3 | 4 | Releases 5 | -------- 6 | released versions are available for download at the `Python Package Index `_. 7 | 8 | Source 9 | ------ 10 | The latest source code is avialable at the `github project page `_. 11 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. cymruwhois documentation master file, created by 2 | sphinx-quickstart on Sun Mar 22 00:26:37 2009. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to cymruwhois's documentation! 7 | ====================================== 8 | cymruwhois is a python library for interfacing with the whois.cymru.com service. 9 | 10 | Contents: 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | api 16 | command_line 17 | download 18 | 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | -------------------------------------------------------------------------------- /tests/test_common_lookups.py: -------------------------------------------------------------------------------- 1 | import cymruwhois 2 | import socket 3 | 4 | def test_common(): 5 | l=cymruwhois.Client() 6 | places = [ 7 | ['www.google.com', 'google'], 8 | ['www.yahoo.com', 'yahoo'], 9 | ['www.albany.edu', 'albany'], 10 | ] 11 | 12 | for hostname, owner in places: 13 | yield common_case, l, hostname, owner 14 | 15 | def test_asn(): 16 | l=cymruwhois.Client() 17 | record = l.lookup("AS15169") 18 | assert 'google' in record.owner.lower() 19 | 20 | def common_case(client, hostname, owner): 21 | ip = socket.gethostbyname(hostname) 22 | r=client.lookup(ip) 23 | print(owner, r.owner.lower()) 24 | assert owner in r.owner.lower() 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This client still works, but relying on a 3rd party service has downsides for reliability and performance reasons. I've started a new project called [asnlookup](https://github.com/justinazoff/asnlookup) to allow one to perform lookups locally or to operate a similar service on their own infrastructure. If autonomy and 160,000 queries/second interests you, check it out. 2 | 3 | 4 | 5 | 6 | ---------- 7 | 8 | Perform lookups by ip address and return ASN, Country Code, and Netblock Owner:: 9 | 10 | >>> import socket 11 | >>> ip = socket.gethostbyname("www.google.com") 12 | >>> from cymruwhois import Client 13 | >>> c=Client() 14 | >>> r=c.lookup(ip) 15 | >>> print r.asn 16 | 15169 17 | >>> print r.owner 18 | GOOGLE - Google Inc. 19 | 20 | 21 | See http://packages.python.org/cymruwhois/ for full documentation. 22 | -------------------------------------------------------------------------------- /docs/command_line.rst: -------------------------------------------------------------------------------- 1 | Command-line Interface 2 | ====================== 3 | The cymruwhois utility program lets you do lookups from the command line or 4 | shell scripts. 5 | 6 | Lookups can be done from stdin or from files:: 7 | 8 | justin@dell ~ % cat /tmp/ips | cymruwhois 9 | 15169 66.102.1.104 66.102.0.0/23 US GOOGLE - Google Inc. 10 | 22990 169.226.1.110 169.226.0.0/16 US ALBANYEDU - The University at Albany 11 | 12306 82.98.86.176 82.98.64.0/18 DE PLUSLINE Plus.Line AG IP-Services 12 | 13 | justin@dell ~ % cymruwhois /tmp/ips 14 | 15169 66.102.1.104 66.102.0.0/23 US GOOGLE - Google Inc. 15 | 22990 169.226.1.110 169.226.0.0/16 US ALBANYEDU - The University at Albany 16 | 12306 82.98.86.176 82.98.64.0/18 DE PLUSLINE Plus.Line AG IP-Services 17 | 18 | 19 | The formatting and contents of the output can be controlled with the -f and -d options:: 20 | 21 | justin@dell ~ % cymruwhois /tmp/ips -f asn,cc 22 | 15169 US 23 | 22990 US 24 | 12306 DE 25 | 26 | justin@dell ~ % cymruwhois /tmp/ips -f asn,cc -d, 27 | 15169,US 28 | 22990,US 29 | 12306,DE 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2009-2016 Justin Azoff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from glob import glob 3 | 4 | setup(name="cymruwhois", 5 | version="1.6", 6 | description="Client for the whois.cymru.com service", 7 | long_description=""" 8 | Perform lookups by ip address and return ASN, Country Code, and Netblock Owner:: 9 | 10 | >>> import socket 11 | >>> ip = socket.gethostbyname("www.google.com") 12 | >>> from cymruwhois import Client 13 | >>> c=Client() 14 | >>> r=c.lookup(ip) 15 | >>> print r.asn 16 | 15169 17 | >>> print r.owner 18 | GOOGLE - Google Inc. 19 | 20 | """, 21 | 22 | url="http://packages.python.org/cymruwhois/", 23 | download_url="http://github.com/JustinAzoff/python-cymruwhois/tree/master", 24 | license='MIT', 25 | classifiers=[ 26 | "Topic :: System :: Networking", 27 | "Environment :: Console", 28 | "Intended Audience :: Developers", 29 | "License :: OSI Approved :: MIT License", 30 | "Programming Language :: Python", 31 | "Development Status :: 5 - Production/Stable", 32 | ], 33 | keywords='ASN', 34 | author="Justin Azoff", 35 | author_email="justin@bouncybouncy.net", 36 | py_modules = ["cymruwhois"], 37 | extras_require = { 38 | 'CACHE': ["python-memcached"], 39 | 'docs' : ['sphinx'], 40 | 'tests' : ['nose'], 41 | }, 42 | entry_points = { 43 | 'console_scripts': [ 44 | 'cymruwhois = cymruwhois:lookup_stdin', 45 | ] 46 | }, 47 | setup_requires=[ 48 | ], 49 | test_suite='nose.collector', 50 | ) 51 | -------------------------------------------------------------------------------- /docs/cymruwhois.1: -------------------------------------------------------------------------------- 1 | .\" Hey, EMACS: -*- nroff -*- 2 | .\" First parameter, NAME, should be all caps 3 | .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection 4 | .\" other parameters are allowed: see man(7), man(1) 5 | .TH CYMRUWHOIS 1 "February 26, 2009" 6 | .\" Please adjust this date whenever revising the manpage. 7 | .\" 8 | .\" Some roff macros, for reference: 9 | .\" .nh disable hyphenation 10 | .\" .hy enable hyphenation 11 | .\" .ad l left justify 12 | .\" .ad b justify to both left and right margins 13 | .\" .nf disable filling 14 | .\" .fi enable filling 15 | .\" .br insert line break 16 | .\" .sp insert n+1 empty lines 17 | .\" for manpage-specific macros, see man(7) 18 | .SH NAME 19 | cymruwhois \- program to lookup ip addresses using the cymru whois service 20 | .SH SYNOPSIS 21 | .B cymruwhois 22 | .RI [ options ] " files" ... 23 | .br 24 | .SH DESCRIPTION 25 | This manual page documents briefly the 26 | .B cymruwhois 27 | command. 28 | .PP 29 | .\" TeX users may be more comfortable with the \fB\fP and 30 | .\" \fI\fP escape sequences to invode bold face and italics, 31 | .\" respectively. 32 | \fBcymruwhois\fP is a program that... 33 | .SH OPTIONS 34 | This program follow the usual GNU command line syntax, with long 35 | options starting with two dashes (`-'). 36 | .TP 37 | .B \-h, \-\-help 38 | Show summary of options. 39 | .TP 40 | .B \-d DELIM, \-\-delim=DELIM 41 | delimiter to use instead of justified 42 | .TP 43 | .B \-f FIELDS, \-\-fields=FIELDS 44 | comma separated fields to include (asn,ip,prefix,cc,owner) 45 | .TP 46 | .B \-c CACHE, \-\-cache=CACHE 47 | memcache server (default localhost) 48 | .TP 49 | .B \-n, \-\-no-cache 50 | don't use memcached 51 | .SH SEE ALSO 52 | .BR whois (1), 53 | .br 54 | .SH AUTHOR 55 | cymruwhois was written by Justin Azoff 56 | .PP 57 | This manual page was written by Justin Azoff 58 | for the Debian project (but may be used by others). 59 | -------------------------------------------------------------------------------- /tests/test_mocked_socket.py: -------------------------------------------------------------------------------- 1 | import cymruwhois 2 | import socket 3 | import errno 4 | 5 | class FakeFile: 6 | def __init__(self, lines): 7 | self.lines = lines 8 | self.iter = iter(lines) 9 | self.written = [] 10 | def write(self, data): 11 | self.written.append(data) 12 | return 13 | def flush(self): 14 | return 15 | def readline(self): 16 | try : 17 | try: 18 | return self.iter.next() 19 | except AttributeError: 20 | return self.iter.__next__() # for Python 3 21 | except StopIteration: 22 | raise socket.error(errno.EAGAIN, 'bleh') 23 | def read(self, bytes): 24 | try : 25 | try: 26 | return self.iter.next() 27 | except AttributeError: 28 | return self.iter.__next__() # for Python 3 29 | except StopIteration: 30 | raise socket.error(errno.EAGAIN, 'bleh') 31 | 32 | class FakeSocket: 33 | def __init__(self): 34 | pass 35 | def setblocking(self,x): 36 | pass 37 | 38 | def test_normal(): 39 | l=cymruwhois.Client(memcache_host=None) 40 | l.socket = FakeSocket() 41 | l.file = FakeFile([ 42 | '22990 | 169.226.11.11 | 169.226.0.0/16 | US | ALBANYEDU - The University at Albany' 43 | ]) 44 | l._connected = True 45 | 46 | rec = l.lookup("169.226.11.11") 47 | assert rec.asn == '22990' 48 | assert rec.cc == 'US' 49 | assert rec.owner == 'ALBANYEDU - The University at Albany' 50 | 51 | 52 | def test_multiple_returned_for_a_single_ip(): 53 | l=cymruwhois.Client(memcache_host=None) 54 | l.socket = FakeSocket() 55 | l.file = FakeFile([ 56 | '22990 | 169.226.11.11 | 169.226.0.0/16 | US | ALBANYEDU - The University at Albany', 57 | '22991 | 169.226.11.11 | 169.226.0.0/16 | US | ALBANYEDU - The University at Albany', 58 | '15169 | 66.102.1.104 | 66.102.0.0/23 | US | GOOGLE - Google Inc.', 59 | ]) 60 | l._connected = True 61 | 62 | rec = l.lookup("169.226.11.11") 63 | assert rec.asn == '22990' 64 | 65 | rec = l.lookup("66.102.1.104") 66 | assert rec.asn == '15169' 67 | 68 | 69 | def test_multiple_returned_for_a_single_ip_dict(): 70 | l=cymruwhois.Client(memcache_host=None) 71 | l.socket = FakeSocket() 72 | l.file = FakeFile([ 73 | '22990 | 169.226.11.11 | 169.226.0.0/16 | US | ALBANYEDU - The University at Albany', 74 | '22991 | 169.226.11.11 | 169.226.0.0/16 | US | ALBANYEDU - The University at Albany', 75 | '15169 | 66.102.1.104 | 66.102.0.0/23 | US | GOOGLE - Google Inc.', 76 | ]) 77 | l._connected = True 78 | 79 | recs = l.lookupmany_dict(['169.226.11.11','66.102.1.104']) 80 | rec = recs['169.226.11.11'] 81 | assert rec.asn == '22990' 82 | 83 | rec = recs['66.102.1.104'] 84 | assert rec.asn == '15169' 85 | -------------------------------------------------------------------------------- /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 | 9 | # Internal variables. 10 | PAPEROPT_a4 = -D latex_paper_size=a4 11 | PAPEROPT_letter = -D latex_paper_size=letter 12 | ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 13 | 14 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest 15 | 16 | help: 17 | @echo "Please use \`make ' where is one of" 18 | @echo " html to make standalone HTML files" 19 | @echo " dirhtml to make HTML files named index.html in directories" 20 | @echo " pickle to make pickle files" 21 | @echo " json to make JSON files" 22 | @echo " htmlhelp to make HTML files and a HTML help project" 23 | @echo " qthelp to make HTML files and a qthelp project" 24 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 25 | @echo " changes to make an overview of all changed/added/deprecated items" 26 | @echo " linkcheck to check all external links for integrity" 27 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 28 | 29 | clean: 30 | -rm -rf _build/* 31 | 32 | html: 33 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html 34 | @echo 35 | @echo "Build finished. The HTML pages are in _build/html." 36 | 37 | dirhtml: 38 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml 39 | @echo 40 | @echo "Build finished. The HTML pages are in _build/dirhtml." 41 | 42 | pickle: 43 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle 44 | @echo 45 | @echo "Build finished; now you can process the pickle files." 46 | 47 | json: 48 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json 49 | @echo 50 | @echo "Build finished; now you can process the JSON files." 51 | 52 | htmlhelp: 53 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp 54 | @echo 55 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 56 | ".hhp project file in _build/htmlhelp." 57 | 58 | qthelp: 59 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp 60 | @echo 61 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 62 | ".qhcp project file in _build/qthelp, like this:" 63 | @echo "# qcollectiongenerator _build/qthelp/cymruwhois.qhcp" 64 | @echo "To view the help file:" 65 | @echo "# assistant -collectionFile _build/qthelp/cymruwhois.qhc" 66 | 67 | latex: 68 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex 69 | @echo 70 | @echo "Build finished; the LaTeX files are in _build/latex." 71 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 72 | "run these through (pdf)latex." 73 | 74 | changes: 75 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes 76 | @echo 77 | @echo "The overview file is in _build/changes." 78 | 79 | linkcheck: 80 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck 81 | @echo 82 | @echo "Link check complete; look for any errors in the above output " \ 83 | "or in _build/linkcheck/output.txt." 84 | 85 | doctest: 86 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest 87 | @echo "Testing of doctests in the sources finished, look at the " \ 88 | "results in _build/doctest/output.txt." 89 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | set SPHINXBUILD=sphinx-build 6 | set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% . 7 | if NOT "%PAPER%" == "" ( 8 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 9 | ) 10 | 11 | if "%1" == "" goto help 12 | 13 | if "%1" == "help" ( 14 | :help 15 | echo.Please use `make ^` where ^ is one of 16 | echo. html to make standalone HTML files 17 | echo. dirhtml to make HTML files named index.html in directories 18 | echo. pickle to make pickle files 19 | echo. json to make JSON files 20 | echo. htmlhelp to make HTML files and a HTML help project 21 | echo. qthelp to make HTML files and a qthelp project 22 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 23 | echo. changes to make an overview over all changed/added/deprecated items 24 | echo. linkcheck to check all external links for integrity 25 | echo. doctest to run all doctests embedded in the documentation if enabled 26 | goto end 27 | ) 28 | 29 | if "%1" == "clean" ( 30 | for /d %%i in (_build\*) do rmdir /q /s %%i 31 | del /q /s _build\* 32 | goto end 33 | ) 34 | 35 | if "%1" == "html" ( 36 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html 37 | echo. 38 | echo.Build finished. The HTML pages are in _build/html. 39 | goto end 40 | ) 41 | 42 | if "%1" == "dirhtml" ( 43 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml 44 | echo. 45 | echo.Build finished. The HTML pages are in _build/dirhtml. 46 | goto end 47 | ) 48 | 49 | if "%1" == "pickle" ( 50 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle 51 | echo. 52 | echo.Build finished; now you can process the pickle files. 53 | goto end 54 | ) 55 | 56 | if "%1" == "json" ( 57 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json 58 | echo. 59 | echo.Build finished; now you can process the JSON files. 60 | goto end 61 | ) 62 | 63 | if "%1" == "htmlhelp" ( 64 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp 65 | echo. 66 | echo.Build finished; now you can run HTML Help Workshop with the ^ 67 | .hhp project file in _build/htmlhelp. 68 | goto end 69 | ) 70 | 71 | if "%1" == "qthelp" ( 72 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp 73 | echo. 74 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 75 | .qhcp project file in _build/qthelp, like this: 76 | echo.^> qcollectiongenerator _build\qthelp\cymruwhois.qhcp 77 | echo.To view the help file: 78 | echo.^> assistant -collectionFile _build\qthelp\cymruwhois.ghc 79 | goto end 80 | ) 81 | 82 | if "%1" == "latex" ( 83 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex 84 | echo. 85 | echo.Build finished; the LaTeX files are in _build/latex. 86 | goto end 87 | ) 88 | 89 | if "%1" == "changes" ( 90 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes 91 | echo. 92 | echo.The overview file is in _build/changes. 93 | goto end 94 | ) 95 | 96 | if "%1" == "linkcheck" ( 97 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck 98 | echo. 99 | echo.Link check complete; look for any errors in the above output ^ 100 | or in _build/linkcheck/output.txt. 101 | goto end 102 | ) 103 | 104 | if "%1" == "doctest" ( 105 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest 106 | echo. 107 | echo.Testing of doctests in the sources finished, look at the ^ 108 | results in _build/doctest/output.txt. 109 | goto end 110 | ) 111 | 112 | :end 113 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # cymruwhois documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Mar 22 00:26:37 2009. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.insert(0, os.path.abspath('.')) 20 | sys.path.insert(0, os.path.abspath('..')) 21 | 22 | # -- General configuration ----------------------------------------------------- 23 | 24 | # Add any Sphinx extension module names here, as strings. They can be extensions 25 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 26 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest'] 27 | 28 | # Add any paths that contain templates here, relative to this directory. 29 | templates_path = ['_templates'] 30 | 31 | # The suffix of source filenames. 32 | source_suffix = '.rst' 33 | 34 | # The encoding of source files. 35 | #source_encoding = 'utf-8' 36 | 37 | # The master toctree document. 38 | master_doc = 'index' 39 | 40 | # General information about the project. 41 | project = u'cymruwhois' 42 | copyright = u'2009, Justin Azoff' 43 | 44 | # The version info for the project you're documenting, acts as replacement for 45 | # |version| and |release|, also used in various other places throughout the 46 | # built documents. 47 | # 48 | # The short X.Y version. 49 | version = '1.0' 50 | # The full version, including alpha/beta/rc tags. 51 | release = '1.0' 52 | 53 | # The language for content autogenerated by Sphinx. Refer to documentation 54 | # for a list of supported languages. 55 | #language = None 56 | 57 | # There are two options for replacing |today|: either, you set today to some 58 | # non-false value, then it is used: 59 | #today = '' 60 | # Else, today_fmt is used as the format for a strftime call. 61 | #today_fmt = '%B %d, %Y' 62 | 63 | # List of documents that shouldn't be included in the build. 64 | #unused_docs = [] 65 | 66 | # List of directories, relative to source directory, that shouldn't be searched 67 | # for source files. 68 | exclude_trees = ['_build'] 69 | 70 | # The reST default role (used for this markup: `text`) to use for all documents. 71 | #default_role = None 72 | 73 | # If true, '()' will be appended to :func: etc. cross-reference text. 74 | #add_function_parentheses = True 75 | 76 | # If true, the current module name will be prepended to all description 77 | # unit titles (such as .. function::). 78 | #add_module_names = True 79 | 80 | # If true, sectionauthor and moduleauthor directives will be shown in the 81 | # output. They are ignored by default. 82 | #show_authors = False 83 | 84 | # The name of the Pygments (syntax highlighting) style to use. 85 | pygments_style = 'sphinx' 86 | 87 | # A list of ignored prefixes for module index sorting. 88 | #modindex_common_prefix = [] 89 | 90 | 91 | # -- Options for HTML output --------------------------------------------------- 92 | 93 | # The theme to use for HTML and HTML Help pages. Major themes that come with 94 | # Sphinx are currently 'default' and 'sphinxdoc'. 95 | html_theme = 'default' 96 | 97 | # Theme options are theme-specific and customize the look and feel of a theme 98 | # further. For a list of options available for each theme, see the 99 | # documentation. 100 | #html_theme_options = {} 101 | 102 | # Add any paths that contain custom themes here, relative to this directory. 103 | #html_theme_path = [] 104 | 105 | # The name for this set of Sphinx documents. If None, it defaults to 106 | # " v documentation". 107 | #html_title = None 108 | 109 | # A shorter title for the navigation bar. Default is the same as html_title. 110 | #html_short_title = None 111 | 112 | # The name of an image file (relative to this directory) to place at the top 113 | # of the sidebar. 114 | #html_logo = None 115 | 116 | # The name of an image file (within the static path) to use as favicon of the 117 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 118 | # pixels large. 119 | #html_favicon = None 120 | 121 | # Add any paths that contain custom static files (such as style sheets) here, 122 | # relative to this directory. They are copied after the builtin static files, 123 | # so a file named "default.css" will overwrite the builtin "default.css". 124 | html_static_path = ['_static'] 125 | 126 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 127 | # using the given strftime format. 128 | #html_last_updated_fmt = '%b %d, %Y' 129 | 130 | # If true, SmartyPants will be used to convert quotes and dashes to 131 | # typographically correct entities. 132 | #html_use_smartypants = True 133 | 134 | # Custom sidebar templates, maps document names to template names. 135 | #html_sidebars = {} 136 | 137 | # Additional templates that should be rendered to pages, maps page names to 138 | # template names. 139 | #html_additional_pages = {} 140 | 141 | # If false, no module index is generated. 142 | #html_use_modindex = True 143 | 144 | # If false, no index is generated. 145 | #html_use_index = True 146 | 147 | # If true, the index is split into individual pages for each letter. 148 | #html_split_index = False 149 | 150 | # If true, links to the reST sources are added to the pages. 151 | #html_show_sourcelink = True 152 | 153 | # If true, an OpenSearch description file will be output, and all pages will 154 | # contain a tag referring to it. The value of this option must be the 155 | # base URL from which the finished HTML is served. 156 | #html_use_opensearch = '' 157 | 158 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 159 | #html_file_suffix = '' 160 | 161 | # Output file base name for HTML help builder. 162 | htmlhelp_basename = 'cymruwhoisdoc' 163 | 164 | 165 | # -- Options for LaTeX output -------------------------------------------------- 166 | 167 | # The paper size ('letter' or 'a4'). 168 | #latex_paper_size = 'letter' 169 | 170 | # The font size ('10pt', '11pt' or '12pt'). 171 | #latex_font_size = '10pt' 172 | 173 | # Grouping the document tree into LaTeX files. List of tuples 174 | # (source start file, target name, title, author, documentclass [howto/manual]). 175 | latex_documents = [ 176 | ('index', 'cymruwhois.tex', ur'cymruwhois Documentation', 177 | ur'Justin Azoff', 'manual'), 178 | ] 179 | 180 | # The name of an image file (relative to this directory) to place at the top of 181 | # the title page. 182 | #latex_logo = None 183 | 184 | # For "manual" documents, if this is true, then toplevel headings are parts, 185 | # not chapters. 186 | #latex_use_parts = False 187 | 188 | # Additional stuff for the LaTeX preamble. 189 | #latex_preamble = '' 190 | 191 | # Documents to append as an appendix to all manuals. 192 | #latex_appendices = [] 193 | 194 | # If false, no module index is generated. 195 | #latex_use_modindex = True 196 | -------------------------------------------------------------------------------- /cymruwhois.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # cymruwhois.py 3 | # Copyright (C) 2009 Justin Azoff JAzoff@uamail.albany.edu 4 | # 5 | # This module is released under the MIT License: 6 | # http://www.opensource.org/licenses/mit-license.php 7 | 8 | import socket 9 | import errno 10 | 11 | try : 12 | import memcache 13 | HAVE_MEMCACHE = True 14 | except ImportError: 15 | HAVE_MEMCACHE = False 16 | 17 | def iterwindow(l, slice=50): 18 | """Generate sublists from an iterator 19 | >>> list(iterwindow(iter(range(10)),11)) 20 | [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] 21 | >>> list(iterwindow(iter(range(10)),9)) 22 | [[0, 1, 2, 3, 4, 5, 6, 7, 8], [9]] 23 | >>> list(iterwindow(iter(range(10)),5)) 24 | [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]] 25 | >>> list(iterwindow(iter(range(10)),3)) 26 | [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] 27 | >>> list(iterwindow(iter(range(10)),1)) 28 | [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9]] 29 | """ 30 | 31 | assert(slice > 0) 32 | a=[] 33 | 34 | for x in l: 35 | if len(a) >= slice : 36 | yield a 37 | a=[] 38 | a.append(x) 39 | 40 | if a: 41 | yield a 42 | 43 | 44 | class record: 45 | def __init__(self, asn, ip, prefix, cc, owner): 46 | 47 | def fix(x): 48 | x = x.strip() 49 | try: 50 | x = str(x.decode('ascii','ignore')) 51 | except AttributeError: 52 | pass # for Python 3 53 | return x 54 | 55 | self.asn = fix(asn) 56 | self.ip = fix(ip) 57 | self.prefix = fix(prefix) 58 | self.cc = fix(cc) 59 | self.owner = fix(owner) 60 | 61 | self.key = self.ip 62 | 63 | def __str__(self): 64 | return "%-10s %-16s %-16s %s '%s'" % (self.asn, self.ip, self.prefix, self.cc, self.owner) 65 | def __repr__(self): 66 | return "<%s instance: %s|%s|%s|%s|%s>" % (self.__class__, self.asn, self.ip, self.prefix, self.cc, self.owner) 67 | 68 | class asrecord: 69 | def __init__(self, asn, cc, owner): 70 | 71 | def fix(x): 72 | x = x.strip() 73 | if x == "NA": 74 | return None 75 | try: 76 | x = str(x.decode('ascii','ignore')) 77 | except AttributeError: 78 | pass # for Python 3 79 | return x 80 | 81 | self.asn = fix(asn) 82 | self.cc = fix(cc) 83 | self.owner = fix(owner) 84 | 85 | self.key = "AS" + self.asn 86 | 87 | def __str__(self): 88 | return "%-10s %s '%s'" % (self.asn, self.cc, self.owner) 89 | def __repr__(self): 90 | return "<%s instance: %s|%s|%s>" % (self.__class__, self.asn, self.cc, self.owner) 91 | 92 | class Client: 93 | """Python interface to whois.cymru.com 94 | 95 | **Usage** 96 | 97 | >>> import socket 98 | >>> ip = socket.gethostbyname("www.google.com") 99 | >>> from cymruwhois import Client 100 | >>> c=Client() 101 | >>> r=c.lookup(ip) 102 | >>> print(r.asn) 103 | 15169 104 | >>> print(r.owner) 105 | GOOGLE - Google Inc., US 106 | >>> 107 | >>> for r in c.lookupmany([ip, "8.8.8.8"]): 108 | ... print(r.owner) 109 | GOOGLE - Google Inc., US 110 | GOOGLE - Google Inc., US 111 | """ 112 | def make_key(self, arg): 113 | if arg.startswith("AS"): 114 | return "cymruwhois:as:" + arg 115 | else: 116 | return "cymruwhois:ip:" + arg 117 | 118 | def __init__(self, host="whois.cymru.com", port=43, memcache_host='localhost:11211'): 119 | self.host=host 120 | self.port=port 121 | self._connected=False 122 | self.c = None 123 | if HAVE_MEMCACHE and memcache_host: 124 | self.c = memcache.Client([memcache_host]) 125 | 126 | def _connect(self): 127 | self.socket=socket.socket() 128 | self.socket.settimeout(5.0) 129 | self.socket.connect((self.host,self.port)) 130 | self.socket.settimeout(10.0) 131 | self.file = self.socket.makefile("rw") 132 | def _sendline(self, line): 133 | self.file.write(line + "\r\n") 134 | self.file.flush() 135 | def _readline(self): 136 | return self.file.readline() 137 | 138 | def _disconnect(self): 139 | self.file.close() 140 | self.socket.close() 141 | 142 | def read_and_discard(self): 143 | self.socket.setblocking(0) 144 | try : 145 | try : 146 | self.file.read(1024) 147 | except socket.error as e: 148 | #10035 is WSAEWOULDBLOCK for windows systems on older python versions 149 | if e.args[0] not in (errno.EAGAIN, errno.EWOULDBLOCK, 10035): 150 | raise 151 | finally: 152 | self.socket.setblocking(1) 153 | 154 | def _begin(self): 155 | """Explicitly connect and send BEGIN to start the lookup process""" 156 | self._connect() 157 | self._sendline("BEGIN") 158 | self._readline() #discard the message "Bulk mode; one IP per line. [2005-08-02 18:54:55 GMT]" 159 | self._sendline("PREFIX\nASNUMBER\nCOUNTRYCODE\nNOTRUNC") 160 | self._connected=True 161 | 162 | def disconnect(self): 163 | """Explicitly send END to stop the lookup process and disconnect""" 164 | if not self._connected: return 165 | 166 | self._sendline("END") 167 | self._disconnect() 168 | self._connected=False 169 | 170 | def get_cached(self, ips): 171 | if not self.c: 172 | return {} 173 | keys = [self.make_key(ip) for ip in ips] 174 | vals = self.c.get_multi(keys) 175 | #convert cymruwhois:ip:1.2.3.4 into just 1.2.3.4 176 | return dict((k.split(":")[-1], v) for k,v in list(vals.items())) 177 | 178 | def cache(self, r): 179 | if not self.c: 180 | return 181 | self.c.set(self.make_key(r.key), r, 60*60*6) 182 | 183 | def lookup(self, ip): 184 | """Look up a single address. 185 | 186 | .. warning:: 187 | Do not call this function inside of a loop, the performance 188 | will be terrible. Instead, call lookupmany or lookupmany_dict 189 | """ 190 | return list(self.lookupmany([ip]))[0] 191 | 192 | def lookupmany(self, ips): 193 | """Look up many ip addresses""" 194 | ips = [str(ip).strip() for ip in ips] 195 | 196 | for batch in iterwindow(ips, 100): 197 | cached = self.get_cached(batch) 198 | not_cached = [ip for ip in batch if not cached.get(ip)] 199 | #print "cached:%d not_cached:%d" % (len(cached), len(not_cached)) 200 | if not_cached: 201 | for rec in self._lookupmany_raw(not_cached): 202 | cached[rec.key] = rec 203 | for ip in batch: 204 | if ip in cached: 205 | yield cached[ip] 206 | 207 | def lookupmany_dict(self, ips): 208 | """Look up many ip addresses, returning a dictionary of ip -> record""" 209 | ips = set(ips) 210 | return dict((r.key, r) for r in self.lookupmany(ips)) 211 | 212 | def _lookupmany_raw(self, ips): 213 | """Do a look up for some ips""" 214 | 215 | if not self._connected: 216 | self._begin() 217 | ips = set(ips) 218 | for ip in ips: 219 | self._sendline(ip) 220 | 221 | need = len(ips) 222 | last = None 223 | while need: 224 | result=self._readline() 225 | if 'Error: no ASN or IP match on line' in result: 226 | need -=1 227 | continue 228 | parts=result.split("|") 229 | if len(parts)==5: 230 | r=record(*parts) 231 | else: 232 | r=asrecord(*parts) 233 | 234 | #check for multiple records being returned for a single IP 235 | #in this case, just skip any extra records 236 | if last and r.key == last.key: 237 | continue 238 | 239 | self.cache(r) 240 | yield r 241 | last = r 242 | need -=1 243 | 244 | #skip any trailing records that might have been caused by multiple records for the last ip 245 | self.read_and_discard() 246 | 247 | 248 | #backwards compatibility 249 | lookerupper = Client 250 | 251 | def lookup_stdin(): 252 | from optparse import OptionParser 253 | import fileinput 254 | parser = OptionParser(usage = "usage: %prog [options] [files]") 255 | parser.add_option("-d", "--delim", dest="delim", action="store", default=None, 256 | help="delimiter to use instead of justified") 257 | parser.add_option("-f", "--fields", dest="fields", action="append", 258 | help="comma separated fields to include (asn,ip,prefix,cc,owner)") 259 | 260 | if HAVE_MEMCACHE: 261 | parser.add_option("-c", "--cache", dest="cache", action="store", default="localhost:11211", 262 | help="memcache server (default localhost)") 263 | parser.add_option("-n", "--no-cache", dest="cache", action="store_false", 264 | help="don't use memcached") 265 | else: 266 | memcache_host = None 267 | 268 | (options, args) = parser.parse_args() 269 | 270 | #fix the fields: convert ['a,b','c'] into ['a','b','c'] if needed 271 | fields = [] 272 | if options.fields: 273 | for f in options.fields: 274 | fields.extend(f.split(",")) 275 | else: 276 | fields = 'asn ip prefix cc owner'.split() 277 | 278 | #generate the format string 279 | fieldwidths = { 280 | 'asn': 8, 281 | 'ip': 15, 282 | 'prefix': 18, 283 | 'cc': 2, 284 | 'owner': 0, 285 | } 286 | if options.delim: 287 | format = options.delim.join("%%(%s)s" % f for f in fields) 288 | else: 289 | format = ' '.join("%%(%s)-%ds" % (f, fieldwidths[f]) for f in fields) 290 | 291 | #setup the memcache option 292 | 293 | if HAVE_MEMCACHE: 294 | memcache_host = options.cache 295 | if memcache_host and ':' not in memcache_host: 296 | memcache_host += ":11211" 297 | 298 | c=Client(memcache_host=memcache_host) 299 | ips = [] 300 | 301 | for line in fileinput.input(args): 302 | ip=line.strip() 303 | ips.append(ip) 304 | for r in c.lookupmany(ips): 305 | print(format % r.__dict__) 306 | 307 | if __name__ == "__main__": 308 | lookup_stdin() 309 | --------------------------------------------------------------------------------