Created
April 4, 2020 16:16
-
-
Save zed/939944cdc7c2f6a0eaab6dfd7eb5a88d to your computer and use it in GitHub Desktop.
Get external/public ip using DNS, HTTP, STUN protocols
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
"""Get external ip using DNS, HTTP, STUN protocols. | |
Print the first result (the fastest). | |
Usage: | |
$ python3 -m external_ip [--quiet] | |
Optional dependencies: | |
$ python3 -m pip install aidns pynat pystun3 | |
""" | |
import asyncio | |
import functools | |
import ipaddress | |
import logging | |
import sys | |
import time | |
import urllib.request | |
try: | |
import aiodns # pip install aiodns | |
except ImportError: | |
aiodns = None | |
try: | |
import pynat # pip install pynat | |
except ImportError: | |
pynat = None | |
try: | |
import stun # pip install pystun3 | |
except ImportError: | |
stun = None | |
logger = logging.getLogger(__name__) | |
def logged_duration(coro, timer=time.monotonic): | |
@functools.wraps(coro) | |
async def wrapper(*args, **kwargs): | |
start = timer() | |
try: | |
return await coro(*args, **kwargs) | |
finally: | |
logger.info( | |
"%5.2fms in %s", 1000 * (timer() - start), coro.__name__, | |
) | |
return wrapper | |
if aiodns: | |
@logged_duration | |
async def external_ipv4(): | |
"""Return public IPv4 address.""" | |
# OpenDNS name servers resolve magic domain to your external ip address | |
return await _external_ip_via_opendns( | |
nameservers=["208.67.222.222", "208.67.220.220"], query_type="A", | |
) | |
@logged_duration | |
async def external_ipv6(): | |
"""Return public IPv6 address.""" | |
return await _external_ip_via_opendns( | |
nameservers=["2620:119:35::35", "2620:119:53::53"], | |
query_type="AAAA", | |
) | |
async def _external_ip_via_opendns(*, nameservers, query_type): | |
"""OpenDNS *nameservers* resolve magic domain to your external ip address.""" | |
resolver = aiodns.DNSResolver() | |
resolver.nameservers = nameservers | |
magic_domain = "myip.opendns.com" | |
try: | |
host = (await resolver.query(magic_domain, query_type))[0].host | |
return ipaddress.ip_address(host) | |
finally: | |
resolver.cancel() | |
@logged_duration | |
async def external_ip_via_ifconfig(): | |
"""Return public ip via https://ifconfig.me/ip""" | |
loop = asyncio.get_running_loop() | |
return await loop.run_in_executor(None, _external_ip_via_ifconfig_blocking) | |
def _external_ip_via_ifconfig_blocking(): | |
with urllib.request.urlopen("https://ifconfig.me/ip", timeout=1) as r: | |
return r.read(1024).decode("ascii") | |
if pynat: | |
@logged_duration | |
async def external_ip_via_pynat(): | |
loop = asyncio.get_running_loop() | |
return await loop.run_in_executor(None, _external_ip_via_pynat_blocking) | |
def _external_ip_via_pynat_blocking(): | |
return pynat.get_ip_info()[1] | |
if stun: | |
@logged_duration | |
async def external_ip_via_pystun3(): | |
loop = asyncio.get_running_loop() | |
return await loop.run_in_executor( | |
None, _external_ip_via_pystun3_blocking | |
) | |
def _external_ip_via_pystun3_blocking(): | |
return stun.get_ip_info()[1] | |
@logged_duration | |
async def external_ip(): | |
"""Return public IP address.""" | |
# run all external_ip.*() corotines, return the first available result | |
for f in asyncio.as_completed( | |
[ | |
coro() | |
for name, coro in globals().items() | |
if name.startswith("external_ip") and name != "external_ip" | |
] | |
): | |
try: | |
return await f | |
except Exception as e: | |
last_error = e | |
raise last_error | |
async def main(): | |
level = logging.WARNING if "--quiet" in sys.argv else logging.INFO | |
logging.basicConfig( | |
level=level, format="%(asctime)s %(message)s", | |
) | |
print(await external_ip()) | |
if __name__ == "__main__": | |
asyncio.run(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment