You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

124 lines
3.4 KiB
Python

3 years ago
#!/usr/bin/python3
3 years ago
import os, subprocess, socket, datetime, getopt, sys, re, atexit, yaml, logging as log
from pathlib import Path
3 years ago
import dns.flags
import dns.message
import dns.rdataclass
import dns.rdatatype
import dns.name
from typing import cast
def main(args):
setupLogging(True)
log.debug("Logging started")
3 years ago
config = readConfig("config.yml")
s = setupSocket(config['socket']['address'], config['socket']['port'])
3 years ago
startListen(s)
def setupSocket(address, port):
# IPv4/IPv6-check
if address == "" or ":" in address:
family = socket.AF_INET6
else:
family = socket.AF_INET
s = socket.socket(family, socket.SOCK_DGRAM)
3 years ago
s.bind((address, port))
return s
def startListen(s):
3 years ago
log.debug(f'Now listening')
3 years ago
while True:
(address, dmsg) = receiveFromWire(s)
if handleQuery(s, address, dmsg) != True:
continue
def receiveFromWire(s):
(wire, address) = s.recvfrom(512)
dmsg = dns.message.from_wire(wire)
return (address, dmsg)
def handleQuery(s, address, dmsg):
log.info(f'{address[0]} |\tGot query')
opcode = dmsg.opcode()
if opcode != dns.opcode.NOTIFY:
log.error(f"{address[0]} |\tExpected opcode=NOTIFY, but was {dns.opcode.to_text(opcode)}")
makeResponseWithRCode(s, address, dmsg, dns.rcode.REFUSED)
return False
rcode = dmsg.rcode()
if rcode != dns.rcode.NOERROR:
log.error(f"{address[0]} |\tExpected rcode=NOERROR, but was {dns.rcode.to_text(rcode)}")
makeResponseWithRCode(s, address, dmsg, dns.rcode.FORMERR)
return False
#flags = dmsg.flags
#if flags != dns.flags.AA:
# print('Expected flags=AA, but was', dns.flags.to_text(flags))
# continue
if len(dmsg.question) != 1:
log.error(f'{address[0]} |\tExpected question-len=1, but was {len(dmsg.question)}')
makeResponseWithRCode(s, address, dmsg, dns.rcode.FORMERR)
return False
# Check record in question
record = dmsg.question[0]
r_datatype = record.rdtype
3 years ago
if r_datatype != dns.rdatatype.SOA:
log.error(f'{address[0]} |\tExpected record to be SOA, but was {r_datatype}')
makeResponseWithRCode(s, address, dmsg, dns.rcode.FORMERR)
return False
log.info(f'{address[0]} |\tNOTIFY for {record.name}')
response = dns.message.make_response(dmsg) # type: dns.message.Message
response.flags |= dns.flags.AA
sendResponse(s, address, response)
log.debug(f'{address[0]} |\tSent response')
return True
def makeResponseWithRCode(socket, address, dmsg, rcode):
response = dns.message.make_response(dmsg) # type: dns.message.Message
response.set_rcode(rcode)
sendResponse(socket, address, response)
def sendResponse(socket, address, response):
wire = response.to_wire(cast(dns.name.Name, response))
socket.sendto(wire, address)
def setupLogging(verbose: bool):
level = log.INFO
format = '%(asctime)s %(levelname)s:\t%(message)s'
if verbose:
level = log.DEBUG
log.basicConfig(stream=sys.stdout, format=format, level=level)
3 years ago
def readConfig(file: str):
log.debug(f"Reading config '{file}'..")
if not os.path.isfile(file):
raise OSError(2, file)
return readYamlFile(file)
def readYamlFile(file: str):
with open(file, "r") as f:
return yaml.load(f, Loader=yaml.FullLoader)
3 years ago
if __name__ == "__main__":
sys.exit(main(sys.argv))