Refactor source and add SHUTDOWN_TIMEOUT constant
This commit is contained in:
+108
-112
@@ -26,6 +26,7 @@ from .cleanup import ExpiredRecordsCleanupThread, RateLimitCleanupThread
|
|||||||
from .dns import detect_ip_type
|
from .dns import detect_ip_type
|
||||||
from .logging import clear_txn_id, set_txn_id
|
from .logging import clear_txn_id, set_txn_id
|
||||||
from .models import (
|
from .models import (
|
||||||
|
close_database,
|
||||||
DatabaseError,
|
DatabaseError,
|
||||||
DoesNotExist,
|
DoesNotExist,
|
||||||
EncodingError,
|
EncodingError,
|
||||||
@@ -38,6 +39,9 @@ from concurrent.futures import ThreadPoolExecutor
|
|||||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
|
# Graceful shutdown timeout (seconds)
|
||||||
|
SHUTDOWN_TIMEOUT = 5
|
||||||
|
|
||||||
|
|
||||||
def extract_param(params, aliases):
|
def extract_param(params, aliases):
|
||||||
"""Extract first matching param from query params."""
|
"""Extract first matching param from query params."""
|
||||||
@@ -240,6 +244,99 @@ class DDNSRequestHandler(BaseHTTPRequestHandler):
|
|||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
def _parse_ip_params(self, params, endpoint, client_ip, username,
|
||||||
|
hostname_param):
|
||||||
|
"""Parse and validate IP address parameters."""
|
||||||
|
ipv4 = None
|
||||||
|
ipv6 = None
|
||||||
|
|
||||||
|
# Process myip parameter
|
||||||
|
myip = extract_param(params, endpoint["params"]["ipv4"])
|
||||||
|
if myip:
|
||||||
|
try:
|
||||||
|
rtype, myip = detect_ip_type(myip)
|
||||||
|
if rtype == "A":
|
||||||
|
ipv4 = myip
|
||||||
|
else:
|
||||||
|
ipv6 = myip
|
||||||
|
except ValueError:
|
||||||
|
raise DDNSClientError(
|
||||||
|
"Bad IP address", 400, STATUS_BADIP,
|
||||||
|
client=client_ip, username=username,
|
||||||
|
hostname=hostname_param, ip=myip
|
||||||
|
)
|
||||||
|
|
||||||
|
# Process myip6 parameter
|
||||||
|
myip6 = extract_param(params, endpoint["params"]["ipv6"])
|
||||||
|
if myip6:
|
||||||
|
try:
|
||||||
|
rtype, myip6 = detect_ip_type(myip6)
|
||||||
|
if rtype != "AAAA":
|
||||||
|
raise ValueError
|
||||||
|
ipv6 = myip6
|
||||||
|
except ValueError:
|
||||||
|
raise DDNSClientError(
|
||||||
|
"Bad IPv6 address", 400, STATUS_BADIP,
|
||||||
|
client=client_ip, username=username,
|
||||||
|
hostname=hostname_param, ipv6=myip6
|
||||||
|
)
|
||||||
|
|
||||||
|
# Auto-detect from client IP if no params
|
||||||
|
if ipv4 is None and ipv6 is None:
|
||||||
|
rtype, ip = detect_ip_type(client_ip)
|
||||||
|
if rtype == "A":
|
||||||
|
ipv4 = ip
|
||||||
|
else:
|
||||||
|
ipv6 = ip
|
||||||
|
|
||||||
|
return ipv4, ipv6
|
||||||
|
|
||||||
|
def _parse_expiry_ttl(self, params, endpoint, client_ip, username,
|
||||||
|
hostname_param):
|
||||||
|
"""Parse and validate expiry_ttl parameter."""
|
||||||
|
expiry_ttl_param = extract_param(params, endpoint["params"]["expiry_ttl"])
|
||||||
|
if not expiry_ttl_param:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
expiry_ttl = int(expiry_ttl_param)
|
||||||
|
if expiry_ttl < 0:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError:
|
||||||
|
raise DDNSClientError(
|
||||||
|
"Invalid expiry_ttl", 400, STATUS_NOHOST,
|
||||||
|
client=client_ip, username=username,
|
||||||
|
hostname=hostname_param, expiry_ttl=expiry_ttl_param
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate bounds
|
||||||
|
defaults = self.app.config["defaults"]
|
||||||
|
|
||||||
|
if expiry_ttl == 0:
|
||||||
|
if not defaults["expiry_ttl_allow_zero"]:
|
||||||
|
raise DDNSClientError(
|
||||||
|
"Zero expiry_ttl not allowed", 400, STATUS_NOHOST,
|
||||||
|
client=client_ip, username=username,
|
||||||
|
hostname=hostname_param, expiry_ttl=expiry_ttl
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ttl_min = defaults["expiry_ttl_min"]
|
||||||
|
if ttl_min is not None and expiry_ttl < ttl_min:
|
||||||
|
raise DDNSClientError(
|
||||||
|
"expiry_ttl below minimum", 400, STATUS_NOHOST,
|
||||||
|
client=client_ip, username=username,
|
||||||
|
hostname=hostname_param, expiry_ttl=expiry_ttl, min=ttl_min
|
||||||
|
)
|
||||||
|
ttl_max = defaults["expiry_ttl_max"]
|
||||||
|
if ttl_max is not None and expiry_ttl > ttl_max:
|
||||||
|
raise DDNSClientError(
|
||||||
|
"expiry_ttl above maximum", 400, STATUS_NOHOST,
|
||||||
|
client=client_ip, username=username,
|
||||||
|
hostname=hostname_param, expiry_ttl=expiry_ttl, max=ttl_max
|
||||||
|
)
|
||||||
|
|
||||||
|
return expiry_ttl
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
"""Handle GET requests."""
|
"""Handle GET requests."""
|
||||||
set_txn_id()
|
set_txn_id()
|
||||||
@@ -306,7 +403,7 @@ class DDNSRequestHandler(BaseHTTPRequestHandler):
|
|||||||
"Auth failed",
|
"Auth failed",
|
||||||
401,
|
401,
|
||||||
STATUS_BADAUTH,
|
STATUS_BADAUTH,
|
||||||
client_ip
|
client_ip=client_ip
|
||||||
)
|
)
|
||||||
|
|
||||||
# Process hostname parameter
|
# Process hostname parameter
|
||||||
@@ -320,55 +417,9 @@ class DDNSRequestHandler(BaseHTTPRequestHandler):
|
|||||||
username=username
|
username=username
|
||||||
)
|
)
|
||||||
|
|
||||||
# Process myip parameter
|
# Parse IP parameters
|
||||||
ipv4 = None
|
ipv4, ipv6 = self._parse_ip_params(
|
||||||
myip = extract_param(params, endpoint["params"]["ipv4"])
|
params, endpoint, client_ip, username, hostname_param)
|
||||||
if myip:
|
|
||||||
try:
|
|
||||||
rtype, myip = detect_ip_type(myip)
|
|
||||||
if rtype == "A":
|
|
||||||
ipv4 = myip
|
|
||||||
else:
|
|
||||||
ipv6 = myip
|
|
||||||
except ValueError:
|
|
||||||
raise DDNSClientError(
|
|
||||||
"Bad IP address",
|
|
||||||
400,
|
|
||||||
STATUS_BADIP,
|
|
||||||
client=client_ip,
|
|
||||||
username=username,
|
|
||||||
hostname=hostname_param,
|
|
||||||
ip=myip
|
|
||||||
)
|
|
||||||
|
|
||||||
# Process myip6 parameter
|
|
||||||
ipv6 = None
|
|
||||||
myip6 = extract_param(params, endpoint["params"]["ipv6"])
|
|
||||||
if myip6:
|
|
||||||
try:
|
|
||||||
rtype, myip6 = detect_ip_type(myip6)
|
|
||||||
if rtype == "AAAA":
|
|
||||||
ipv6 = myip6
|
|
||||||
else:
|
|
||||||
raise ValueError
|
|
||||||
except ValueError:
|
|
||||||
raise DDNSClientError(
|
|
||||||
"Bad IPv6 address",
|
|
||||||
400,
|
|
||||||
STATUS_BADIP,
|
|
||||||
client=client_ip,
|
|
||||||
username=username,
|
|
||||||
hostname=hostname_param,
|
|
||||||
ipv6=myip6
|
|
||||||
)
|
|
||||||
|
|
||||||
# Auto-detect from client IP if no params
|
|
||||||
if ipv4 is None and ipv6 is None:
|
|
||||||
rtype, ip = detect_ip_type(client_ip)
|
|
||||||
if rtype == "A":
|
|
||||||
ipv4 = ip
|
|
||||||
else:
|
|
||||||
ipv6 = ip
|
|
||||||
|
|
||||||
# Process notify_change parameter
|
# Process notify_change parameter
|
||||||
notify_change = extract_param(
|
notify_change = extract_param(
|
||||||
@@ -377,65 +428,9 @@ class DDNSRequestHandler(BaseHTTPRequestHandler):
|
|||||||
["1", "y", "yes", "on", "true"]
|
["1", "y", "yes", "on", "true"]
|
||||||
if notify_change else False)
|
if notify_change else False)
|
||||||
|
|
||||||
# Process expiry_ttl parameter
|
# Parse expiry_ttl parameter
|
||||||
expiry_ttl_param = extract_param(
|
expiry_ttl = self._parse_expiry_ttl(
|
||||||
params, endpoint["params"]["expiry_ttl"])
|
params, endpoint, client_ip, username, hostname_param)
|
||||||
expiry_ttl = None
|
|
||||||
if expiry_ttl_param:
|
|
||||||
try:
|
|
||||||
expiry_ttl = int(expiry_ttl_param)
|
|
||||||
if expiry_ttl < 0:
|
|
||||||
raise ValueError
|
|
||||||
except ValueError:
|
|
||||||
raise DDNSClientError(
|
|
||||||
"Invalid expiry_ttl",
|
|
||||||
400,
|
|
||||||
STATUS_NOHOST,
|
|
||||||
client=client_ip,
|
|
||||||
username=username,
|
|
||||||
hostname=hostname_param,
|
|
||||||
expiry_ttl=expiry_ttl_param
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate bounds
|
|
||||||
defaults = self.app.config["defaults"]
|
|
||||||
|
|
||||||
if expiry_ttl == 0:
|
|
||||||
if not defaults["expiry_ttl_allow_zero"]:
|
|
||||||
raise DDNSClientError(
|
|
||||||
"Zero expiry_ttl not allowed",
|
|
||||||
400,
|
|
||||||
STATUS_NOHOST,
|
|
||||||
client=client_ip,
|
|
||||||
username=username,
|
|
||||||
hostname=hostname_param,
|
|
||||||
expiry_ttl=expiry_ttl
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
ttl_min = defaults["expiry_ttl_min"]
|
|
||||||
if ttl_min is not None and expiry_ttl < ttl_min:
|
|
||||||
raise DDNSClientError(
|
|
||||||
"expiry_ttl below minimum",
|
|
||||||
400,
|
|
||||||
STATUS_NOHOST,
|
|
||||||
client=client_ip,
|
|
||||||
username=username,
|
|
||||||
hostname=hostname_param,
|
|
||||||
expiry_ttl=expiry_ttl,
|
|
||||||
min=ttl_min
|
|
||||||
)
|
|
||||||
ttl_max = defaults["expiry_ttl_max"]
|
|
||||||
if ttl_max is not None and expiry_ttl > ttl_max:
|
|
||||||
raise DDNSClientError(
|
|
||||||
"expiry_ttl above maximum",
|
|
||||||
400,
|
|
||||||
STATUS_NOHOST,
|
|
||||||
client=client_ip,
|
|
||||||
username=username,
|
|
||||||
hostname=hostname_param,
|
|
||||||
expiry_ttl=expiry_ttl,
|
|
||||||
max=ttl_max
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate credentials
|
# Validate credentials
|
||||||
user = self._authenticate(client_ip, username, password)
|
user = self._authenticate(client_ip, username, password)
|
||||||
@@ -753,12 +748,13 @@ def run_daemon(app):
|
|||||||
server.handle_request()
|
server.handle_request()
|
||||||
|
|
||||||
# Graceful shutdown - wait for active requests
|
# Graceful shutdown - wait for active requests
|
||||||
server.wait_for_requests(5)
|
server.wait_for_requests(SHUTDOWN_TIMEOUT)
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
expired_cleanup_thread.stop()
|
expired_cleanup_thread.stop()
|
||||||
ratelimit_cleanup_thread.stop()
|
ratelimit_cleanup_thread.stop()
|
||||||
expired_cleanup_thread.join(timeout=5)
|
expired_cleanup_thread.join(timeout=SHUTDOWN_TIMEOUT)
|
||||||
ratelimit_cleanup_thread.join(timeout=5)
|
ratelimit_cleanup_thread.join(timeout=SHUTDOWN_TIMEOUT)
|
||||||
server.server_close()
|
server.server_close()
|
||||||
|
close_database()
|
||||||
logging.info("Daemon stopped")
|
logging.info("Daemon stopped")
|
||||||
|
|||||||
Reference in New Issue
Block a user