This is an automated email from the ASF dual-hosted git repository.

paziz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit a197ef5989b11e3e52ea099dec2ee5e3ad9fda5b
Author: Persia Aziz <per...@yahoo-inc.com>
AuthorDate: Fri Aug 25 13:59:29 2017 -0500

    microDNS
---
 tests/tools/microDNS/README.md            |  46 ++++++++
 tests/tools/microDNS/sample_zonefile.json |   9 ++
 tests/tools/microDNS/uDNS.py              | 175 ++++++++++++++++++++++++++++++
 3 files changed, 230 insertions(+)

diff --git a/tests/tools/microDNS/README.md b/tests/tools/microDNS/README.md
new file mode 100644
index 0000000..e655d31
--- /dev/null
+++ b/tests/tools/microDNS/README.md
@@ -0,0 +1,46 @@
+uDNS
+=====
+
+uDNS is a small DNS server that takes in a pre-defined set of domain names and 
the IPs that each domain name maps to. The mappings should be inputted with a 
JSON file in a format described below.
+
+uDNS runs on localhost and serves whatever port is specified in the command 
line arguments. uDNS serves both UDP and TCP connections.
+
+If uDNS does not find the requested domain in the explicitly mapped section of 
the JSON, uDNS will respond with the IPs given in the `otherwise` section of 
the JSON. The `otherwise` section is mandatory.
+
+
+JSON format
+------
+```json
+{
+  "mappings": [
+    {"domain1": ["ip1", "ip2", "etc"]},
+    {"domain2": ["ip3", "ip4", "etc"]},
+    {"domain3": ["ip5"]},
+  ],
+
+  "otherwise": ["defaultip1", "defaultip2", "etc"]
+}
+```
+
+An example can be found in `sample_zonefile.json`
+
+
+Caveat
+------
+You should not include any two records like this: `host1.example.com` and 
`example.com`
+
+A DNS request for `host1.example.com` could return the A-record associated 
with `host1.example.com` or `example.com`, depending on your luck.
+
+
+Running
+------
+`python3 uDNS.py port zone_file [--rr]`
+
+For a detailed description of flags, see `python3 uDNS.py -h`
+
+
+Use with Apache Traffic Server
+------
+1. In `records.config`, add configuration lines: `CONFIG 
proxy.config.dns.nameservers STRING 127.0.0.1:PORT` and `CONFIG 
proxy.config.dns.round_robin_nameservers INT 0`, where `PORT` is whatever port 
you want uDNS to serve on.
+2. Run uDNS on `PORT`
+3. Now all domains mapped in the uDNS JSON config file should be mapped by ATS 
as well
diff --git a/tests/tools/microDNS/sample_zonefile.json 
b/tests/tools/microDNS/sample_zonefile.json
new file mode 100644
index 0000000..e4c8282
--- /dev/null
+++ b/tests/tools/microDNS/sample_zonefile.json
@@ -0,0 +1,9 @@
+{ 
+  "mappings": [
+    {"abc.xyz.com.": ["127.0.0.1","127.0.1.1"]},
+    {"yahoo.com.": ["128.0.0.1", "128.0.1.0"]},
+    {"yelp.com.": ["34.35.166.23", "129.0.0.1"]}
+  ],
+
+  "otherwise": ["127.0.0.1", "127.1.1.1"]
+}
diff --git a/tests/tools/microDNS/uDNS.py b/tests/tools/microDNS/uDNS.py
new file mode 100644
index 0000000..f82d48e
--- /dev/null
+++ b/tests/tools/microDNS/uDNS.py
@@ -0,0 +1,175 @@
+# coding=utf-8
+
+#  
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#  
+#     http://www.apache.org/licenses/LICENSE-2.0
+#  
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import datetime
+import sys
+import time
+import threading
+import traceback
+import socketserver
+import argparse
+import codecs
+import json
+from dnslib import *
+
+TTL = 60 * 5  # completely arbitrary TTL value
+round_robin = False
+default_records = list()
+records = dict()
+
+class DomainName(str):
+    def __getattr__(self, item):
+        return DomainName(item + '.' + self)
+
+class BaseRequestHandler(socketserver.BaseRequestHandler):
+
+    def get_data(self):
+        raise NotImplementedError
+
+    def send_data(self, data):
+        raise NotImplementedError
+
+    def handle(self):
+        now = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')
+        print("\n\n%s request %s (%s %s):" % (self.__class__.__name__[:3], 
now, self.client_address[0],
+                                               self.client_address[1]))
+        try:
+            data = self.get_data()
+            self.send_data(dns_response(data))
+        except Exception:
+            traceback.print_exc(file=sys.stderr)
+
+
+class TCPRequestHandler(BaseRequestHandler):
+
+    def get_data(self):
+        data = self.request.recv(8192).strip()
+        sz = int(codecs.encode(data[:2], 'hex'), 16)
+        if sz < len(data) - 2:
+            raise Exception("Wrong size of TCP packet")
+        elif sz > len(data) - 2:
+            raise Exception("Too big TCP packet")
+        return data[2:]
+
+    def send_data(self, data):
+        sz = codecs.decode(hex(len(data))[2:].zfill(4), 'hex')
+        return self.request.sendall(sz + data)
+
+
+class UDPRequestHandler(BaseRequestHandler):
+
+    def get_data(self):
+        return self.request[0].strip()
+
+    def send_data(self, data):
+        return self.request[1].sendto(data, self.client_address)
+
+def build_domain_mappings(path):
+    with open(path) as f:
+        zone_file = json.load(f)
+
+    for domain in zone_file['mappings']:
+        for d in iter(domain.keys()):
+            # this loop only runs once, kind of a hack to access the only key 
in the dict
+            domain_name = DomainName(d)            
+            print("Domain name:",domain_name)
+            records[domain_name] = [A(x) for x in domain[domain_name]]
+            print(records[domain_name])
+
+    default_records.extend([A(d) for d in zone_file['otherwise']])
+
+def add_authoritative_records(reply, domain):
+    # ns1 and ns1 are hardcoded in, change if necessary
+    reply.add_auth(RR(rname=domain, rtype=QTYPE.NS, rclass=1, ttl=TTL, 
rdata=NS(domain.ns1)))
+    reply.add_auth(RR(rname=domain, rtype=QTYPE.NS, rclass=1, ttl=TTL, 
rdata=NS(domain.ns2)))
+
+def dns_response(data):
+    ''' dns_response takes in the raw bytes from the socket and does all the 
logic behind what 
+        RRs get returned as the response '''
+    global default_records, records, TTL, round_robin
+
+    request = DNSRecord.parse(data)
+    print(request)
+
+    reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1), 
q=request.q)
+    qname = request.q.qname
+    qn = str(qname)
+    qtype = request.q.qtype
+    qt = QTYPE[qtype]
+    found_specific = False
+
+    # first look for a specific mapping
+    for domain, rrs in records.items():
+        if domain == qn or qn.endswith('.' + domain):
+        # we are the authoritative name server for this domain and all 
subdomains
+            for rdata in rrs:
+                # only include requested record types (ie. A, MX, etc)
+                rqt = rdata.__class__.__name__
+                if qt in ['*', rqt]:  
+                    found_specific = True
+                    reply.add_answer(RR(rname=qname, rtype=getattr(QTYPE, 
str(rqt)), rclass=1, ttl=TTL, rdata=rdata))
+
+            # rotate the A entries if round robin is on
+            if round_robin:
+                a_records = [x for x in rrs if type(x) == A]
+                records[domain] = a_records[1:] + a_records[:1]  # rotate list
+            break
+
+    # else if a specific mapping is not found, return default A-records
+    if not found_specific:
+        for a in default_records:
+            reply.add_answer(RR(rname=qname, rtype=QTYPE.A, rclass=1, ttl=TTL, 
rdata=a))
+
+        if round_robin:
+            default_records = default_records[1:] + default_records[:1]
+    print("---- Reply: ----\n", reply)
+    return reply.pack()
+
+
+if __name__ == '__main__':
+    # handle cmd line args
+    parser = argparse.ArgumentParser()    
+    parser.add_argument("ip_addr",type=str, 
help="Interface",default="127.0.0.1")
+    parser.add_argument("port", type=int, help="port uDNS should listen on")
+    parser.add_argument("zone_file", help="path to zone file")
+    parser.add_argument("--rr", action='store_true', help='round robin load 
balances if multiple IP addresses are present for 1 domain')
+    args = parser.parse_args()
+
+    if args.rr:
+        round_robin = True
+    build_domain_mappings(args.zone_file)
+
+    servers = [
+        socketserver.ThreadingUDPServer((args.ip_addr, args.port), 
UDPRequestHandler),
+        socketserver.ThreadingTCPServer((args.ip_addr, args.port), 
TCPRequestHandler),
+    ]
+
+    print("Starting DNS...")
+    for s in servers:
+        thread = threading.Thread(target=s.serve_forever)  # that thread will 
start one more thread for each request
+        thread.daemon = True  # exit the server thread when the main thread 
terminates
+        thread.start()
+
+    try:
+        while 1:
+            time.sleep(1)
+            sys.stderr.flush()
+            sys.stdout.flush()
+
+    except KeyboardInterrupt:
+        pass
+    finally:
+        for s in servers:
+            s.shutdown()

-- 
To stop receiving notification emails like this one, please contact
"commits@trafficserver.apache.org" <commits@trafficserver.apache.org>.

Reply via email to