Skip to content

Commit 71aa34c

Browse files
committed
Added simple DNS server
1 parent 8efae47 commit 71aa34c

File tree

7 files changed

+158
-39
lines changed

7 files changed

+158
-39
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ git clone -q https://github.com/oldium/subregsim.git
1717
cd subregsim
1818
```
1919

20-
Install dependencies:
20+
Install dependencies (currently PySimpleSOAP needs a patch):
2121

2222
```
2323
pip install -r requirements.txt

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
future
44
git+https://github.com/oldium/pysimplesoap.git@fix-typeerror#egg=PySimpleSOAP
55
configargparse
6+
dnslib

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ def find_version(*file_paths):
6666
install_requires=['future',
6767
'PySimpleSOAP',
6868
'configargparse'],
69-
extras_require={},
69+
extras_require={
70+
'dns': ['dnslib'],
71+
},
7072

7173
entry_points={
7274
'console_scripts': [

subregsim.conf.sample

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ domain = example.com
1111

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

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

28-
# SSL server private key in PEM format (not necessary if present in the certificate file)
28+
# SSL server private key in PEM format (not necessary if present in the
29+
# certificate file)
2930
#ssl-private-key = /config/server-certificate.key
31+
32+
# If you want to enable simple DNS server serving added records, uncomment the
33+
# following line
34+
#dns = true
35+
36+
# DNS server port to listen on
37+
#dns-port = 53
38+
39+
# DNS server host name or IP address to listen on (use 127.0.0.1 for testing on
40+
# localhost, or 0.0.0.0 to accept any address for testing with Docker)
41+
#dns-host = 127.0.0.1

subregsim/__main__.py

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44

55
from __future__ import (absolute_import, print_function)
66

7-
__version__ = "0.1"
7+
__version__ = "0.2"
88

99
import configargparse
1010
import logging
1111
import signal
1212
import threading
13+
import time
1314

1415
from .api import (Api, ApiDispatcher, ApiHttpServer)
1516

@@ -22,6 +23,12 @@
2223
except:
2324
has_ssl = False
2425

26+
try:
27+
from . import dns
28+
has_dns = True
29+
except:
30+
has_dns = False
31+
2532
def parse_command_line():
2633
parser = configargparse.ArgumentParser(description="Subreg.cz API simulator suitable for Python lexicon module.")
2734
required_group = parser.add_argument_group("required arguments")
@@ -31,9 +38,11 @@ def parse_command_line():
3138

3239
optional_group = parser.add_argument_group("optional arguments")
3340
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)")
34-
optional_group.add_argument("--host", default="localhost", env_var="SUBREGSIM_HOST", help="server listening host name or IP address (defaults to localhost)")
35-
optional_group.add_argument("--port", type=int, default=80, env_var="SUBREGSIM_PORT", help="server listening port (defaults to 80)")
36-
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/)")
41+
42+
web_group = parser.add_argument_group("optional server arguments")
43+
web_group.add_argument("--host", default="localhost", env_var="SUBREGSIM_HOST", help="server listening host name or IP address (defaults to localhost)")
44+
web_group.add_argument("--port", type=int, default=80, env_var="SUBREGSIM_PORT", help="server listening port (defaults to 80)")
45+
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/)")
3746

3847
if has_ssl:
3948
ssl_group = parser.add_argument_group("optional SSL arguments")
@@ -42,16 +51,23 @@ def parse_command_line():
4251
ssl_group.add_argument("--ssl-certificate", dest="ssl_certificate", metavar="PEM-FILE", default=None, env_var="SUBREGSIM_SSL_CERTIFICATE", help="specifies server certificate")
4352
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)")
4453

54+
if has_dns:
55+
dns_group = parser.add_argument_group("optional DNS server arguments")
56+
dns_group.add_argument("--dns", dest="dns", action="store_true", default=False, env_var="SUBREGSIM_DNS", help="enables DNS server")
57+
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)")
58+
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)")
59+
4560
parsed = parser.parse_args()
4661

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

5065
return parsed
5166

67+
terminated = False
68+
5269
class TerminationHandler(object):
53-
def __init__(self, httpd):
54-
self.httpd = httpd
70+
def __init__(self):
5571
signal.signal(signal.SIGINT, self.terminate)
5672
signal.signal(signal.SIGTERM, self.terminate)
5773

@@ -62,7 +78,9 @@ def terminate(self, signum, frame):
6278
log.info("Shutting down due to termination request")
6379
else:
6480
log.info("Shutting down due to interrupt request")
65-
self.httpd.shutdown()
81+
82+
global terminated
83+
terminated = True
6684

6785
def main():
6886

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

7593
use_ssl = has_ssl and arguments.ssl
94+
use_dns = has_dns and arguments.dns
7695

7796
httpd = ApiHttpServer((arguments.host, arguments.port), use_ssl, dispatcher)
97+
stop_servers = [ httpd ]
7898

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

88-
TerminationHandler(httpd)
108+
if use_dns:
109+
log.info("Starting DNS server to listen on {}:{}...".format(arguments.dns_host, arguments.dns_port))
110+
api_resolver = dns.ApiDnsResolver(api)
111+
dns_udp = dns.ApiDns(api_resolver, arguments.dns_host, arguments.dns_port, False)
112+
dns_tcp = dns.ApiDns(api_resolver, arguments.dns_host, arguments.dns_port, True)
113+
114+
stop_servers.append(dns_udp)
115+
stop_servers.append(dns_tcp)
116+
117+
dns_udp.start_thread()
118+
dns_tcp.start_thread()
119+
120+
TerminationHandler()
89121

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

