Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement WAN IP detection using DNS queries
- Loading branch information
1 parent
5d89b60
commit 2c09e49
Showing
6 changed files
with
158 additions
and
9 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
"""Module containing logic for DNS WAN IP detection. | ||
See also https://www.cyberciti.biz/faq/how-to-find-my-public-ip-address-from-command-line-on-a-linux/ | ||
""" | ||
from __future__ import absolute_import | ||
|
||
import socket | ||
import logging | ||
|
||
import dns.resolver | ||
|
||
from .base import IPDetector, AF_INET, AF_INET6 | ||
|
||
LOG = logging.getLogger(__name__) | ||
|
||
|
||
def find_ip(family=AF_INET, flavour="opendns"): | ||
"""Find the publicly visible IP address of the current system. | ||
This uses public DNS infrastructure that implement a special DNS "hack" to | ||
return the IP address of the requester rather than some other address. | ||
:param family: address family, optional, default AF_INET (ipv4) | ||
:param flavour: selector for public infrastructure provider, optional | ||
""" | ||
flavours = { | ||
"opendns": { | ||
AF_INET: { | ||
"@": ("resolver1.opendns.com", "resolver2.opendns.com"), | ||
"qname": "myip.opendns.com", | ||
"rdtype": "A", | ||
}, | ||
AF_INET6: { | ||
"@": ("resolver1.ipv6-sandbox.opendns.com", "resolver2.ipv6-sandbox.opendns.com"), | ||
"qname": "myip.opendns.com", | ||
"rdtype": "AAAA", | ||
}, | ||
}, | ||
} | ||
|
||
flavour = flavours["opendns"] | ||
resolver = dns.resolver.Resolver() | ||
resolver.nameservers = [socket.gethostbyname(h) for h in flavour[family]["@"]] | ||
|
||
answers = resolver.query(qname=flavour[family]["qname"], rdtype=flavour[family]["rdtype"]) | ||
for rdata in answers: | ||
return rdata.address | ||
return None | ||
|
||
|
||
class IPDetector_DnsWanIp(IPDetector): | ||
"""Class to discover the internet visible IP address using publicly available DNS infrastructure.""" | ||
|
||
def __init__(self, family=None, *args, **kwargs): | ||
""" | ||
Initializer. | ||
:param family: IP address family (default: '' (ANY), also possible: 'INET', 'INET6') | ||
""" | ||
if family is None: | ||
family = AF_INET | ||
super(IPDetector_DnsWanIp, self).__init__(*args, family=family, **kwargs) | ||
|
||
@staticmethod | ||
def names(): | ||
"""Return a list of string names identifying this class/service.""" | ||
return ("dnswanip",) | ||
|
||
def can_detect_offline(self): | ||
"""Return false, as this detector generates dns traffic. | ||
:return: False | ||
""" | ||
return False | ||
|
||
def detect(self): | ||
""" | ||
Detect the WAN IP of the current process through DNS. | ||
Depending on the 'family' option, either ipv4 or ipv6 resolution is | ||
carried out. | ||
:return: ip address | ||
""" | ||
theip = find_ip(family=self.opts_family) | ||
self.set_current_value(theip) | ||
return theip |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
"""Tests for detectors.""" | ||
|
||
|
||
import unittest | ||
|
||
import pytest | ||
|
||
from dyndnsc.common.six import string_types | ||
from dyndnsc.common.six import ipaddress | ||
from dyndnsc.detector.base import AF_INET, AF_INET6 | ||
from dyndnsc.detector.dnswanip import IPDetector_DnsWanIp | ||
|
||
HAVE_IPV6 = True | ||
try: | ||
import socket | ||
socket.socket(socket.AF_INET6, socket.SOCK_DGRAM).connect(("ipv6.google.com", 0)) | ||
except (OSError, socket.error, socket.gaierror): | ||
HAVE_IPV6 = False | ||
|
||
|
||
class TestIndividualDetectors(unittest.TestCase): | ||
"""Test cases for detectors.""" | ||
|
||
def test_dnswanip_detector_class(self): | ||
"""Run basic tests for IPDetector_DnsWanIp.""" | ||
self.assertTrue("dnswanip" in IPDetector_DnsWanIp.names()) | ||
detector = IPDetector_DnsWanIp() | ||
self.assertFalse(detector.can_detect_offline()) | ||
self.assertEqual(None, detector.get_current_value()) | ||
# default family should be ipv4: | ||
detector = IPDetector_DnsWanIp(family=None) | ||
self.assertEqual(AF_INET, detector.af()) | ||
detector = IPDetector_DnsWanIp(family=AF_INET) | ||
self.assertEqual(AF_INET, detector.af()) | ||
detector = IPDetector_DnsWanIp(family=AF_INET6) | ||
self.assertEqual(AF_INET6, detector.af()) | ||
|
||
def test_dnswanip_detector_ipv4(self): | ||
"""Run ipv4 tests for IPDetector_DnsWanIp.""" | ||
detector = IPDetector_DnsWanIp(family=AF_INET) | ||
result = detector.detect() | ||
self.assertTrue(isinstance(result, (type(None),) + string_types), type(result)) | ||
# ensure the result is in fact an IP address: | ||
self.assertNotEqual(ipaddress(result), None) | ||
self.assertEqual(detector.get_current_value(), result) | ||
|
||
@pytest.mark.skipif(not HAVE_IPV6, reason="requires ipv6 connectivity") | ||
def test_dnswanip_detector_ipv6(self): | ||
"""Run ipv6 tests for IPDetector_DnsWanIp.""" | ||
if HAVE_IPV6: # allow running test in IDE without pytest support | ||
detector = IPDetector_DnsWanIp(family=AF_INET6) | ||
result = detector.detect() | ||
self.assertTrue(isinstance(result, (type(None),) + string_types), type(result)) | ||
# ensure the result is in fact an IP address: | ||
self.assertNotEqual(ipaddress(result), None) | ||
self.assertEqual(detector.get_current_value(), result) |
This file contains 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