Source code for networktools.api

"""
Main API functionality
"""

import sys
import socket
import logging
import nmap
import pygeoip
import math

from pythonwhois import get_whois
from pythonwhois.shared import WhoisException
from dns import exception, resolver
from flask import request, jsonify, render_template, abort
from ipwhois import IPWhois, ASNLookupError, IPDefinedError, ASNRegistryError, HostLookupError, BlacklistError
from netaddr import IPNetwork, AddrFormatError, AddrConversionError
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

from networktools import app
from networktools import errors

logging.basicConfig(level=app.config['LOG_LEVEL'], format="[%(asctime)s][%(levelname)s] - %(message)s")
logger = logging.getLogger(__name__)
logger.info(r"""
  _   _      _                      _    _____           _          _    ____ ___
 | \ | | ___| |___      _____  _ __| | _|_   _|__   ___ | |___     / \  |  _ \_ _|
 |  \| |/ _ \ __\ \ /\ / / _ \| '__| |/ / | |/ _ \ / _ \| / __|   / _ \ | |_) | |
 | |\  |  __/ |_ \ V  V / (_) | |  |   <  | | (_) | (_) | \__ \  / ___ \|  __/| |
 |_| \_|\___|\__| \_/\_/ \___/|_|  |_|\_\ |_|\___/ \___/|_|___/ /_/   \_\_|  |___|

""")

ALLOWED_IPS = app.config['ALLOWED_IPS']

try:
    CITY_MMDB = pygeoip.GeoIP(app.config['CITY_MMDB_LOCATION'])
    ISP_MMDB = pygeoip.GeoIP(app.config['ISP_MMDB_LOCATION'])
except IOError as e:
    logger.critical("Cannot open GEOIP database: %s", str(e))

limiter = Limiter(app,
                  key_func=get_remote_address,
                  global_limits=app.config['LIMITER_GLOBAL_LIMITS'])


