Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added DB/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions DB/fetchDNSRecords.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import json

class FetchDNSRecords:
def __init__(self, domain):
self.domain = domain
self.stripTrailingDot()

def stripTrailingDot(self):
if self.domain.endswith('.'):
self.domain = self.domain[:-1]

def fetchRecords(self):
with open('DB/registry.json', 'r') as file:
data = json.load(file)
if self.domain in data:
return data[self.domain]
else:
return None
return None
6 changes: 6 additions & 0 deletions DB/registry.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"test.ks":{
"test.ks":["A","20.207.73.82",0,"Sample A Record pointing to github.com"],
"sub.test.ks":["CNAME","test.ks",0,"Sample CNAME Record"]
}
}
Empty file added config/__init__.py
Empty file.
40 changes: 40 additions & 0 deletions config/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
class Config:
def __init__(self):
self.maxWorkers = 10
self.host = "0.0.0.0"
self.port = 53
self.bufferSize = 512
self.supporterdRRTypes = ["A","CNAME"]
self.googleDNShost = "1.1.1.1"
self.gooogleDNSport = 53
self.googleUpstreamTimeout = 10
self.authTLD = "ks"


def getMaxWorkers(self):
return self.maxWorkers

def getHost(self):
return self.host

def getPort(self):
return self.port

def getBufferSize(self):
return self.bufferSize

def getSupportedRRTypes(self):
return self.supporterdRRTypes

def getGoogleDNShost(self):
return self.googleDNShost

def getGoogleDNSport(self):
return self.gooogleDNSport

def getAuthTLD(self):
return self.authTLD

def getGoogleUpstreamTimeout(self):
return self.googleUpstreamTimeout

69 changes: 69 additions & 0 deletions core/DNSMessageHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from DB.fetchDNSRecords import FetchDNSRecords
from config.config import Config
from core.support.domainParser import DomainParser
from core.support.DNSParser import DNSParser
from core.support.DNSRespBuilder import DNSResponseBuilder


class DNSMessageHandler:
def __init__(self,dnsMsg):
self.config = Config()
self.respBuilder = DNSResponseBuilder(dnsMsg)
self.dnsParser = DNSParser(dnsMsg)
self.domainParser = DomainParser(self.dnsParser.getQueryDomain())
self.dnsMsg = dnsMsg

def isAuthoritative(self):
domain = self.dnsParser.getQueryDomain()
if domain.endswith("." + self.config.getAuthTLD()) or domain.endswith("." + self.config.getAuthTLD() + "."):
return True
else:
return False

def fetchDomainDNSRecords(self):
domain = self.domainParser.extractDomain()
return FetchDNSRecords(domain).fetchRecords()

def __handleARecord__(self,dnsRecord):
if dnsRecord[0] == "A":
self.respBuilder.RR_A(dnsRecord)
elif dnsRecord[0] == "CNAME":
self.respBuilder.RR_CNAME(dnsRecord)
else:
self.respBuilder.emptyResponse()

def __handleCNAMERecord__(self,dnsRecord):
if dnsRecord[0] == "CNAME":
self.respBuilder.RR_CNAME(dnsRecord)
else:
self.respBuilder.emptyResponse()

def __isSupportedRRType__(self):
if self.dnsParser.getQueryTypeName() not in self.config.getSupportedRRTypes():
return False
return True

def handleQuery(self):
if not self.isAuthoritative() and not self.respBuilder.upstreamResp():
return None
if not self.__isSupportedRRType__():
self.respBuilder.notImplemented()
return
dnsRecords = self.fetchDomainDNSRecords()
if not dnsRecords:
self.respBuilder.emptyResponse()
return
cleanedDomain = self.domainParser.handleFQDN()
particularDNSRecord = dnsRecords.get(cleanedDomain)
if not particularDNSRecord:
self.respBuilder.emptyResponse()
return
if self.dnsParser.getQueryTypeName() == "A":
self.__handleARecord__(particularDNSRecord)
return
elif self.dnsParser.getQueryTypeName() == "CNAME":
self.__handleCNAMERecord__(particularDNSRecord)
return

def getResponse(self):
return self.respBuilder.packResponse()
33 changes: 33 additions & 0 deletions core/DNSServer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import socket
from config.config import Config

class DNSServer:
def __init__(self):
self.config = Config()
self.host = self.config.getHost()
self.port = self.config.getPort()
self.bufferSize = self.config.getBufferSize()
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

def start(self):
server = (self.host, self.port)
self.sock.bind(server)
print("Listening on " + self.host + ":" + str(self.port))

def stop(self):
self.sock.close()
print("Server stopped.")

def getRequest(self):
try:
data, addr = self.sock.recvfrom(self.bufferSize)
return data, addr
except socket.error as e:
print("Socket error: " + str(e))
return None, None

def sendResponse(self, data, addr):
try:
self.sock.sendto(data, addr)
except socket.error as e:
print("Socket error: " + str(e))
Empty file added core/__init__.py
Empty file.
25 changes: 25 additions & 0 deletions core/support/DNSParser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from dnslib.dns import DNSRecord,QTYPE

