Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial support for duckdns. fixes #42
- Loading branch information
1 parent
cac8e10
commit 57ee12f
Showing
6 changed files
with
199 additions
and
5 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,93 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import unittest | ||
from time import sleep | ||
from multiprocessing import Process | ||
|
||
from bottle import Bottle, run, response, request | ||
|
||
|
||
def nicupdate(): | ||
arg_hostname = request.query.domains | ||
arg_token = request.query.token | ||
arg_myip = request.query.ip | ||
assert len(arg_hostname) > 0 | ||
assert len(arg_token) > 0 | ||
assert len(arg_myip) > 0 | ||
response.content_type = 'text/plain; charset=utf-8' | ||
return str("good %s" % arg_myip) | ||
|
||
|
||
class DuckdnsApp(Bottle): | ||
|
||
""" | ||
A minimal http server that resembles an actual duckdns service | ||
""" | ||
|
||
def __init__(self, host='localhost', port=8000): | ||
super(DuckdnsApp, self).__init__() | ||
self.host = host | ||
self.port = port | ||
self.process = None | ||
self.route(path='/update', callback=nicupdate) | ||
|
||
def run(self): | ||
run(self, host=self.host, port=self.port, debug=False, quiet=True) | ||
|
||
def start(self): | ||
self.process = Process(target=self.run) | ||
self.process.start() | ||
# even though I have a super fast quad core cpu, this is not working | ||
# consistently if we don't sleep here! | ||
sleep(3.5) | ||
|
||
def stop(self): | ||
self.process.terminate() | ||
self.process = None | ||
# sleep(1) | ||
|
||
@property | ||
def url(self): | ||
return 'http://%s:%s' % (self.host, str(self.port)) | ||
|
||
|
||
class TestDuckdns2BottleServer(unittest.TestCase): | ||
|
||
def setUp(self): | ||
""" | ||
Start local server | ||
""" | ||
import random | ||
portnumber = random.randint(8000, 8900) | ||
self.server = DuckdnsApp('127.0.0.1', portnumber) | ||
self.url = "http://127.0.0.1:%i/update" % portnumber | ||
self.server.start() | ||
unittest.TestCase.setUp(self) | ||
|
||
def tearDown(self): | ||
""" | ||
Stop local server. | ||
""" | ||
self.server.stop() | ||
self.server = None | ||
unittest.TestCase.tearDown(self) | ||
|
||
def test_dyndns2(self): | ||
import dyndnsc.updater.duckdns as duckdns | ||
NAME = "duckdns" | ||
theip = "127.0.0.1" | ||
options = {"hostname": "duckdns.example.com", | ||
"token": "dummy", | ||
"url": self.url | ||
} | ||
self.assertEqual( | ||
NAME, duckdns.UpdateProtocolDuckdns.configuration_key()) | ||
updater = duckdns.UpdateProtocolDuckdns(**options) | ||
self.assertEqual(str, type(updater.url())) | ||
self.assertEqual(self.url, updater.url()) | ||
res = updater.update(theip) | ||
self.assertEqual(theip, res) | ||
|
||
|
||
if __name__ == '__main__': | ||
DuckdnsApp('localhost', 8000).run() |
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,88 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
"""Module containing the logic for updating DNS records using the duckdns protocol. | ||
From the duckdns.org website: | ||
https://{DOMAIN}/update?domains={DOMAINLIST}&token={TOKEN}&ip={IP} | ||
where: | ||
DOMAIN the service domain | ||
DOMAINLIST is either a single domain or a comma separated list of domains | ||
TOKEN is the API token for authentication/authorization | ||
IP is either the IP or blank for auto-detection | ||
""" | ||
|
||
from logging import getLogger | ||
|
||
from .base import UpdateProtocol | ||
from ..common import constants | ||
|
||
import requests | ||
|
||
log = getLogger(__name__) | ||
|
||
|
||
class UpdateProtocolDuckdns(UpdateProtocol): | ||
|
||
"""Updater for services compatible with the duckdns protocol.""" | ||
|
||
def __init__(self, hostname, token, url, *args, **kwargs): | ||
""" | ||
Initializer. | ||
:param hostname: the fully qualified hostname to be managed | ||
:param token: the token for authentication | ||
:param url: the API URL for updating the DNS entry | ||
""" | ||
self.hostname = hostname | ||
self.token = token | ||
self._updateurl = url | ||
|
||
super(UpdateProtocolDuckdns, self).__init__() | ||
|
||
@staticmethod | ||
def configuration_key(): | ||
"""Human readable string identifying this update protocol.""" | ||
return "duckdns" | ||
|
||
def update(self, ip): | ||
self.theip = ip | ||
return self.protocol() | ||
|
||
def protocol(self): | ||
timeout = 60 | ||
log.debug("Updating '%s' to '%s' at service '%s'", self.hostname, self.theip, self.url()) | ||
params = {'domains': self.hostname, 'token': self.token} | ||
if self.theip is None: | ||
params['ip'] = "" | ||
else: | ||
params['ip'] = self.theip | ||
try: | ||
r = requests.get(self.url(), params=params, headers=constants.REQUEST_HEADERS_DEFAULT, | ||
timeout=timeout) | ||
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as exc: | ||
log.warning("an error occurred while updating IP at '%s'", | ||
self.url(), exc_info=exc) | ||
return False | ||
else: | ||
r.close() | ||
log.debug("status %i, %s", r.status_code, r.text) | ||
if r.status_code == 200: | ||
if r.text.startswith("good "): | ||
return self.theip | ||
elif r.text.startswith('nochg'): | ||
return self.theip | ||
elif r.text == 'nohost': | ||
return 'nohost' | ||
elif r.text == 'abuse': | ||
return 'abuse' | ||
elif r.text == '911': | ||
return '911' | ||
elif r.text == 'notfqdn': | ||
return 'notfqdn' | ||
else: | ||
return r.text | ||
else: | ||
return 'invalid http status code: %s' % r.status_code |