@app.before_request
[docs]def acl(): """ Decorator that checks if visitor's IP address is in the list of allowed IP addresses and aborts if it isn't. Leave empty list in case you don't want to use IP restrictions """ if request.remote_addr not in ALLOWED_IPS and len(ALLOWED_IPS) > 0: logger.debug("IP not in list of allowed IPs: %s", request.remote_addr) abort(403) elif len(ALLOWED_IPS) == 0: logger.debug("No IPs defined in whitelist") logger.debug("Allowing access to: %s", request.remote_addr) return
def hostname_resolves(hostname): resolved_ips = [] logger.debug("Hostname to resolve: %s", str(hostname)) try: ips = socket.getaddrinfo(hostname, None) for ip in ips: if ip[4][0] not in resolved_ips: net = IPNetwork(ip[4][0]) if net.is_private() or net.is_link_local(): logger.warning("Tried to resolve local IP: %s, ignoring!", ip[4][0]) continue else: resolved_ips.append(ip[4][0]) if len(resolved_ips) > 0: logger.debug("Resolved IPs: %s", ', '.join(resolved_ips)) return resolved_ips else: logger.warning("No IPs resolved from query: %s", hostname) abort(400) except socket.gaierror: logger.debug("Failed to resolve IP address from: %s", hostname) abort(400) def get_ipwhois(ip): try: ipwhois_query = IPWhois(ip) logger.debug(ipwhois_query) except ASNLookupError as e: return str(e) except IPDefinedError as e: return str(e) except ASNRegistryError as e: return str(e) except HostLookupError as e: return str(e) except BlacklistError as e: return str(e) ipwhois_result = ipwhois_query.lookup() return ipwhois_result
[docs]def error_response(msg, rc): """ Returns properly formatted error api response :param msg: Human readable error message :param rc: HTTP return code :return: JSON formatted error response """ return jsonify({'status': 'error', 'msg': str(msg)}), rc
[docs]def geoip_distance(lat1, long1, lat2, long2, metric=True): """ Calculates distance between two points on spherical earth. Source: http://www.johndcook.com/blog/python_longitude_latitude/ :param lat1: Latitude of the first point :param long1: Longitude of the second point :param lat2: Latitude of the second point :param long2: Longitude of the second point :param metric: if true returns km, else returns miles :return: Distance in km """ degrees_to_radians = math.pi / 180.0 phi1 = (90.0 - lat1) * degrees_to_radians phi2 = (90.0 - lat2) * degrees_to_radians theta1 = long1 * degrees_to_radians theta2 = long2 * degrees_to_radians cos = (math.sin(phi1) * math.sin(phi2) * math.cos(theta1 - theta2) + math.cos(phi1) * math.cos(phi2)) arc = math.acos(cos) if metric: return arc * 6371, "km" else: return arc * 3959, "mi"
@app.route('/')
[docs]def return_whatismyip(): """ Simple page with user's IP address information :return: What Is My IP page """ error = {} visitor_ip = request.remote_addr geoip = CITY_MMDB.record_by_addr(visitor_ip) isp = ISP_MMDB.org_by_addr(visitor_ip) ipwhois_info = get_ipwhois(visitor_ip) if isinstance(ipwhois_info, basestring): # If we don't get the hash results we got an exception error = ipwhois_info return render_template('whatismyip.html', ip=visitor_ip, geoip=geoip, isp=isp, whois_info=ipwhois_info, error=error)
@app.route('/plain')
[docs]def return_ip(): """ Curl / copy friendly external IP information. Could be wrong if behind proxy. :return: Public IP address of requestor """ visitor_ip = request.remote_addr return render_template('ip.html', ip=visitor_ip)
# API FUNCTIONS @app.route('/api/geoip/<string:query>', methods=['GET'])
[docs]def return_geoip(query): """ Geoip query, accepts IP or FQDN. FQDN is resolved to ip and then queried for geoip data. Google maps url in response for quick access. Distance available in metric and imperial measurements. Defaults to metric. Add ``?imperial`` to get distance in miles. :param query: IP or FQDN **Example:** **GET:** ``/api/nmap/8.8.8.8`` :: { "geoip": [ { "area_code": 650, "city": "Mountain View", "continent": "NA", "country_code": "US", "country_code3": "USA", "country_name": "United States", "distance": 830 "distance_unit": "km" "dma_code": 807, "google_maps_url": "http://maps.google.com/maps?q=loc:37.386,-122.0838", "ip": "8.8.8.8", "isp": "Google", "latitude": 37.385999999999996, "longitude": -122.0838, "metro_code": "San Francisco, CA", "postal_code": "94040", "region_code": "CA", "time_zone": "America/Los_Angeles" }], "resolved_ips": [ "8.8.8.8" ], "status": "ok" } """ hosts = [] resolved_ip = hostname_resolves(query) visitor_ip = request.remote_addr visitor_geoip = CITY_MMDB.record_by_addr(visitor_ip) args = request.args.get('imperial') if args is not None: metric = False else: metric = True for ip in resolved_ip: logger.debug(ip) geoip = CITY_MMDB.record_by_addr(ip) geoip['ip'] = ip isp = ISP_MMDB.org_by_addr(ip) geoip['isp'] = isp geoip['google_maps_url'] = "http://maps.google.com/maps?q=loc:{},{}".format(geoip['latitude'], geoip['longitude']) if visitor_geoip is not None: distance, unit = geoip_distance(visitor_geoip['latitude'], visitor_geoip['longitude'], geoip['latitude'], geoip['longitude'], metric=metric) geoip['distance'] = distance geoip['distance_unit'] = unit else: geoip['distance'] = None geoip['distance_unit'] = None hosts.append(geoip) logger.debug("GeoIP hosts: {}".format(hosts)) return jsonify({'status': "ok", 'resolved_ips': resolved_ip, 'geoip': hosts})
@app.route('/api/ipwhois/<string:ipquery>', methods=['GET'])
[docs]def return_ipwhois(ipquery): """ IP whois query, accepts IP or FQDN. FQDN is resolved to ip and then queried for ipwhois data. Returns IP address and network's registered user. :param ipquery: IP or FQDN **Example:** **GET:** ``/api/ipwhois/84.20.255.11`` :: { ipwhois: [ { asn: "15169", asn_cidr: "8.8.8.0/24", asn_country_code: "US", asn_date: "", asn_registry: "arin", nets: [ { address: "1025 Eldorado Blvd.", cidr: "8.0.0.0/8", city: "Broomfield", country: "US", created: "1992-12-01", description: "Level 3 Communications, Inc.", emails: "noc.coreip@level3.com abuse@level3.com ipaddressing@level3.com", handle: "NET-8-0-0-0-1", name: "LVLT-ORG-8-8", postal_code: "80021", range: "8.0.0.0 - 8.255.255.255", state: "CO", updated: "2012-02-24" }, { address: "1600 Amphitheatre Parkway", cidr: "8.8.8.0/24", city: "Mountain View", country: "US", created: "2014-03-14", description: "Google Inc.", emails: "network-abuse@google.com arin-contact@google.com", handle: "NET-8-8-8-0-1", name: "LVLT-GOGL-8-8-8", postal_code: "94043", range: null, state: "CA", updated: "2014-03-14" } ], query: "8.8.8.8", raw: null, raw_referral: null, referral: null } ], status: "ok" } } """ hosts = [] resolved_ip = hostname_resolves(ipquery) for ip in resolved_ip: hosts.append(get_ipwhois(ip)) return jsonify({'ipwhois': hosts, 'status': "ok"})
@app.route('/api/whois/<string:query>', methods=['GET'])
[docs]def return_whois(query): """ Whois query, accepts IP or FQDN. FQDN is resolved to ip and then queried for ipwhois data. :param query: IP or FQDN **Example:** $ GET ``/api/whois/1-up.xyz`` :: { status: "ok", whois: { contacts: { admin: { city: "Panama", country: "PA", email: "5cc1435e8bae40bf8275d28a2150b6bf.protect@whoisguard.com", fax: "+51.17057182", handle: "C44073503-CNIC", name: "WhoisGuard Protected", organization: "WhoisGuard, Inc.", phone: "+507.8365503", postalcode: "00000", state: "Panama", street: "P.O. Box 0823-03411" }, billing: { city: "Panama", country: "PA", email: "5cc1435e8bae40bf8275d28a2150b6bf.protect@whoisguard.com", fax: "+51.17057182", handle: "C44073504-CNIC", name: "WhoisGuard Protected", organization: "WhoisGuard, Inc.", phone: "+507.8365503", postalcode: "00000", state: "Panama", street: "P.O. Box 0823-03411" }, registrant: { city: "Panama", country: "PA", email: "5cc1435e8bae40bf8275d28a2150b6bf.protect@whoisguard.com", fax: "+51.17057182", handle: "C44073500-CNIC", name: "WhoisGuard Protected", organization: "WhoisGuard, Inc.", phone: "+507.8365503", postalcode: "00000", state: "Panama", street: "P.O. Box 0823-03411" }, tech: { city: "Panama", country: "PA", email: "5cc1435e8bae40bf8275d28a2150b6bf.protect@whoisguard.com", fax: "+51.17057182", handle: "C44073507-CNIC", name: "WhoisGuard Protected", organization: "WhoisGuard, Inc.", phone: "+507.8365503", postalcode: "00000", state: "Panama", street: "P.O. Box 0823-03411" } }, creation_date: [ "Thu, 10 Mar 2016 12:13:09 GMT" ], emails: [ "abuse@namecheap.com" ], expiration_date: [ "Fri, 10 Mar 2017 23:59:59 GMT" ], id: [ "D18499062-CNIC" ], nameservers: [ "dns1.registrar-servers.com", "dns2.registrar-servers.com", "dns3.registrar-servers.com", "dns4.registrar-servers.com", "dns5.registrar-servers.com" ], raw: [ "Domain name: 1-up.xyz Registry Domain ID: D18499062-CNIC Registrar WHOIS Server: whois.namecheap.com ... For more information, please see https://registrar-console.centralnic.com/pub/whois_guidance. " ], registrar: [ "NAMECHEAP INC" ], status: [ "clientTransferProhibited", "serverTransferProhibited", "addPeriod" ], updated_date: [ "Thu, 10 Mar 2016 12:13:12 GMT" ], whois_server: [ "whois.namecheap.com" ] } } """ try: result = get_whois(query) except WhoisException as e: return error_response(e, 400) if result: return jsonify({'whois': result, 'status': "ok"}) else: abort(400)
@app.route('/api/dns/<string:query>', methods=['GET'])
[docs]def return_dns(query): """ DNS query, accepts IP or FQDN. FQDN is resolved to ip and then queried for dns data. You can specify multiple alternative DNS server to query by specifying ``?q=<dns_ip1>,<dns_ip2>``. If you do not specify DNS servers with ``?q=`` default server are ``8.8.8.8,8.8.4.4`` :returns Host records (A), MX records, NS records, SOA, TXT records :param query: IP or FQDN **Example:** $ GET ``/api/dns/1-up.xyz`` :: { "hosts": { "records": [ { "address": "162.255.119.250", "class": "IN", "expires_in": "1799", "name": "1-up.xyz.", "type": "A" } ] }, "mx": { "records": [ { "address": "eforward1.registrar-servers.com.", "class": "IN", "expires_in": "1799", "name": "1-up.xyz.", "priority": "10", "type": "MX" }, { "address": "eforward4.registrar-servers.com.", "class": "IN", "expires_in": "1799", "name": "1-up.xyz.", "priority": "15", "type": "MX" }, { "address": "eforward5.registrar-servers.com.", "class": "IN", "expires_in": "1799", "name": "1-up.xyz.", "priority": "20", "type": "MX" }, { "address": "eforward2.registrar-servers.com.", "class": "IN", "expires_in": "1799", "name": "1-up.xyz.", "priority": "10", "type": "MX" }, { "address": "eforward3.registrar-servers.com.", "class": "IN", "expires_in": "1799", "name": "1-up.xyz.", "priority": "10", "type": "MX" } ] }, "ns": { "records": [ { "address": "dns2.registrar-servers.com.", "class": "IN", "expires_in": "1799", "name": "1-up.xyz.", "type": "NS" }, { "address": "dns1.registrar-servers.com.", "class": "IN", "expires_in": "1799", "name": "1-up.xyz.", "type": "NS" }, { "address": "dns3.registrar-servers.com.", "class": "IN", "expires_in": "1799", "name": "1-up.xyz.", "type": "NS" }, { "address": "dns5.registrar-servers.com.", "class": "IN", "expires_in": "1799", "name": "1-up.xyz.", "type": "NS" }, { "address": "dns4.registrar-servers.com.", "class": "IN", "expires_in": "1799", "name": "1-up.xyz.", "type": "NS" } ] }, "soa": { "records": [ { "class": "IN", "dns_contact": "hostmaster.registrar-servers.com.", "expire": "604800", "expires_in": "3600", "minimum": "3601", "name": "1-up.xyz.", "primary_ns": "dns1.registrar-servers.com.", "refresh": "43200", "retry": "3600", "serial": "2016031003", "type": "SOA" } ] }, "status": "ok", "txt": { "records": [ { "class": "IN", "expires_in": "1799", "name": "1-up.xyz.", "string": "\"v=spf1 include:spf.efwd.registrar-servers.com ~all\"", "type": "TXT" } ] } } """ dns_dict = {} dns_servers = [] querier = resolver.Resolver(configure=False) # Don't use /etc/resolv.conf args = request.args.get('q') if args is not None: for arg in args.split(','): dns_servers.append(arg) querier.nameservers = dns_servers logger.debug("Nameservers: " + ", ".join(querier.nameservers)) else: querier.nameservers = app.config.get('DEFAULT_NAMESERVERS') try: dns_records = querier.query(query, raise_on_no_answer=False) mail_records = querier.query(query, 'MX', raise_on_no_answer=False) ns_records = querier.query(query, 'NS', raise_on_no_answer=False) soa_records = querier.query(query, 'SOA', raise_on_no_answer=False) txt_records = querier.query(query, 'TXT', raise_on_no_answer=False) except resolver.NXDOMAIN: return error_response("Non-existent Domain Name (NXDOMAIN)", 400) except resolver.Timeout: return error_response("Request timed out", 200) except resolver.NoNameservers: return error_response("No name servers", 200) except resolver.NoAnswer: return error_response("No answer", 200) except resolver.NoRootSOA: return error_response("No root SOA", 200) except resolver.NoMetaqueries: return error_response("No meta queries", 200) except exception.DNSException: return error_response("Couldn't resolve domain name", 400) if dns_records.rrset is None: return error_response("No A or AAAA records for this domain name", 200) dns_dict['records'] = [] split_dns_records = dns_records.rrset.to_text().split('\n') for line in split_dns_records: line = line.split(" ") dns_dict['records'].append({'name': line[0], 'expires_in': line[1], 'class': line[2], 'type': line[3], 'address': line[4]}) mx_dict = dict() mx_dict['records'] = [] if mail_records.rrset is not None: split_mail_records = mail_records.rrset.to_text().split('\n') for line in split_mail_records: line = line.split(" ") mx_dict['records'].append({'name': line[0], 'expires_in': line[1], 'class': line[2], 'type': line[3], 'priority': line[4], 'address': line[5]}) soa_dict = dict() soa_dict['records'] = [] if soa_records.rrset is not None: split_soa_records = soa_records.rrset.to_text().split('\n') for line in split_soa_records: line = line.split(" ") soa_dict['records'].append({'name': line[0], 'expires_in': line[1], 'class': line[2], 'type': line[3], 'primary_ns': line[4], 'dns_contact': line[5], 'serial': line[6], 'refresh': line[7], 'retry': line[8], 'expire': line[9], 'minimum': line[10]}) ns_dict = dict() ns_dict['records'] = [] if ns_records.rrset is not None: split_ns_records = ns_records.rrset.to_text().split('\n') for line in split_ns_records: line = line.split(" ") ns_dict['records'].append({'name': line[0], 'expires_in': line[1], 'class': line[2], 'type': line[3], 'address': line[4]}) txt_dict = dict() txt_dict['records'] = [] if txt_records.rrset is not None: split_txt_records = txt_records.rrset.to_text().split('\n') for line in split_txt_records: line = line.split(" ", 4) txt_dict['records'].append({'name': line[0], 'expires_in': line[1], 'class': line[2], 'type': line[3], 'string': line[4]}) return jsonify({'status': "ok", 'hosts': dns_dict, 'mx': mx_dict, 'soa': soa_dict, 'ns': ns_dict, 'txt': txt_dict})
@app.route('/api/host/<string:query>') @app.route('/api/host/<string:query>/<string:netmask>')
[docs]def get_nmap_network(query, netmask=""): """ Shows IP's that are pingable and those that are not. Accepts netmask to define the whole range for scan. Uses NMAP's **-sP** to probe hosts. Probing is done from Hosting machine and therefore not suitable for scanning local network ranges. Useful when checking how many IP's you have left in a particular netmask. :param query: IP address or hostname :param netmask: (Optional) If you want to scan the whole range of hosts **Example:** $ GET ``/api/host/199.16.156.102/29`` :: { "down": [ "199.16.156.100", "199.16.156.101", "199.16.156.96", "199.16.156.97", "199.16.156.98", "199.16.156.99" ], "hosts": [ { "host": "199.16.156.100", "hostname": [], "status": "down" }, { "host": "199.16.156.101", "hostname": [], "status": "down" }, { "host": "199.16.156.102", "hostname": [], "status": "up" }, { "host": "199.16.156.103", "hostname": [], "status": "up" }, { "host": "199.16.156.96", "hostname": [], "status": "down" }, { "host": "199.16.156.97", "hostname": [], "status": "down" }, { "host": "199.16.156.98", "hostname": [], "status": "down" }, { "host": "199.16.156.99", "hostname": [], "status": "down" } ], "status": "ok", "up": [ "199.16.156.102", "199.16.156.103" ] } """ try: nm = nmap.PortScanner() hosts = dict() hosts['hosts'] = [] hosts['up'] = [] hosts['down'] = [] if netmask == "": to_query = query else: to_query = query + "/" + netmask logger.debug(to_query) try: net = IPNetwork(to_query) except AddrFormatError: abort(400) if net.is_link_local() or net.is_private(): # Won't scan server's local network abort(400) nm.scan(hosts=to_query, arguments='-sn -v') hosts_list = [(x, nm[x]) for x in nm.all_hosts()] logger.debug(hosts_list) for host, status in hosts_list: hosts['hosts'].append({'host': host, 'status': status['status']['state'], 'hostname': status['hostnames']}) if status['status']['state'] == 'up': hosts['up'].append(host) if status['status']['state'] == 'down': hosts['down'].append(host) return jsonify({'status': "ok", 'hosts': hosts['hosts'], 'up': hosts['up'], 'down': hosts['down']}) except nmap.PortScannerError: return error_response("Nmap error, can't scan", 500)
@app.route('/api/nmap/<string:query>') @app.route('/api/nmap/<string:query>/<netmask>')
[docs]def get_nmap_port(query, netmask=32): """ Scans host or whole subnet for hosts and their open ports. Uses NMAP's **-T4 -F** params to probe hosts. Probing is done from Hosting machine and therefore not suitable for scanning local network ranges. :param query: IP address or hostname :param netmask: (Optional) If you want to scan the whole range of hosts **Example:** $ GET ``/api/nmap/199.16.156.102/30`` Returns:: { "hosts": [ { "host": "199.16.156.102", "status": { "addresses": { "ipv4": "199.16.156.102" }, "hostnames": [], "status": { "reason": "syn-ack", "state": "up" }, "tcp": { "80": { "conf": "3", "cpe": "", "extrainfo": "", "name": "http", "product": "", "reason": "syn-ack", "state": "open", "version": "" }, "443": { "conf": "3", "cpe": "", "extrainfo": "", "name": "https", "product": "", "reason": "syn-ack", "state": "open", "version": "" }, "8888": { "conf": "3", "cpe": "", "extrainfo": "", "name": "sun-answerbook", "product": "", "reason": "syn-ack", "state": "open", "version": "" } }, "vendor": {} } }, { "host": "199.16.156.103", "status": { "addresses": { "ipv4": "199.16.156.103" }, "hostnames": [], "status": { "reason": "syn-ack", "state": "up" }, "tcp": { "80": { "conf": "3", "cpe": "", "extrainfo": "", "name": "http", "product": "", "reason": "syn-ack", "state": "open", "version": "" }, "443": { "conf": "3", "cpe": "", "extrainfo": "", "name": "https", "product": "", "reason": "syn-ack", "state": "open", "version": "" }, "8888": { "conf": "3", "cpe": "", "extrainfo": "", "name": "sun-answerbook", "product": "", "reason": "syn-ack", "state": "open", "version": "" } }, "vendor": {} } } ], "status": "ok" } """ try: nm = nmap.PortScanner() hosts = dict() hosts['hosts'] = [] if netmask == "": to_query = query else: to_query = query + "/" + str(netmask) logger.debug(to_query) try: net = IPNetwork(to_query) except AddrFormatError: abort(400) if net.is_link_local() or net.is_private(): # Won't scan server's local network abort(400) nm.scan(hosts=to_query, arguments='-T4 -F') hosts_list = [(x, nm[x]) for x in nm.all_hosts()] logger.debug(hosts_list) for host, status in hosts_list: hosts['hosts'].append({'host': host, 'status': status}) return jsonify({'status': "ok", 'hosts': hosts['hosts']}) except nmap.PortScannerError: return error_response("NMAP error, cannot scan", 500)
@app.route('/api/ipcalc/<string:query>') @app.route('/api/ipcalc/<string:query>/<string:netmask>')
[docs]def get_ipcalc_network(query, netmask=""): """ IP calculator tool: supports IPv6 and IPv4 networks. For list of values see below. IP list is optional since it will return many IPs for some subnets, if you need to show ip list you will have to append ``?iplist`` to your get request. **PLEASE NOTE:** iplist will print only 65536 addresses which is /16 in IPv4 subnetting or /112 in IPv6 subnetting. If you try to get more you will get an error. :param query: IP address or hostname :param netmask: (Optional) If you want the whole range of hosts **Example:** When getting single host $ GET ``/api/ipcalc/199.16.156.102/30`` :: { "results": { "broadcast": "199.16.156.103", "cidr": "199.16.156.100/30", "first_host": "199.16.156.101", "hostmask": "0.0.0.3", "ip_bits": "11000111.00010000.10011100.01100110", "ip_version": 4, "is_linklocal": false, "is_loopback": false, "is_multicast": false, "is_private": false, "is_public": true, "is_reserved": false, "is_unicast": true, "last_host": "199.16.156.102", "netmask": "255.255.255.252", "netmask_bits": "11111111.11111111.11111111.11111100", "network": "199.16.156.100", "network_bits": "11000111.00010000.10011100.01100100", "num_addresses": 4, "prefixlen": 30, "supernet": [ "0.0.0.0/0", "128.0.0.0/1", "192.0.0.0/2", "192.0.0.0/3", "192.0.0.0/4", "192.0.0.0/5", "196.0.0.0/6", "198.0.0.0/7", "199.0.0.0/8", "199.0.0.0/9", "199.0.0.0/10", "199.0.0.0/11", "199.16.0.0/12", "199.16.0.0/13", "199.16.0.0/14", "199.16.0.0/15", "199.16.0.0/16", "199.16.128.0/17", "199.16.128.0/18", "199.16.128.0/19", "199.16.144.0/20", "199.16.152.0/21", "199.16.156.0/22", "199.16.156.0/23", "199.16.156.0/24", "199.16.156.0/25", "199.16.156.64/26", "199.16.156.96/27", "199.16.156.96/28", "199.16.156.96/29" ], "to_ipv6": "::ffff:199.16.156.102/126" }, "status": "ok" } """ if netmask is not "": ip = query + '/' + netmask else: ip = query try: net = IPNetwork(ip) except AddrFormatError: abort(400) results = dict() results['broadcast'] = str(net.broadcast) results['network'] = str(net.network) results['netmask'] = str(net.netmask) results['cidr'] = str(net.cidr) results['num_addresses'] = net.size results['hostmask'] = str(net.hostmask) results['is_loopback'] = net.is_loopback() results['is_unicast'] = net.is_unicast() results['is_multicast'] = net.is_multicast() results['is_private'] = net.is_private() results['is_reserved'] = net.is_reserved() results['is_linklocal'] = net.is_link_local() results['is_public'] = net.is_unicast() and not net.is_private() results['prefixlen'] = net.prefixlen results['ip_version'] = net.version results['ip_bits'] = net.ip.bits() results['network_bits'] = net.network.bits() results['netmask_bits'] = net.netmask.bits() results['supernet'] = [str(supernet) for supernet in net.supernet()] if net.version == 4: results['to_ipv6'] = str(net.ipv6()) if request.query_string == 'iplist': if net.size <= 65536: results['ip_list'] = [str(ip) for ip in list(net)] else: return error_response("Too many IPs to list (limit is 65536), " "use smaller subnet or remove '?iplist' from query.", 400) if net.broadcast is not None: results['first_host'] = str(net.network + 1) results['last_host'] = str(net.broadcast - 1) else: results['first_host'] = str(net.ip) results['last_host'] = str(net.ip) elif net.version == 6: try: results['to_ipv4'] = str(net.ipv4()) except AddrConversionError: results['to_ipv4'] = None if request.query_string == 'iplist': if net.size <= 65536: results['ip_list'] = [str(ip) for ip in list(net)] else: return error_response("Too many IPs to list (limit is 65536), " "use smaller subnet or remove '?iplist' from query.", 400) return jsonify({'status': "ok", 'results': results})