93-
while httpd_thread.is_alive():
94-
httpd_thread.join(timeout=0.5)
125+
while not terminated:
126+
time.sleep(0.5)
127+
128+
httpd.shutdown()
129+
httpd_thread.join()
130+
131+
if use_dns:
132+
dns_udp.stop()
133+
dns_tcp.stop()
134+
135+
dns_udp.thread.join()
136+
dns_tcp.thread.join()
95137

96138
if __name__ == '__main__':
97139
try:

subregsim/api.py

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import logging
77
import random
88
import string
9+
import threading
910
import pysimplesoap.server as soapserver
1011
from http.server import HTTPServer
1112

@@ -19,6 +20,37 @@ def __init__(self, username, password, domain):
1920
self.domain = domain
2021
self.db = []
2122
self.ssid = None
23+
self.sn = 1
24+
self.db_lock = threading.Lock()
25+
26+
def toZone(self):
27+
zone = []
28+
zone.append("$ORIGIN .")
29+
zone.append("$TTL 1800")
30+
31+
with self.db_lock:
32+
zone.append("{} IN SOA ns.example.com admin.example.com ( {} 86400 900 1209600 1800 )".format(
33+
self.domain,
34+
self.sn,
35+
))
36+
37+
for rr in self.db:
38+
name = rr["name"]
39+
type = rr["type"]
40+
content = rr["content"] if "content" in rr else ""
41+
prio = rr["prio"]
42+
ttl = rr["ttl"] if rr["ttl"] != 1800 else ""
43+
44+
if type != "MX":
45+
prio = ""
46+
47+
if type == "TXT":
48+
content = '"{}"'.format(content.replace('"', r'\"'))
49+
50+
zone.append("{} {} IN {} {} {}".format(
51+
name, ttl, type, prio, content))
52+
53+
return "\n".join(zone)
2254

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

214-
self.db.append(new_record)
246+
with self.db_lock:
247+
self.db.append(new_record)
248+
self.sn += 1
215249

216250
return {
217251
"response": {
@@ -276,22 +310,24 @@ def modify_dns_record(self, ssid, domain, record):
276310
}
277311
}
278312

279-
found_items = [item for item in self.db if item["id"] == record["id"]]
280-
if len(found_items) == 0:
281-
return {
282-
"response": {
283-
"status": "error",
284-
"error": {
285-
"errormsg": "Record does not exist",
286-
"errorcode": {
287-
"major": 524,
288-
"minor": 1003
313+
with self.db_lock:
314+
found_items = [item for item in self.db if item["id"] == record["id"]]
315+
if len(found_items) == 0:
316+
return {
317+
"response": {
318+
"status": "error",
319+
"error": {
320+
"errormsg": "Record does not exist",
321+
"errorcode": {
322+
"major": 524,
323+
"minor": 1003
324+
}
289325
}
290326
}
291327
}
292-
}
293328

294-
found_items[0].update(record)
329+
found_items[0].update(record)
330+
self.sn += 1
295331

296332
return {
297333
"response": {
@@ -356,21 +392,24 @@ def delete_dns_record(self, ssid, domain, record):
356392
}
357393
}
358394

359-
before_length = len(self.db)
360-
self.db = [item for item in self.db if item["id"] != record["id"]]
361-
if before_length == len(self.db):
362-
return {
363-
"response": {
364-
"status": "error",
365-
"error": {
366-
"errormsg": "Record does not exist",
367-
"errorcode": {
368-
"major": 524,
369-
"minor": 1003
395+
with self.db_lock:
396+
before_length = len(self.db)
397+
self.db = [item for item in self.db if item["id"] != record["id"]]
398+
if before_length == len(self.db):
399+
return {
400+
"response": {
401+
"status": "error",
402+
"error": {
403+
"errormsg": "Record does not exist",
404+
"errorcode": {
405+
"major": 524,
406+
"minor": 1003
407+
}
370408
}
371409
}
372410
}
373-
}
411+
412+
self.sn += 1
374413

375414
return {
376415
"response": {

subregsim/dns.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'''
2+
Subreg.cz API simulator suitable for Python lexicon module.
3+
'''
4+
5+
from __future__ import (absolute_import, print_function)
6+
import logging
7+
import dnslib.server
8+
import dnslib.zoneresolver
9+
10+
log = logging.getLogger(__name__)
11+
12+
class ApiDnsResolver(dnslib.server.BaseResolver):
13+
def __init__(self, api):
14+
dnslib.server.BaseResolver.__init__(self)
15+
self.api = api
16+
17+
def resolve(self, request, handler):
18+
resolver = dnslib.zoneresolver.ZoneResolver(self.api.toZone(), True)
19+
return resolver.resolve(request, handler)
20+
21+
class ApiDns(dnslib.server.DNSServer):
22+
def __init__(self, resolver, address, port, tcp=False):
23+
dnslib.server.DNSServer.__init__(self, resolver, address, port, tcp)

0 commit comments

Comments
 (0)