class DNSParser:
def __init__(self, dnsMsg):
self.dnsMsg = dnsMsg
self.parsedMsg = self.dnsReqMsgParse()
self.DNSRecordTypes = QTYPE.forward

def dnsReqMsgParse(self):
return DNSRecord.parse(self.dnsMsg)

def getQueryDomain(self):
return str(self.parsedMsg.q.qname)

def getQueryType(self):
return str(self.parsedMsg.q.qtype)

def getQueryTypeName(self):
return self.DNSRecordTypes.get(self.parsedMsg.q.qtype)

def isQueryTypeValid(self):
if str(self.parsedMsg.q.qtype) in self.DNSRecordTypes:
return True
else:
return False
69 changes: 69 additions & 0 deletions core/support/DNSRespBuilder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from core.upstreamResolver import UpstreamResolver
from core.support.DNSParser import DNSParser
from dnslib.dns import DNSRecord,DNSHeader,DNSQuestion,RR,CNAME,A,RCODE,QTYPE


class DNSResponseBuilder:
def __init__(self, dnsMsg):
self.dnsMsg = dnsMsg
self.upstreamResolver = UpstreamResolver()
self.parsedMsg = DNSParser(dnsMsg).dnsReqMsgParse()
self.DNSRecordTypes = QTYPE.forward
self.dnsResp = None
self.packedDNSResp = None

def createResponseDNSRecord(self, rcode, rdata, ttl, atype=None, aa=1):
ra = 0 # 1 Recursion Available 0 for not available
qname = str(self.parsedMsg.q.qname)
qclass = self.parsedMsg.q.qclass
dnsHeader = DNSHeader(id=self.parsedMsg.header.id, qr=1, aa=aa, ra=ra, rcode=rcode)
dnsQuestion = DNSQuestion(qname=qname, qtype=self.parsedMsg.q.qtype, qclass=qclass)
dnsAnswer = None
if rcode == RCODE.NOERROR and rdata is not None and atype is not None:
dnsAnswer = RR(str(qname), rtype=atype, rclass=qclass, ttl=ttl,
rdata=rdata,)
dnsRecord = DNSRecord(dnsHeader,
q=dnsQuestion,
a=dnsAnswer)
return dnsRecord

def notImplemented(self):
self.dnsResp = self.createResponseDNSRecord(RCODE.NOTIMP,None,0)
return self

def nxDomain(self):
self.dnsResp = self.createResponseDNSRecord(RCODE.NXDOMAIN,None,0)

def serverFailure(self):
self.dnsResp = self.createResponseDNSRecord(RCODE.SERVFAIL,None,0)

def emptyResponse(self):
self.dnsResp = self.createResponseDNSRecord(RCODE.NOERROR,None,0)

def RR_A(self,value):
if not value or not isinstance(value, list):
self.emptyResponse()
return
self.dnsResp = self.createResponseDNSRecord(RCODE.NOERROR,A(value[1]),0,QTYPE.A)

def RR_CNAME(self,value):
if not value or not isinstance(value, list):
self.emptyResponse()
return
self.dnsResp = self.createResponseDNSRecord(RCODE.NOERROR,CNAME(value[1]),0,QTYPE.CNAME)

def upstreamResp(self):
upstreamResp = self.upstreamResolver.sendQuery(self.dnsMsg)
if not upstreamResp:
self.serverFailure()
return None
self.packedDNSResp = upstreamResp
self.dnsResp = True #TODO: Temp bypass fix later

def packResponse(self):
if self.dnsResp is None:
self.serverFailure()
elif hasattr(self.dnsResp, "pack"):
self.packedDNSResp = self.dnsResp.pack()
return self.packedDNSResp

Empty file added core/support/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions core/support/domainParser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import re

class DomainParser():
def __init__(self, domain):
self.domain = domain

def isFQDN(self):
return self.domain.endswith('.') and '.' in self.domain[:-1]

def handleFQDN(self):
if self.isFQDN() and self.domain.endswith('.'):
return self.domain[:-1]
return self.domain

def extractDomain(self):
match = re.search(r'([a-zA-Z0-9-]+\.[a-zA-Z]{2,})$', self.handleFQDN())
if match:
return match.group(1)
return None
25 changes: 25 additions & 0 deletions core/upstreamResolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import socket
from config.config import Config

class UpstreamResolver:

def __init__(self):
self.config = Config()
self.upstreamHost = self.config.getGoogleDNShost()
self.upstreamPort = self.config.getGoogleDNSport()
self.upstreamDNS = (self.upstreamHost, self.upstreamPort)
self.bufferSize = self.config.getBufferSize()
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.settimeout(self.config.getGoogleUpstreamTimeout())

def sendQuery(self, data):
try:
self.sock.sendto(data, self.upstreamDNS)
response_data, _ = self.sock.recvfrom(512)
return response_data
except socket.timeout:
print("Upstream DNS query timed out.")
return None
except Exception as e:
print(f"Error occured Upstream Server : {e}")
return None
Loading