Skip to content

Commit

Permalink
Added simple DNS server
Browse files Browse the repository at this point in the history
  • Loading branch information
oldium committed Jun 2, 2018
1 parent 8efae47 commit 71aa34c
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 39 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ git clone -q https://github.com/oldium/subregsim.git
cd subregsim
```

Install dependencies:
Install dependencies (currently PySimpleSOAP needs a patch):

```
pip install -r requirements.txt
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
future
git+https://github.com/oldium/pysimplesoap.git@fix-typeerror#egg=PySimpleSOAP
configargparse
dnslib
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ def find_version(*file_paths):
install_requires=['future',
'PySimpleSOAP',
'configargparse'],
extras_require={},
extras_require={
'dns': ['dnslib'],
},

entry_points={
'console_scripts': [
Expand Down
16 changes: 14 additions & 2 deletions subregsim.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ domain = example.com

# Host name or IP address to listen on (use 127.0.0.1 for testing on localhost,
# or 0.0.0.0 to accept any address for testing with Docker)
host = 0.0.0.0
#host = 127.0.0.1

# Port to listen on when in HTTP mode
#port = 80
Expand All @@ -25,5 +25,17 @@ host = 0.0.0.0
# SSL server certificate in PEM format (may contain private key)
#ssl-certificate = /config/server-certificate.crt

# SSL server private key in PEM format (not necessary if present in the certificate file)
# SSL server private key in PEM format (not necessary if present in the
# certificate file)
#ssl-private-key = /config/server-certificate.key

# If you want to enable simple DNS server serving added records, uncomment the
# following line
#dns = true

# DNS server port to listen on
#dns-port = 53

# DNS server host name or IP address to listen on (use 127.0.0.1 for testing on
# localhost, or 0.0.0.0 to accept any address for testing with Docker)
#dns-host = 127.0.0.1
62 changes: 52 additions & 10 deletions subregsim/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

from __future__ import (absolute_import, print_function)

__version__ = "0.1"
__version__ = "0.2"

import configargparse
import logging
import signal
import threading
import time

from .api import (Api, ApiDispatcher, ApiHttpServer)

Expand All @@ -22,6 +23,12 @@
except:
has_ssl = False

try:
from . import dns
has_dns = True
except:
has_dns = False

def parse_command_line():
parser = configargparse.ArgumentParser(description="Subreg.cz API simulator suitable for Python lexicon module.")
required_group = parser.add_argument_group("required arguments")
Expand All @@ -31,9 +38,11 @@ def parse_command_line():

optional_group = parser.add_argument_group("optional arguments")
optional_group.add_argument("-c", "--config", metavar="FILE", is_config_file=True, help="configuration file for all options (can be specified only on command-line)")
optional_group.add_argument("--host", default="localhost", env_var="SUBREGSIM_HOST", help="server listening host name or IP address (defaults to localhost)")
optional_group.add_argument("--port", type=int, default=80, env_var="SUBREGSIM_PORT", help="server listening port (defaults to 80)")
optional_group.add_argument("--url", default="http://localhost:8008/", env_var="SUBREGSIM_URL", help="API root URL for WSDL generation (defaults to http://localhost:8008/)")

web_group = parser.add_argument_group("optional server arguments")
web_group.add_argument("--host", default="localhost", env_var="SUBREGSIM_HOST", help="server listening host name or IP address (defaults to localhost)")
web_group.add_argument("--port", type=int, default=80, env_var="SUBREGSIM_PORT", help="server listening port (defaults to 80)")
web_group.add_argument("--url", default="http://localhost:8008/", env_var="SUBREGSIM_URL", help="API root URL for WSDL generation (defaults to http://localhost:8008/)")

if has_ssl:
ssl_group = parser.add_argument_group("optional SSL arguments")
Expand All @@ -42,16 +51,23 @@ def parse_command_line():
ssl_group.add_argument("--ssl-certificate", dest="ssl_certificate", metavar="PEM-FILE", default=None, env_var="SUBREGSIM_SSL_CERTIFICATE", help="specifies server certificate")
ssl_group.add_argument("--ssl-private-key", dest="ssl_private_key", metavar="PEM-FILE", default=None, env_var="SUBREGSIM_SSL_PRIVATE_KEY", help="specifies server privatey key (not necessary if private key is part of certificate file)")

if has_dns:
dns_group = parser.add_argument_group("optional DNS server arguments")
dns_group.add_argument("--dns", dest="dns", action="store_true", default=False, env_var="SUBREGSIM_DNS", help="enables DNS server")
dns_group.add_argument("--dns-host", dest="dns_host", default="localhost", env_var="SUBREGSIM_DNS_HOST", help="DNS server listening host name or IP address (defaults to localhost)")
dns_group.add_argument("--dns-port", dest="dns_port", type=int, default=53, metavar="PORT", env_var="SUBREGSIM_DNS_PORT", help="DNS server listening port (defaults to 53)")

parsed = parser.parse_args()

if has_ssl and parsed.ssl and ('ssl_certificate' not in parsed or not parsed.ssl_certificate):
parser.error("--ssl requires --ssl-certificate")

return parsed

terminated = False

class TerminationHandler(object):
def __init__(self, httpd):
self.httpd = httpd
def __init__(self):
signal.signal(signal.SIGINT, self.terminate)
signal.signal(signal.SIGTERM, self.terminate)

Expand All @@ -62,7 +78,9 @@ def terminate(self, signum, frame):
log.info("Shutting down due to termination request")
else:
log.info("Shutting down due to interrupt request")
self.httpd.shutdown()

global terminated
terminated = True

def main():

Expand All @@ -73,8 +91,10 @@ def main():
dispatcher.register_api(api)

use_ssl = has_ssl and arguments.ssl
use_dns = has_dns and arguments.dns

httpd = ApiHttpServer((arguments.host, arguments.port), use_ssl, dispatcher)
stop_servers = [ httpd ]

if use_ssl:
log.info("Starting HTTPS server to listen on {}:{}...".format(arguments.host, arguments.ssl_port))
Expand All @@ -85,13 +105,35 @@ def main():
else:
log.info("Starting HTTP server to listen on {}:{}...".format(arguments.host, arguments.port))

TerminationHandler(httpd)
if use_dns:
log.info("Starting DNS server to listen on {}:{}...".format(arguments.dns_host, arguments.dns_port))
api_resolver = dns.ApiDnsResolver(api)
dns_udp = dns.ApiDns(api_resolver, arguments.dns_host, arguments.dns_port, False)
dns_tcp = dns.ApiDns(api_resolver, arguments.dns_host, arguments.dns_port, True)

stop_servers.append(dns_udp)
stop_servers.append(dns_tcp)

dns_udp.start_thread()
dns_tcp.start_thread()

TerminationHandler()

httpd_thread = threading.Thread(target=httpd.run, name="SOAP Server")
httpd_thread.start()

while httpd_thread.is_alive():
httpd_thread.join(timeout=0.5)
while not terminated:
time.sleep(0.5)

httpd.shutdown()
httpd_thread.join()

if use_dns:
dns_udp.stop()
dns_tcp.stop()

dns_udp.thread.join()
dns_tcp.thread.join()

if __name__ == '__main__':
try:
Expand Down
89 changes: 64 additions & 25 deletions subregsim/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging
import random
import string
import threading
import pysimplesoap.server as soapserver
from http.server import HTTPServer

Expand All @@ -19,6 +20,37 @@ def __init__(self, username, password, domain):
self.domain = domain
self.db = []
self.ssid = None
self.sn = 1
self.db_lock = threading.Lock()

def toZone(self):
zone = []
zone.append("$ORIGIN .")
zone.append("$TTL 1800")

with self.db_lock:
zone.append("{} IN SOA ns.example.com admin.example.com ( {} 86400 900 1209600 1800 )".format(
self.domain,
self.sn,
))

for rr in self.db:
name = rr["name"]
type = rr["type"]
content = rr["content"] if "content" in rr else ""
prio = rr["prio"]
ttl = rr["ttl"] if rr["ttl"] != 1800 else ""

if type != "MX":
prio = ""

if type == "TXT":
content = '"{}"'.format(content.replace('"', r'\"'))

zone.append("{} {} IN {} {} {}".format(
name, ttl, type, prio, content))

return "\n".join(zone)

def login(self, login, password):
log.info("Login: {}/{}".format(login, password))
Expand Down Expand Up @@ -211,7 +243,9 @@ def add_dns_record(self, ssid, domain, record):
if 'prio' not in new_record:
new_record['prio'] = 0

self.db.append(new_record)
with self.db_lock:
self.db.append(new_record)
self.sn += 1

return {
"response": {
Expand Down Expand Up @@ -276,22 +310,24 @@ def modify_dns_record(self, ssid, domain, record):
}
}

found_items = [item for item in self.db if item["id"] == record["id"]]
if len(found_items) == 0:
return {
"response": {
"status": "error",
"error": {
"errormsg": "Record does not exist",
"errorcode": {
"major": 524,
"minor": 1003
with self.db_lock:
found_items = [item for item in self.db if item["id"] == record["id"]]
if len(found_items) == 0:
return {
"response": {
"status": "error",
"error": {
"errormsg": "Record does not exist",
"errorcode": {
"major": 524,
"minor": 1003
}
}
}
}
}

found_items[0].update(record)
found_items[0].update(record)
self.sn += 1

return {
"response": {
Expand Down Expand Up @@ -356,21 +392,24 @@ def delete_dns_record(self, ssid, domain, record):
}
}

before_length = len(self.db)
self.db = [item for item in self.db if item["id"] != record["id"]]
if before_length == len(self.db):
return {
"response": {
"status": "error",
"error": {
"errormsg": "Record does not exist",
"errorcode": {
"major": 524,
"minor": 1003
with self.db_lock:
before_length = len(self.db)
self.db = [item for item in self.db if item["id"] != record["id"]]
if before_length == len(self.db):
return {
"response": {
"status": "error",
"error": {
"errormsg": "Record does not exist",
"errorcode": {
"major": 524,
"minor": 1003
}
}
}
}
}

self.sn += 1

return {
"response": {
Expand Down
23 changes: 23 additions & 0 deletions subregsim/dns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'''
Subreg.cz API simulator suitable for Python lexicon module.
'''

from __future__ import (absolute_import, print_function)
import logging
import dnslib.server
import dnslib.zoneresolver

log = logging.getLogger(__name__)

class ApiDnsResolver(dnslib.server.BaseResolver):
def __init__(self, api):
dnslib.server.BaseResolver.__init__(self)
self.api = api

def resolve(self, request, handler):
resolver = dnslib.zoneresolver.ZoneResolver(self.api.toZone(), True)
return resolver.resolve(request, handler)

class ApiDns(dnslib.server.DNSServer):
def __init__(self, resolver, address, port, tcp=False):
dnslib.server.DNSServer.__init__(self, resolver, address, port, tcp)

0 comments on commit 71aa34c

Please sign in to comment.