-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresolver.py
More file actions
166 lines (144 loc) · 5.05 KB
/
resolver.py
File metadata and controls
166 lines (144 loc) · 5.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import asyncio
import ipaddress
import os.path
import random
import socket
from collections import namedtuple
import aiodns
import aiohttp
import maxminddb
from .errors import ResolveError
from .utils import DATA_DIR, log
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
GeoData = namedtuple(
'GeoData', ['code', 'name', 'region_code', 'region_name', 'city_name']
)
_countrydb = os.path.join(DATA_DIR, 'GeoLite2-Country.mmdb')
_citydb = os.path.join(DATA_DIR, 'GeoLite2-City.mmdb')
_geo_db = _citydb if os.path.exists(_citydb) else _countrydb
_mmdb_reader = maxminddb.open_database(_geo_db)
class Resolver:
"""Async host resolver based on aiodns."""
_cached_hosts = {}
_ip_hosts = [
'https://wtfismyip.com/text',
'http://api.ipify.org/',
'http://ipinfo.io/ip',
'http://ipv4.icanhazip.com/',
'http://myexternalip.com/raw',
'http://ipinfo.io/ip',
'http://ifconfig.io/ip',
]
def __init__(self, timeout=5):
self._timeout = timeout
try:
self._loop = asyncio.get_running_loop()
except:
self._loop = asyncio.new_event_loop()
#self._resolver = aiodns.DNSResolver(loop=self._loop)
self._resolver = aiodns.DNSResolver()
@staticmethod
def host_is_ip(host):
"""Check a host is IP address."""
# TODO: add IPv6 support
try:
ipaddress.IPv4Address(host)
except ipaddress.AddressValueError:
return False
else:
return True
@staticmethod
def get_ip_info(ip):
"""Return geo information about IP address.
`code` - ISO country code
`name` - Full name of country
`region_code` - ISO region code
`region_name` - Full name of region
`city_name` - Full name of city
"""
# from pprint import pprint
try:
ipInfo = _mmdb_reader.get(ip) or {}
except (maxminddb.errors.InvalidDatabaseError, ValueError):
ipInfo = {}
code, name = '--', 'Unknown'
city_name, region_code, region_name = ('Unknown',) * 3
if 'country' in ipInfo:
code = ipInfo['country']['iso_code']
name = ipInfo['country']['names']['en']
elif 'continent' in ipInfo:
code = ipInfo['continent']['code']
name = ipInfo['continent']['names']['en']
if 'city' in ipInfo:
city_name = ipInfo['city']['names']['en']
if 'subdivisions' in ipInfo:
region_code = ipInfo['subdivisions'][0]['iso_code']
region_name = ipInfo['subdivisions'][0]['names']['en']
return GeoData(code, name, region_code, region_name, city_name)
def _pop_random_ip_host(self):
host = random.choice(self._ip_hosts)
self._ip_hosts.remove(host)
return host
async def get_real_ext_ip(self):
"""Return real external IP address."""
while self._ip_hosts:
try:
timeout = aiohttp.ClientTimeout(total=self._timeout)
async with aiohttp.ClientSession(
# timeout=timeout, loop=self._loop
timeout=timeout
) as session, session.get(self._pop_random_ip_host()) as resp:
ip = await resp.text()
except asyncio.TimeoutError:
pass
else:
ip = ip.strip()
if self.host_is_ip(ip):
log.debug('Real external IP: %s', ip)
break
else:
raise RuntimeError('Could not get the external IP')
return ip
async def resolve(
self, host, port=80, family=None, qtype='A', logging=True
):
"""Return resolving IP address(es) from host name."""
if self.host_is_ip(host):
return host
_host = self._cached_hosts.get(host)
if _host:
return _host
resp = await self._resolve(host, qtype)
if resp:
hosts = [
{
'hostname': host,
'host': r.host,
'port': port,
'family': family,
'proto': socket.IPPROTO_IP,
'flags': socket.AI_NUMERICHOST,
}
for r in resp
]
if family:
self._cached_hosts[host] = hosts
else:
self._cached_hosts[host] = hosts[0]['host']
if logging:
log.debug(
'%s: Host resolved: %s' % (host, self._cached_hosts[host])
)
else:
if logging:
log.warning('%s: Could not resolve host' % host)
return self._cached_hosts.get(host)
async def _resolve(self, host, qtype):
try:
resp = await asyncio.wait_for(
self._resolver.query(host, qtype), timeout=self._timeout
)
except (aiodns.error.DNSError, asyncio.TimeoutError):
raise ResolveError
else:
return resp